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