1 // sass.hpp must go before all system headers to get the 2 // __EXTENSIONS__ fix on Solaris. 3 #include "sass.hpp" 4 5 #include <cmath> 6 #include "operators.hpp" 7 8 namespace Sass { 9 10 namespace Operators { 11 add(double x,double y)12 inline double add(double x, double y) { return x + y; } sub(double x,double y)13 inline double sub(double x, double y) { return x - y; } mul(double x,double y)14 inline double mul(double x, double y) { return x * y; } div(double x,double y)15 inline double div(double x, double y) { return x / y; } // x/0 checked by caller 16 mod(double x,double y)17 inline double mod(double x, double y) { // x/0 checked by caller 18 if ((x > 0 && y < 0) || (x < 0 && y > 0)) { 19 double ret = std::fmod(x, y); 20 return ret ? ret + y : ret; 21 } else { 22 return std::fmod(x, y); 23 } 24 } 25 26 typedef double (*bop)(double, double); 27 bop ops[Sass_OP::NUM_OPS] = { 28 0, 0, // and, or 29 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte 30 add, sub, mul, div, mod 31 }; 32 33 /* static function, has no pstate or traces */ eq(ExpressionObj lhs,ExpressionObj rhs)34 bool eq(ExpressionObj lhs, ExpressionObj rhs) 35 { 36 // operation is undefined if one is not a number 37 if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); 38 // use compare operator from ast node 39 return *lhs == *rhs; 40 } 41 42 /* static function, throws OperationError, has no pstate or traces */ cmp(ExpressionObj lhs,ExpressionObj rhs,const Sass_OP op)43 bool cmp(ExpressionObj lhs, ExpressionObj rhs, const Sass_OP op) 44 { 45 // can only compare numbers!? 46 Number_Obj l = Cast<Number>(lhs); 47 Number_Obj r = Cast<Number>(rhs); 48 // operation is undefined if one is not a number 49 if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); 50 // use compare operator from ast node 51 return *l < *r; 52 } 53 54 /* static functions, throws OperationError, has no pstate or traces */ lt(ExpressionObj lhs,ExpressionObj rhs)55 bool lt(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } neq(ExpressionObj lhs,ExpressionObj rhs)56 bool neq(ExpressionObj lhs, ExpressionObj rhs) { return eq(lhs, rhs) == false; } gt(ExpressionObj lhs,ExpressionObj rhs)57 bool gt(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } lte(ExpressionObj lhs,ExpressionObj rhs)58 bool lte(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } gte(ExpressionObj lhs,ExpressionObj rhs)59 bool gte(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } 60 61 /* colour math deprecation warning */ op_color_deprecation(enum Sass_OP op,sass::string lsh,sass::string rhs,const SourceSpan & pstate)62 void op_color_deprecation(enum Sass_OP op, sass::string lsh, sass::string rhs, const SourceSpan& pstate) 63 { 64 deprecated( 65 "The operation `" + lsh + " " + sass_op_to_name(op) + " " + rhs + 66 "` is deprecated and will be an error in future versions.", 67 "Consider using Sass's color functions instead.\n" 68 "https://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions", 69 /*with_column=*/false, pstate); 70 } 71 72 /* static function, throws OperationError, has no traces but optional pstate for returned value */ op_strings(Sass::Operand operand,Value & lhs,Value & rhs,struct Sass_Inspect_Options opt,const SourceSpan & pstate,bool delayed)73 Value* op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) 74 { 75 enum Sass_OP op = operand.operand; 76 77 String_Quoted* lqstr = Cast<String_Quoted>(&lhs); 78 String_Quoted* rqstr = Cast<String_Quoted>(&rhs); 79 80 sass::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); 81 sass::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); 82 83 if (Cast<Null>(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); 84 if (Cast<Null>(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); 85 86 sass::string sep; 87 switch (op) { 88 case Sass_OP::ADD: sep = ""; break; 89 case Sass_OP::SUB: sep = "-"; break; 90 case Sass_OP::DIV: sep = "/"; break; 91 case Sass_OP::EQ: sep = "=="; break; 92 case Sass_OP::NEQ: sep = "!="; break; 93 case Sass_OP::LT: sep = "<"; break; 94 case Sass_OP::GT: sep = ">"; break; 95 case Sass_OP::LTE: sep = "<="; break; 96 case Sass_OP::GTE: sep = ">="; break; 97 default: 98 throw Exception::UndefinedOperation(&lhs, &rhs, op); 99 break; 100 } 101 102 if (op == Sass_OP::ADD) { 103 // create string that might be quoted on output (but do not unquote what we pass) 104 return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); 105 } 106 107 // add whitespace around operator 108 // but only if result is not delayed 109 if (sep != "" && delayed == false) { 110 if (operand.ws_before) sep = " " + sep; 111 if (operand.ws_after) sep = sep + " "; 112 } 113 114 if (op == Sass_OP::SUB || op == Sass_OP::DIV) { 115 if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); 116 if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); 117 } 118 119 return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); 120 } 121 122 /* ToDo: allow to operate also with hsla colors */ 123 /* static function, throws OperationError, has no traces but optional pstate for returned value */ op_colors(enum Sass_OP op,const Color_RGBA & lhs,const Color_RGBA & rhs,struct Sass_Inspect_Options opt,const SourceSpan & pstate,bool delayed)124 Value* op_colors(enum Sass_OP op, const Color_RGBA& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) 125 { 126 127 if (lhs.a() != rhs.a()) { 128 throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); 129 } 130 if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && (!rhs.r() || !rhs.g() || !rhs.b())) { 131 throw Exception::ZeroDivisionError(lhs, rhs); 132 } 133 134 op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); 135 136 return SASS_MEMORY_NEW(Color_RGBA, 137 pstate, 138 ops[op](lhs.r(), rhs.r()), 139 ops[op](lhs.g(), rhs.g()), 140 ops[op](lhs.b(), rhs.b()), 141 lhs.a()); 142 } 143 144 /* static function, throws OperationError, has no traces but optional pstate for returned value */ op_numbers(enum Sass_OP op,const Number & lhs,const Number & rhs,struct Sass_Inspect_Options opt,const SourceSpan & pstate,bool delayed)145 Value* op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) 146 { 147 double lval = lhs.value(); 148 double rval = rhs.value(); 149 150 if (op == Sass_OP::MOD && rval == 0) { 151 return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); 152 } 153 154 if (op == Sass_OP::DIV && rval == 0) { 155 sass::string result(lval ? "Infinity" : "NaN"); 156 return SASS_MEMORY_NEW(String_Quoted, pstate, result); 157 } 158 159 size_t l_n_units = lhs.numerators.size(); 160 size_t l_d_units = lhs.numerators.size(); 161 size_t r_n_units = rhs.denominators.size(); 162 size_t r_d_units = rhs.denominators.size(); 163 // optimize out the most common and simplest case 164 if (l_n_units == r_n_units && l_d_units == r_d_units) { 165 if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { 166 if (lhs.numerators == rhs.numerators) { 167 if (lhs.denominators == rhs.denominators) { 168 Number* v = SASS_MEMORY_COPY(&lhs); 169 v->value(ops[op](lval, rval)); 170 return v; 171 } 172 } 173 } 174 } 175 176 Number_Obj v = SASS_MEMORY_COPY(&lhs); 177 178 if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { 179 v->numerators = rhs.numerators; 180 v->denominators = rhs.denominators; 181 } 182 183 if (op == Sass_OP::MUL) { 184 v->value(ops[op](lval, rval)); 185 v->numerators.insert(v->numerators.end(), 186 rhs.numerators.begin(), rhs.numerators.end() 187 ); 188 v->denominators.insert(v->denominators.end(), 189 rhs.denominators.begin(), rhs.denominators.end() 190 ); 191 v->reduce(); 192 } 193 else if (op == Sass_OP::DIV) { 194 v->value(ops[op](lval, rval)); 195 v->numerators.insert(v->numerators.end(), 196 rhs.denominators.begin(), rhs.denominators.end() 197 ); 198 v->denominators.insert(v->denominators.end(), 199 rhs.numerators.begin(), rhs.numerators.end() 200 ); 201 v->reduce(); 202 } 203 else { 204 Number ln(lhs), rn(rhs); 205 ln.reduce(); rn.reduce(); 206 double f(rn.convert_factor(ln)); 207 v->value(ops[op](lval, rn.value() * f)); 208 } 209 210 v->pstate(pstate); 211 return v.detach(); 212 } 213 214 /* static function, throws OperationError, has no traces but optional pstate for returned value */ op_number_color(enum Sass_OP op,const Number & lhs,const Color_RGBA & rhs,struct Sass_Inspect_Options opt,const SourceSpan & pstate,bool delayed)215 Value* op_number_color(enum Sass_OP op, const Number& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) 216 { 217 double lval = lhs.value(); 218 219 switch (op) { 220 case Sass_OP::ADD: 221 case Sass_OP::MUL: { 222 op_color_deprecation(op, lhs.to_string(), rhs.to_string(opt), pstate); 223 return SASS_MEMORY_NEW(Color_RGBA, 224 pstate, 225 ops[op](lval, rhs.r()), 226 ops[op](lval, rhs.g()), 227 ops[op](lval, rhs.b()), 228 rhs.a()); 229 } 230 case Sass_OP::SUB: 231 case Sass_OP::DIV: { 232 sass::string color(rhs.to_string(opt)); 233 op_color_deprecation(op, lhs.to_string(), color, pstate); 234 return SASS_MEMORY_NEW(String_Quoted, 235 pstate, 236 lhs.to_string(opt) 237 + sass_op_separator(op) 238 + color); 239 } 240 default: break; 241 } 242 throw Exception::UndefinedOperation(&lhs, &rhs, op); 243 } 244 245 /* static function, throws OperationError, has no traces but optional pstate for returned value */ op_color_number(enum Sass_OP op,const Color_RGBA & lhs,const Number & rhs,struct Sass_Inspect_Options opt,const SourceSpan & pstate,bool delayed)246 Value* op_color_number(enum Sass_OP op, const Color_RGBA& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) 247 { 248 double rval = rhs.value(); 249 250 if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && rval == 0) { 251 // comparison of Fixnum with Float failed? 252 throw Exception::ZeroDivisionError(lhs, rhs); 253 } 254 255 op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); 256 257 return SASS_MEMORY_NEW(Color_RGBA, 258 pstate, 259 ops[op](lhs.r(), rval), 260 ops[op](lhs.g(), rval), 261 ops[op](lhs.b(), rval), 262 lhs.a()); 263 } 264 265 } 266 267 } 268