1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | |
5 | #include <cstdint> |
6 | #include <cstdlib> |
7 | #include <cmath> |
8 | #include <random> |
9 | #include <sstream> |
10 | #include <iomanip> |
11 | #include <algorithm> |
12 | |
13 | #include "ast.hpp" |
14 | #include "units.hpp" |
15 | #include "fn_utils.hpp" |
16 | #include "fn_numbers.hpp" |
17 | |
18 | #ifdef __MINGW32__ |
19 | #include "windows.h" |
20 | #include "wincrypt.h" |
21 | #endif |
22 | |
23 | namespace Sass { |
24 | |
25 | namespace Functions { |
26 | |
27 | #ifdef __MINGW32__ |
28 | uint64_t GetSeed() |
29 | { |
30 | HCRYPTPROV hp = 0; |
31 | BYTE rb[8]; |
32 | CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); |
33 | CryptGenRandom(hp, sizeof(rb), rb); |
34 | CryptReleaseContext(hp, 0); |
35 | |
36 | uint64_t seed; |
37 | memcpy(&seed, &rb[0], sizeof(seed)); |
38 | |
39 | return seed; |
40 | } |
41 | #else |
42 | uint64_t GetSeed() |
43 | { |
44 | std::random_device rd; |
45 | return rd(); |
46 | } |
47 | #endif |
48 | |
49 | // note: the performance of many implementations of |
50 | // random_device degrades sharply once the entropy pool |
51 | // is exhausted. For practical use, random_device is |
52 | // generally only used to seed a PRNG such as mt19937. |
53 | static std::mt19937 rand(static_cast<unsigned int>(GetSeed())); |
54 | |
55 | /////////////////// |
56 | // NUMBER FUNCTIONS |
57 | /////////////////// |
58 | |
59 | Signature percentage_sig = "percentage($number)" ; |
60 | BUILT_IN(percentage) |
61 | { |
62 | Number_Obj n = ARGN("$number" ); |
63 | if (!n->is_unitless()) error(msg: "argument $number of `" + sass::string(sig) + "` must be unitless" , pstate, traces); |
64 | return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%" ); |
65 | } |
66 | |
67 | Signature round_sig = "round($number)" ; |
68 | BUILT_IN(round) |
69 | { |
70 | Number_Obj r = ARGN("$number" ); |
71 | r->value(value__: Sass::round(val: r->value(), precision: ctx.c_options.precision)); |
72 | r->pstate(pstate__: pstate); |
73 | return r.detach(); |
74 | } |
75 | |
76 | Signature ceil_sig = "ceil($number)" ; |
77 | BUILT_IN(ceil) |
78 | { |
79 | Number_Obj r = ARGN("$number" ); |
80 | r->value(value__: std::ceil(x: r->value())); |
81 | r->pstate(pstate__: pstate); |
82 | return r.detach(); |
83 | } |
84 | |
85 | Signature floor_sig = "floor($number)" ; |
86 | BUILT_IN(floor) |
87 | { |
88 | Number_Obj r = ARGN("$number" ); |
89 | r->value(value__: std::floor(x: r->value())); |
90 | r->pstate(pstate__: pstate); |
91 | return r.detach(); |
92 | } |
93 | |
94 | Signature abs_sig = "abs($number)" ; |
95 | BUILT_IN(abs) |
96 | { |
97 | Number_Obj r = ARGN("$number" ); |
98 | r->value(value__: std::abs(x: r->value())); |
99 | r->pstate(pstate__: pstate); |
100 | return r.detach(); |
101 | } |
102 | |
103 | Signature min_sig = "min($numbers...)" ; |
104 | BUILT_IN(min) |
105 | { |
106 | List* arglist = ARG("$numbers" , List); |
107 | Number_Obj least; |
108 | size_t L = arglist->length(); |
109 | if (L == 0) { |
110 | error(msg: "At least one argument must be passed." , pstate, traces); |
111 | } |
112 | for (size_t i = 0; i < L; ++i) { |
113 | ExpressionObj val = arglist->value_at_index(i); |
114 | Number_Obj xi = Cast<Number>(ptr: val); |
115 | if (!xi) { |
116 | error(msg: "\"" + val->to_string(opt: ctx.c_options) + "\" is not a number for `min'" , pstate, traces); |
117 | } |
118 | if (least) { |
119 | if (*xi < *least) least = xi; |
120 | } else least = xi; |
121 | } |
122 | return least.detach(); |
123 | } |
124 | |
125 | Signature max_sig = "max($numbers...)" ; |
126 | BUILT_IN(max) |
127 | { |
128 | List* arglist = ARG("$numbers" , List); |
129 | Number_Obj greatest; |
130 | size_t L = arglist->length(); |
131 | if (L == 0) { |
132 | error(msg: "At least one argument must be passed." , pstate, traces); |
133 | } |
134 | for (size_t i = 0; i < L; ++i) { |
135 | ExpressionObj val = arglist->value_at_index(i); |
136 | Number_Obj xi = Cast<Number>(ptr: val); |
137 | if (!xi) { |
138 | error(msg: "\"" + val->to_string(opt: ctx.c_options) + "\" is not a number for `max'" , pstate, traces); |
139 | } |
140 | if (greatest) { |
141 | if (*greatest < *xi) greatest = xi; |
142 | } else greatest = xi; |
143 | } |
144 | return greatest.detach(); |
145 | } |
146 | |
147 | Signature random_sig = "random($limit:false)" ; |
148 | BUILT_IN(random) |
149 | { |
150 | AST_Node_Obj arg = env["$limit" ]; |
151 | Value* v = Cast<Value>(ptr: arg); |
152 | Number* l = Cast<Number>(ptr: arg); |
153 | Boolean* b = Cast<Boolean>(ptr: arg); |
154 | if (l) { |
155 | double lv = l->value(); |
156 | if (lv < 1) { |
157 | sass::ostream err; |
158 | err << "$limit " << lv << " must be greater than or equal to 1 for `random'" ; |
159 | error(msg: err.str(), pstate, traces); |
160 | } |
161 | bool eq_int = std::fabs(x: trunc(x: lv) - lv) < NUMBER_EPSILON; |
162 | if (!eq_int) { |
163 | sass::ostream err; |
164 | err << "Expected $limit to be an integer but got " << lv << " for `random'" ; |
165 | error(msg: err.str(), pstate, traces); |
166 | } |
167 | std::uniform_real_distribution<> distributor(1, lv + 1); |
168 | uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand)); |
169 | return SASS_MEMORY_NEW(Number, pstate, (double)distributed); |
170 | } |
171 | else if (b) { |
172 | std::uniform_real_distribution<> distributor(0, 1); |
173 | double distributed = static_cast<double>(distributor(rand)); |
174 | return SASS_MEMORY_NEW(Number, pstate, distributed); |
175 | } else if (v) { |
176 | traces.push_back(x: Backtrace(pstate)); |
177 | throw Exception::InvalidArgumentType(pstate, traces, "random" , "$limit" , "number" , v); |
178 | } else { |
179 | traces.push_back(x: Backtrace(pstate)); |
180 | throw Exception::InvalidArgumentType(pstate, traces, "random" , "$limit" , "number" ); |
181 | } |
182 | } |
183 | |
184 | Signature unique_id_sig = "unique-id()" ; |
185 | BUILT_IN(unique_id) |
186 | { |
187 | sass::ostream ss; |
188 | std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 |
189 | uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand)); |
190 | ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; |
191 | return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); |
192 | } |
193 | |
194 | Signature unit_sig = "unit($number)" ; |
195 | BUILT_IN(unit) |
196 | { |
197 | Number_Obj arg = ARGN("$number" ); |
198 | sass::string str(quote(arg->unit(), q: '"')); |
199 | return SASS_MEMORY_NEW(String_Quoted, pstate, str); |
200 | } |
201 | |
202 | Signature unitless_sig = "unitless($number)" ; |
203 | BUILT_IN(unitless) |
204 | { |
205 | Number_Obj arg = ARGN("$number" ); |
206 | bool unitless = arg->is_unitless(); |
207 | return SASS_MEMORY_NEW(Boolean, pstate, unitless); |
208 | } |
209 | |
210 | Signature comparable_sig = "comparable($number1, $number2)" ; |
211 | BUILT_IN(comparable) |
212 | { |
213 | Number_Obj n1 = ARGN("$number1" ); |
214 | Number_Obj n2 = ARGN("$number2" ); |
215 | if (n1->is_unitless() || n2->is_unitless()) { |
216 | return SASS_MEMORY_NEW(Boolean, pstate, true); |
217 | } |
218 | // normalize into main units |
219 | n1->normalize(); n2->normalize(); |
220 | Units &lhs_unit = *n1, &rhs_unit = *n2; |
221 | bool is_comparable = (lhs_unit == rhs_unit); |
222 | return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); |
223 | } |
224 | |
225 | } |
226 | |
227 | } |
228 | |