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