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