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 | |
12 | inline double add(double x, double y) { return x + y; } |
13 | inline double sub(double x, double y) { return x - y; } |
14 | inline double mul(double x, double y) { return x * y; } |
15 | inline double div(double x, double y) { return x / y; } // x/0 checked by caller |
16 | |
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: x, y: y); |
20 | return ret ? ret + y : ret; |
21 | } else { |
22 | return std::fmod(x: x, y: 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 */ |
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 */ |
43 | bool cmp(ExpressionObj lhs, ExpressionObj rhs, const Sass_OP op) |
44 | { |
45 | // can only compare numbers!? |
46 | Number_Obj l = Cast<Number>(ptr: lhs); |
47 | Number_Obj r = Cast<Number>(ptr: 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 */ |
55 | bool lt(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, op: Sass_OP::LT); } |
56 | bool neq(ExpressionObj lhs, ExpressionObj rhs) { return eq(lhs, rhs) == false; } |
57 | bool gt(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, op: Sass_OP::GT) && neq(lhs, rhs); } |
58 | bool lte(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, op: Sass_OP::LTE) || eq(lhs, rhs); } |
59 | bool gte(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, op: Sass_OP::GTE) || eq(lhs, rhs); } |
60 | |
61 | /* colour math deprecation warning */ |
62 | void op_color_deprecation(enum Sass_OP op, sass::string lsh, sass::string rhs, const SourceSpan& pstate) |
63 | { |
64 | deprecated( |
65 | msg: "The operation `" + lsh + " " + sass_op_to_name(op) + " " + rhs + |
66 | "` is deprecated and will be an error in future versions." , |
67 | msg2: "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 */ |
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>(ptr: &lhs); |
78 | String_Quoted* rqstr = Cast<String_Quoted>(ptr: &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>(ptr: &lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); |
84 | if (Cast<Null>(ptr: &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 */ |
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, lsh: lhs.to_string(), rhs: 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 */ |
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(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(value__: ops[op](lval, rval)); |
185 | v->numerators.insert(position: v->numerators.end(), |
186 | first: rhs.numerators.begin(), last: rhs.numerators.end() |
187 | ); |
188 | v->denominators.insert(position: v->denominators.end(), |
189 | first: rhs.denominators.begin(), last: rhs.denominators.end() |
190 | ); |
191 | v->reduce(); |
192 | } |
193 | else if (op == Sass_OP::DIV) { |
194 | v->value(value__: ops[op](lval, rval)); |
195 | v->numerators.insert(position: v->numerators.end(), |
196 | first: rhs.denominators.begin(), last: rhs.denominators.end() |
197 | ); |
198 | v->denominators.insert(position: v->denominators.end(), |
199 | first: rhs.numerators.begin(), last: 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(value__: ops[op](lval, rn.value() * f)); |
208 | } |
209 | |
210 | v->pstate(pstate__: pstate); |
211 | return v.detach(); |
212 | } |
213 | |
214 | /* static function, throws OperationError, has no traces but optional pstate for returned value */ |
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, lsh: lhs.to_string(), rhs: 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, lsh: lhs.to_string(), rhs: 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 */ |
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::DIV) && rval == 0) { |
251 | // comparison of Fixnum with Float failed? |
252 | throw Exception::ZeroDivisionError(lhs, rhs); |
253 | } |
254 | |
255 | op_color_deprecation(op, lsh: lhs.to_string(), rhs: 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 | |