1#include "sass.hpp"
2#include "sass.h"
3#include "ast.hpp"
4#include "util.hpp"
5#include "util_string.hpp"
6#include "lexer.hpp"
7#include "prelexer.hpp"
8#include "constants.hpp"
9#include "utf8/checked.h"
10
11#include <cmath>
12#include <stdint.h>
13#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64)
14#include <mutex>
15#endif
16
17namespace Sass {
18
19 double round(double val, size_t precision)
20 {
21 // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets
22 // See https://github.com/sass/node-sass/issues/1854 for details
23 // FIXME: Remove this workaround when we switch to VS2015+
24 #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64)
25 static std::once_flag flag;
26 std::call_once(flag, []() { _set_FMA3_enable(0); });
27 #endif
28
29 // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93
30 if (std::fmod(x: val, y: 1) - 0.5 > - std::pow(x: 0.1, y: precision + 1)) return std::ceil(x: val);
31 else if (std::fmod(x: val, y: 1) - 0.5 > std::pow(x: 0.1, y: precision)) return std::floor(x: val);
32 // work around some compiler issue
33 // cygwin has it not defined in std
34 using namespace std;
35 return ::round(x: val);
36 }
37
38 /* Locale unspecific atof function. */
39 double sass_strtod(const char *str)
40 {
41 char separator = *(localeconv()->decimal_point);
42 if(separator != '.'){
43 // The current locale specifies another
44 // separator. convert the separator to the
45 // one understood by the locale if needed
46 const char *found = strchr(s: str, c: '.');
47 if(found != NULL){
48 // substitution is required. perform the substitution on a copy
49 // of the string. This is slower but it is thread safe.
50 char *copy = sass_copy_c_string(str);
51 *(copy + (found - str)) = separator;
52 double res = strtod(nptr: copy, NULL);
53 free(ptr: copy);
54 return res;
55 }
56 }
57
58 return strtod(nptr: str, NULL);
59 }
60
61 // helper for safe access to c_ctx
62 const char* safe_str (const char* str, const char* alt) {
63 return str == NULL ? alt : str;
64 }
65
66 void free_string_array(char ** arr) {
67 if(!arr)
68 return;
69
70 char **it = arr;
71 while (it && (*it)) {
72 free(ptr: *it);
73 ++it;
74 }
75
76 free(ptr: arr);
77 }
78
79 char **copy_strings(const sass::vector<sass::string>& strings, char*** array, int skip) {
80 int num = static_cast<int>(strings.size()) - skip;
81 char** arr = (char**) calloc(nmemb: num + 1, size: sizeof(char*));
82 if (arr == 0)
83 return *array = (char **)NULL;
84
85 for(int i = 0; i < num; i++) {
86 arr[i] = (char*) malloc(size: sizeof(char) * (strings[i + skip].size() + 1));
87 if (arr[i] == 0) {
88 free_string_array(arr);
89 return *array = (char **)NULL;
90 }
91 std::copy(first: strings[i + skip].begin(), last: strings[i + skip].end(), result: arr[i]);
92 arr[i][strings[i + skip].size()] = '\0';
93 }
94
95 arr[num] = 0;
96 return *array = arr;
97 }
98
99 // read css string (handle multiline DELIM)
100 sass::string read_css_string(const sass::string& str, bool css)
101 {
102 if (!css) return str;
103 sass::string out("");
104 bool esc = false;
105 for (auto i : str) {
106 if (i == '\\') {
107 esc = ! esc;
108 } else if (esc && i == '\r') {
109 continue;
110 } else if (esc && i == '\n') {
111 out.resize (n: out.size () - 1);
112 esc = false;
113 continue;
114 } else {
115 esc = false;
116 }
117 out.push_back(c: i);
118 }
119 // happens when parsing does not correctly skip
120 // over escaped sequences for ie. interpolations
121 // one example: foo\#{interpolate}
122 // if (esc) out += '\\';
123 return out;
124 }
125
126 // double escape all escape sequences
127 // keep unescaped quotes and backslashes
128 sass::string evacuate_escapes(const sass::string& str)
129 {
130 sass::string out("");
131 bool esc = false;
132 for (auto i : str) {
133 if (i == '\\' && !esc) {
134 out += '\\';
135 out += '\\';
136 esc = true;
137 } else if (esc && i == '"') {
138 out += '\\';
139 out += i;
140 esc = false;
141 } else if (esc && i == '\'') {
142 out += '\\';
143 out += i;
144 esc = false;
145 } else if (esc && i == '\\') {
146 out += '\\';
147 out += i;
148 esc = false;
149 } else {
150 esc = false;
151 out += i;
152 }
153 }
154 // happens when parsing does not correctly skip
155 // over escaped sequences for ie. interpolations
156 // one example: foo\#{interpolate}
157 // if (esc) out += '\\';
158 return out;
159 }
160
161 // bell characters are replaced with spaces
162 void newline_to_space(sass::string& str)
163 {
164 std::replace(first: str.begin(), last: str.end(), old_value: '\n', new_value: ' ');
165 }
166
167 // 1. Removes whitespace after newlines.
168 // 2. Replaces newlines with spaces.
169 //
170 // This method only considers LF and CRLF as newlines.
171 sass::string string_to_output(const sass::string& str)
172 {
173 sass::string result;
174 result.reserve(res_arg: str.size());
175 std::size_t pos = 0;
176 while (true) {
177 const std::size_t newline = str.find_first_of(s: "\n\r", pos: pos);
178 if (newline == sass::string::npos) break;
179 result.append(str: str, pos: pos, n: newline - pos);
180 if (str[newline] == '\r') {
181 if (str[newline + 1] == '\n') {
182 pos = newline + 2;
183 } else {
184 // CR without LF: append as-is and continue.
185 result += '\r';
186 pos = newline + 1;
187 continue;
188 }
189 } else {
190 pos = newline + 1;
191 }
192 result += ' ';
193 const std::size_t non_space = str.find_first_not_of(s: " \f\n\r\t\v", pos: pos);
194 if (non_space != sass::string::npos) {
195 pos = non_space;
196 }
197 }
198 result.append(str: str, pos: pos, n: sass::string::npos);
199 return result;
200 }
201
202 sass::string escape_string(const sass::string& str)
203 {
204 sass::string out;
205 out.reserve(res_arg: str.size());
206 for (char c : str) {
207 switch (c) {
208 case '\n':
209 out.append(s: "\\n");
210 break;
211 case '\r':
212 out.append(s: "\\r");
213 break;
214 case '\f':
215 out.append(s: "\\f");
216 break;
217 default:
218 out += c;
219 }
220 }
221 return out;
222 }
223
224 sass::string comment_to_compact_string(const sass::string& text)
225 {
226 sass::string str = "";
227 size_t has = 0;
228 char prev = 0;
229 bool clean = false;
230 for (auto i : text) {
231 if (clean) {
232 if (i == '\n') { has = 0; }
233 else if (i == '\t') { ++ has; }
234 else if (i == ' ') { ++ has; }
235 else if (i == '*') {}
236 else {
237 clean = false;
238 str += ' ';
239 if (prev == '*' && i == '/') str += "*/";
240 else str += i;
241 }
242 } else if (i == '\n') {
243 clean = true;
244 } else {
245 str += i;
246 }
247 prev = i;
248 }
249 if (has) return str;
250 else return text;
251 }
252
253 // find best quote_mark by detecting if the string contains any single
254 // or double quotes. When a single quote is found, we not we want a double
255 // quote as quote_mark. Otherwise we check if the string cotains any double
256 // quotes, which will trigger the use of single quotes as best quote_mark.
257 char detect_best_quotemark(const char* s, char qm)
258 {
259 // ensure valid fallback quote_mark
260 char quote_mark = qm && qm != '*' ? qm : '"';
261 while (*s) {
262 // force double quotes as soon
263 // as one single quote is found
264 if (*s == '\'') { return '"'; }
265 // a single does not force quote_mark
266 // maybe we see a double quote later
267 else if (*s == '"') { quote_mark = '\''; }
268 ++ s;
269 }
270 return quote_mark;
271 }
272
273 sass::string read_hex_escapes(const sass::string& s)
274 {
275
276 sass::string result;
277 bool skipped = false;
278
279 for (size_t i = 0, L = s.length(); i < L; ++i) {
280
281 // implement the same strange ruby sass behavior
282 // an escape sequence can also mean a unicode char
283 if (s[i] == '\\' && !skipped) {
284
285 // remember
286 skipped = true;
287
288 // escape length
289 size_t len = 1;
290
291 // parse as many sequence chars as possible
292 // ToDo: Check if ruby aborts after possible max
293 while (i + len < L && s[i + len] && Util::ascii_isxdigit(c: static_cast<unsigned char>(s[i + len]))) ++ len;
294
295 if (len > 1) {
296
297 // convert the extracted hex string to code point value
298 // ToDo: Maybe we could do this without creating a substring
299 uint32_t cp = strtol(nptr: s.substr (pos: i + 1, n: len - 1).c_str(), NULL, base: 16);
300
301 if (s[i + len] == ' ') ++ len;
302
303 // assert invalid code points
304 if (cp == 0) cp = 0xFFFD;
305 // replace bell character
306 // if (cp == '\n') cp = 32;
307
308 // use a very simple approach to convert via utf8 lib
309 // maybe there is a more elegant way; maybe we shoud
310 // convert the whole output from string to a stream!?
311 // allocate memory for utf8 char and convert to utf8
312 unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, result: u);
313 for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(c: u[m]);
314
315 // skip some more chars?
316 i += len - 1; skipped = false;
317
318 }
319
320 else {
321
322 skipped = false;
323
324 result.push_back(c: s[i]);
325
326 }
327
328 }
329
330 else {
331
332 result.push_back(c: s[i]);
333
334 }
335
336 }
337
338 return result;
339
340 }
341
342 sass::string unquote(const sass::string& s, char* qd, bool keep_utf8_sequences, bool strict)
343 {
344
345 // not enough room for quotes
346 // no possibility to unquote
347 if (s.length() < 2) return s;
348
349 char q;
350 bool skipped = false;
351
352 // this is no guarantee that the unquoting will work
353 // what about whitespace before/after the quote_mark?
354 if (*s.begin() == '"' && *s.rbegin() == '"') q = '"';
355 else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\'';
356 else return s;
357
358 sass::string unq;
359 unq.reserve(res_arg: s.length()-2);
360
361 for (size_t i = 1, L = s.length() - 1; i < L; ++i) {
362
363 // implement the same strange ruby sass behavior
364 // an escape sequence can also mean a unicode char
365 if (s[i] == '\\' && !skipped) {
366 // remember
367 skipped = true;
368
369 // skip it
370 // ++ i;
371
372 // if (i == L) break;
373
374 // escape length
375 size_t len = 1;
376
377 // parse as many sequence chars as possible
378 // ToDo: Check if ruby aborts after possible max
379 while (i + len < L && s[i + len] && Util::ascii_isxdigit(c: static_cast<unsigned char>(s[i + len]))) ++ len;
380
381 // hex string?
382 if (keep_utf8_sequences) {
383 unq.push_back(c: s[i]);
384 } else if (len > 1) {
385
386 // convert the extracted hex string to code point value
387 // ToDo: Maybe we could do this without creating a substring
388 uint32_t cp = strtol(nptr: s.substr (pos: i + 1, n: len - 1).c_str(), NULL, base: 16);
389
390 if (s[i + len] == ' ') ++ len;
391
392 // assert invalid code points
393 if (cp == 0) cp = 0xFFFD;
394 // replace bell character
395 // if (cp == '\n') cp = 32;
396
397 // use a very simple approach to convert via utf8 lib
398 // maybe there is a more elegant way; maybe we shoud
399 // convert the whole output from string to a stream!?
400 // allocate memory for utf8 char and convert to utf8
401 unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, result: u);
402 for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(c: u[m]);
403
404 // skip some more chars?
405 i += len - 1; skipped = false;
406
407 }
408
409
410 }
411 // check for unexpected delimiter
412 // be strict and throw error back
413 // else if (!skipped && q == s[i]) {
414 // // don't be that strict
415 // return s;
416 // // this basically always means an internal error and not users fault
417 // error("Unescaped delimiter in string to unquote found. [" + s + "]", SourceSpan("[UNQUOTE]"));
418 // }
419 else {
420 if (strict && !skipped) {
421 if (s[i] == q) return s;
422 }
423 skipped = false;
424 unq.push_back(c: s[i]);
425 }
426
427 }
428 if (skipped) { return s; }
429 if (qd) *qd = q;
430 return unq;
431
432 }
433
434 sass::string quote(const sass::string& s, char q)
435 {
436
437 // autodetect with fallback to given quote
438 q = detect_best_quotemark(s: s.c_str(), qm: q);
439
440 // return an empty quoted string
441 if (s.empty()) return sass::string(2, q ? q : '"');
442
443 sass::string quoted;
444 quoted.reserve(res_arg: s.length()+2);
445 quoted.push_back(c: q);
446
447 const char* it = s.c_str();
448 const char* end = it + strlen(s: it) + 1;
449 while (*it && it < end) {
450 const char* now = it;
451
452 if (*it == q) {
453 quoted.push_back(c: '\\');
454 } else if (*it == '\\') {
455 quoted.push_back(c: '\\');
456 }
457
458 int cp = utf8::next(it, end);
459
460 // in case of \r, check if the next in sequence
461 // is \n and then advance the iterator and skip \r
462 if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') {
463 cp = utf8::next(it, end);
464 }
465
466 if (cp == '\n') {
467 quoted.push_back(c: '\\');
468 quoted.push_back(c: 'a');
469 // we hope we can remove this flag once we figure out
470 // why ruby sass has these different output behaviors
471 // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ")
472 using namespace Prelexer;
473 if (alternatives <
474 Prelexer::char_range<'a', 'f'>,
475 Prelexer::char_range<'A', 'F'>,
476 Prelexer::char_range<'0', '9'>,
477 space
478 >(src: it) != NULL) {
479 quoted.push_back(c: ' ');
480 }
481 } else if (cp < 127) {
482 quoted.push_back(c: (char) cp);
483 } else {
484 while (now < it) {
485 quoted.push_back(c: *now);
486 ++ now;
487 }
488 }
489 }
490
491 quoted.push_back(c: q);
492 return quoted;
493 }
494
495 bool is_hex_doublet(double n)
496 {
497 return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 ||
498 n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 ||
499 n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB ||
500 n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ;
501 }
502
503 bool is_color_doublet(double r, double g, double b)
504 {
505 return is_hex_doublet(n: r) && is_hex_doublet(n: g) && is_hex_doublet(n: b);
506 }
507
508 bool peek_linefeed(const char* start)
509 {
510 using namespace Prelexer;
511 using namespace Constants;
512 return sequence <
513 zero_plus <
514 alternatives <
515 exactly <' '>,
516 exactly <'\t'>,
517 line_comment,
518 block_comment,
519 delimited_by <
520 slash_star,
521 star_slash,
522 false
523 >
524 >
525 >,
526 re_linebreak
527 >(src: start) != 0;
528 }
529
530 namespace Util {
531
532 bool isPrintable(StyleRule* r, Sass_Output_Style style) {
533 if (r == NULL) {
534 return false;
535 }
536
537 Block_Obj b = r->block();
538
539 SelectorList* sl = r->selector();
540 bool hasSelectors = sl ? sl->length() > 0 : false;
541
542 if (!hasSelectors) {
543 return false;
544 }
545
546 bool hasDeclarations = false;
547 bool hasPrintableChildBlocks = false;
548 for (size_t i = 0, L = b->length(); i < L; ++i) {
549 Statement_Obj stm = b->at(i);
550 if (Cast<AtRule>(ptr: stm)) {
551 return true;
552 } else if (Declaration* d = Cast<Declaration>(ptr: stm)) {
553 return isPrintable(d, style);
554 } else if (ParentStatement* p = Cast<ParentStatement>(ptr: stm)) {
555 Block_Obj pChildBlock = p->block();
556 if (isPrintable(b: pChildBlock, style)) {
557 hasPrintableChildBlocks = true;
558 }
559 } else if (Comment* c = Cast<Comment>(ptr: stm)) {
560 // keep for uncompressed
561 if (style != COMPRESSED) {
562 hasDeclarations = true;
563 }
564 // output style compressed
565 if (c->is_important()) {
566 hasDeclarations = c->is_important();
567 }
568 } else {
569 hasDeclarations = true;
570 }
571
572 if (hasDeclarations || hasPrintableChildBlocks) {
573 return true;
574 }
575 }
576
577 return false;
578 }
579
580 bool isPrintable(String_Constant* s, Sass_Output_Style style)
581 {
582 return ! s->value().empty();
583 }
584
585 bool isPrintable(String_Quoted* s, Sass_Output_Style style)
586 {
587 return true;
588 }
589
590 bool isPrintable(Declaration* d, Sass_Output_Style style)
591 {
592 ExpressionObj val = d->value();
593 if (String_Quoted_Obj sq = Cast<String_Quoted>(ptr: val)) return isPrintable(s: sq.ptr(), style);
594 if (String_Constant_Obj sc = Cast<String_Constant>(ptr: val)) return isPrintable(s: sc.ptr(), style);
595 return true;
596 }
597
598 bool isPrintable(SupportsRule* f, Sass_Output_Style style) {
599 if (f == NULL) {
600 return false;
601 }
602
603 Block_Obj b = f->block();
604
605 bool hasDeclarations = false;
606 bool hasPrintableChildBlocks = false;
607 for (size_t i = 0, L = b->length(); i < L; ++i) {
608 Statement_Obj stm = b->at(i);
609 if (Cast<Declaration>(ptr: stm) || Cast<AtRule>(ptr: stm)) {
610 hasDeclarations = true;
611 }
612 else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) {
613 Block_Obj pChildBlock = b->block();
614 if (!b->is_invisible()) {
615 if (isPrintable(b: pChildBlock, style)) {
616 hasPrintableChildBlocks = true;
617 }
618 }
619 }
620
621 if (hasDeclarations || hasPrintableChildBlocks) {
622 return true;
623 }
624 }
625
626 return false;
627 }
628
629 bool isPrintable(CssMediaRule* m, Sass_Output_Style style)
630 {
631 if (m == nullptr) return false;
632 Block_Obj b = m->block();
633 if (b == nullptr) return false;
634 if (m->empty()) return false;
635 for (size_t i = 0, L = b->length(); i < L; ++i) {
636 Statement_Obj stm = b->at(i);
637 if (Cast<AtRule>(ptr: stm)) return true;
638 else if (Cast<Declaration>(ptr: stm)) return true;
639 else if (Comment* c = Cast<Comment>(ptr: stm)) {
640 if (isPrintable(b: c, style)) {
641 return true;
642 }
643 }
644 else if (StyleRule* r = Cast<StyleRule>(ptr: stm)) {
645 if (isPrintable(r, style)) {
646 return true;
647 }
648 }
649 else if (SupportsRule* f = Cast<SupportsRule>(ptr: stm)) {
650 if (isPrintable(f, style)) {
651 return true;
652 }
653 }
654 else if (CssMediaRule* mb = Cast<CssMediaRule>(ptr: stm)) {
655 if (isPrintable(m: mb, style)) {
656 return true;
657 }
658 }
659 else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) {
660 if (isPrintable(b: b->block(), style)) {
661 return true;
662 }
663 }
664 }
665 return false;
666 }
667
668 bool isPrintable(Comment* c, Sass_Output_Style style)
669 {
670 // keep for uncompressed
671 if (style != COMPRESSED) {
672 return true;
673 }
674 // output style compressed
675 if (c->is_important()) {
676 return true;
677 }
678 // not printable
679 return false;
680 };
681
682 bool isPrintable(Block_Obj b, Sass_Output_Style style) {
683 if (!b) {
684 return false;
685 }
686
687 for (size_t i = 0, L = b->length(); i < L; ++i) {
688 Statement_Obj stm = b->at(i);
689 if (Cast<Declaration>(ptr: stm) || Cast<AtRule>(ptr: stm)) {
690 return true;
691 }
692 else if (Comment* c = Cast<Comment>(ptr: stm)) {
693 if (isPrintable(c, style)) {
694 return true;
695 }
696 }
697 else if (StyleRule* r = Cast<StyleRule>(ptr: stm)) {
698 if (isPrintable(r, style)) {
699 return true;
700 }
701 }
702 else if (SupportsRule* f = Cast<SupportsRule>(ptr: stm)) {
703 if (isPrintable(f, style)) {
704 return true;
705 }
706 }
707 else if (CssMediaRule * m = Cast<CssMediaRule>(ptr: stm)) {
708 if (isPrintable(m, style)) {
709 return true;
710 }
711 }
712 else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) {
713 if (isPrintable(b: b->block(), style)) {
714 return true;
715 }
716 }
717 }
718
719 return false;
720 }
721
722 }
723}
724

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