6 #include "prelexer.hpp"
7 #include "constants.hpp"
8 #include "utf8/checked.h"
15 double round(double val, size_t precision)
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
26 /* Locale unspecific atof function. */
27 double sass_atof(const char *str)
29 char separator = *(localeconv()->decimal_point);
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, '.');
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);
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;
54 void free_string_array(char ** arr) {
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*));
71 return *array = (char **)NULL;
73 for(int i = 0; i < num; i++) {
74 arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1));
76 free_string_array(arr);
77 return *array = (char **)NULL;
79 std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]);
80 arr[i][strings[i + skip].size()] = '\0';
87 // read css string (handle multiline DELIM)
88 std::string read_css_string(const std::string& str)
95 } else if (esc && i == '\r') {
97 } else if (esc && i == '\n') {
98 out.resize (out.size () - 1);
106 // happens when parsing does not correctly skip
107 // over escaped sequences for ie. interpolations
108 // one example: foo\#{interpolate}
109 // if (esc) out += '\\';
113 // double escape all escape sequences
114 // keep unescaped quotes and backslashes
115 std::string evacuate_escapes(const std::string& str)
120 if (i == '\\' && !esc) {
124 } else if (esc && i == '"') {
128 } else if (esc && i == '\'') {
132 } else if (esc && i == '\\') {
141 // happens when parsing does not correctly skip
142 // over escaped sequences for ie. interpolations
143 // one example: foo\#{interpolate}
144 // if (esc) out += '\\';
148 // bell characters are replaced with spaces
149 void newline_to_space(std::string& str)
151 std::replace(str.begin(), str.end(), '\n', ' ');
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)
164 } else if (!(lf && isspace(i))) {
172 std::string comment_to_string(const std::string& text)
174 std::string str = "";
178 for (auto i : text) {
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 == '*') {}
188 if (prev == '*' && i == '/') str += "*/";
191 } else if (i == '\n') {
193 } else if (i == '\r') {
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)
210 // ensure valid fallback quote_mark
211 char quote_mark = qm && qm != '*' ? qm : '"';
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 = '\''; }
224 std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict)
227 // not enough room for quotes
228 // no possibility to unquote
229 if (s.length() < 2) return s;
232 bool skipped = false;
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 = '\'';
241 unq.reserve(s.length()-2);
243 for (size_t i = 1, L = s.length() - 1; i < L; ++i) {
245 // implement the same strange ruby sass behavior
246 // an escape sequence can also mean a unicode char
247 if (s[i] == '\\' && !skipped) {
254 // if (i == L) break;
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;
264 if (keep_utf8_sequences) {
266 } else if (len > 1) {
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);
272 if (s[i + len] == ' ') ++ len;
274 // assert invalid code points
275 if (cp == 0) cp = 0xFFFD;
276 // replace bell character
277 // if (cp == '\n') cp = 32;
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]);
286 // skip some more chars?
287 i += len - 1; skipped = false;
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
298 // // this basically always means an internal error and not users fault
299 // error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]"));
302 if (strict && !skipped) {
303 if (s[i] == q) return s;
310 if (skipped) { return s; }
316 std::string quote(const std::string& s, char q)
319 // autodetect with fallback to given quote
320 q = detect_best_quotemark(s.c_str(), q);
322 // return an empty quoted string
323 if (s.empty()) return std::string(2, q ? q : '"');
326 quoted.reserve(s.length()+2);
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;
335 quoted.push_back('\\');
336 } else if (*it == '\\') {
337 quoted.push_back('\\');
340 int cp = utf8::next(it, end);
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);
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;
356 Prelexer::char_range<'a', 'f'>,
357 Prelexer::char_range<'A', 'F'>,
358 Prelexer::char_range<'0', '9'>,
361 quoted.push_back(' ');
363 } else if (cp < 127) {
364 quoted.push_back((char) cp);
367 quoted.push_back(*now);
377 bool is_hex_doublet(double n)
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 ;
385 bool is_color_doublet(double r, double g, double b)
387 return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b);
390 bool peek_linefeed(const char* start)
392 using namespace Prelexer;
393 using namespace Constants;
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(); }
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] == '_') {
434 std::string normalize_decimals(const std::string& str) {
435 std::string prefix = "0";
436 std::string normalized = str;
438 return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized;
441 bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style) {
446 Block_Obj b = r->block();
448 Selector_List_Ptr sl = Cast<Selector_List>(r->selector());
449 bool hasSelectors = sl ? sl->length() > 0 : false;
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)) {
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;
468 } else if (Comment_Ptr c = Cast<Comment>(stm)) {
469 // keep for uncompressed
470 if (style != COMPRESSED) {
471 hasDeclarations = true;
473 // output style compressed
474 if (c->is_important()) {
475 hasDeclarations = c->is_important();
478 hasDeclarations = true;
481 if (hasDeclarations || hasPrintableChildBlocks) {
489 bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style)
491 return ! s->value().empty();
494 bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style)
499 bool isPrintable(Declaration_Ptr d, Sass_Output_Style style)
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);
507 bool isPrintable(Supports_Block_Ptr f, Sass_Output_Style style) {
512 Block_Obj b = f->block();
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;
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;
528 if (hasDeclarations || hasPrintableChildBlocks) {
536 bool isPrintable(Media_Block_Ptr m, Sass_Output_Style style)
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)) {
550 else if (Ruleset_Ptr r = Cast<Ruleset>(stm)) {
551 if (isPrintable(r, style)) {
555 else if (Supports_Block_Ptr f = Cast<Supports_Block>(stm)) {
556 if (isPrintable(f, style)) {
560 else if (Media_Block_Ptr m = Cast<Media_Block>(stm)) {
561 if (isPrintable(m, style)) {
565 else if (Has_Block_Ptr b = Cast<Has_Block>(stm)) {
566 if (isPrintable(b->block(), style)) {
574 bool isPrintable(Comment_Ptr c, Sass_Output_Style style)
576 // keep for uncompressed
577 if (style != COMPRESSED) {
580 // output style compressed
581 if (c->is_important()) {
588 bool isPrintable(Block_Obj b, Sass_Output_Style style) {
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)) {
598 else if (Comment_Ptr c = Cast<Comment>(stm)) {
599 if (isPrintable(c, style)) {
603 else if (Ruleset_Ptr r = Cast<Ruleset>(stm)) {
604 if (isPrintable(r, style)) {
608 else if (Supports_Block_Ptr f = Cast<Supports_Block>(stm)) {
609 if (isPrintable(f, style)) {
613 else if (Media_Block_Ptr m = Cast<Media_Block>(stm)) {
614 if (isPrintable(m, style)) {
618 else if (Has_Block_Ptr b = Cast<Has_Block>(stm)) {
619 if (isPrintable(b->block(), style)) {
628 bool isAscii(const char chr) {
629 return unsigned(chr) < 128;