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
8namespace 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

source code of gtk/subprojects/libsass/src/operators.cpp