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