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__
GetSeed()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)";
BUILT_IN(percentage)60     BUILT_IN(percentage)
61     {
62       Number_Obj n = ARGN("$number");
63       if (!n->is_unitless()) error("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)";
BUILT_IN(round)68     BUILT_IN(round)
69     {
70       Number_Obj r = ARGN("$number");
71       r->value(Sass::round(r->value(), ctx.c_options.precision));
72       r->pstate(pstate);
73       return r.detach();
74     }
75 
76     Signature ceil_sig = "ceil($number)";
BUILT_IN(ceil)77     BUILT_IN(ceil)
78     {
79       Number_Obj r = ARGN("$number");
80       r->value(std::ceil(r->value()));
81       r->pstate(pstate);
82       return r.detach();
83     }
84 
85     Signature floor_sig = "floor($number)";
BUILT_IN(floor)86     BUILT_IN(floor)
87     {
88       Number_Obj r = ARGN("$number");
89       r->value(std::floor(r->value()));
90       r->pstate(pstate);
91       return r.detach();
92     }
93 
94     Signature abs_sig = "abs($number)";
BUILT_IN(abs)95     BUILT_IN(abs)
96     {
97       Number_Obj r = ARGN("$number");
98       r->value(std::abs(r->value()));
99       r->pstate(pstate);
100       return r.detach();
101     }
102 
103     Signature min_sig = "min($numbers...)";
BUILT_IN(min)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("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>(val);
115         if (!xi) {
116           error("\"" + val->to_string(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...)";
BUILT_IN(max)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("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>(val);
137         if (!xi) {
138           error("\"" + val->to_string(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)";
BUILT_IN(random)148     BUILT_IN(random)
149     {
150       AST_Node_Obj arg = env["$limit"];
151       Value* v = Cast<Value>(arg);
152       Number* l = Cast<Number>(arg);
153       Boolean* b = Cast<Boolean>(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(err.str(), pstate, traces);
160         }
161         bool eq_int = std::fabs(trunc(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(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(Backtrace(pstate));
177         throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v);
178       } else {
179         traces.push_back(Backtrace(pstate));
180         throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number");
181       }
182     }
183 
184     Signature unique_id_sig = "unique-id()";
BUILT_IN(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)";
BUILT_IN(unit)195     BUILT_IN(unit)
196     {
197       Number_Obj arg = ARGN("$number");
198       sass::string str(quote(arg->unit(), '"'));
199       return SASS_MEMORY_NEW(String_Quoted, pstate, str);
200     }
201 
202     Signature unitless_sig = "unitless($number)";
BUILT_IN(unitless)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)";
BUILT_IN(comparable)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