sassc 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -101,6 +101,7 @@ namespace Sass {
101
101
  extern Signature keywords_sig;
102
102
  extern Signature set_nth_sig;
103
103
  extern Signature unique_id_sig;
104
+ extern Signature is_superselector_sig;
104
105
 
105
106
  BUILT_IN(rgb);
106
107
  BUILT_IN(rgba_4);
@@ -175,6 +176,7 @@ namespace Sass {
175
176
  BUILT_IN(keywords);
176
177
  BUILT_IN(set_nth);
177
178
  BUILT_IN(unique_id);
179
+ BUILT_IN(is_superselector);
178
180
 
179
181
  }
180
182
  }
@@ -1,7 +1,3 @@
1
- #include "inspect.hpp"
2
- #include "ast.hpp"
3
- #include "context.hpp"
4
- #include "utf8/checked.h"
5
1
  #include <cmath>
6
2
  #include <string>
7
3
  #include <iostream>
@@ -9,6 +5,11 @@
9
5
  #include <stdint.h>
10
6
  #include <stdint.h>
11
7
 
8
+ #include "ast.hpp"
9
+ #include "inspect.hpp"
10
+ #include "context.hpp"
11
+ #include "utf8/checked.h"
12
+
12
13
  namespace Sass {
13
14
  using namespace std;
14
15
 
@@ -99,9 +100,10 @@ namespace Sass {
99
100
  append_token(at_rule->keyword(), at_rule);
100
101
  if (at_rule->selector()) {
101
102
  append_mandatory_space();
103
+ bool was_wrapped = in_wrapped;
102
104
  in_wrapped = true;
103
105
  at_rule->selector()->perform(this);
104
- in_wrapped = false;
106
+ in_wrapped = was_wrapped;
105
107
  }
106
108
  if (at_rule->block()) {
107
109
  at_rule->block()->perform(this);
@@ -114,6 +116,7 @@ namespace Sass {
114
116
  void Inspect::operator()(Declaration* dec)
115
117
  {
116
118
  if (dec->value()->concrete_type() == Expression::NULL_VAL) return;
119
+ bool was_decl = in_declaration;
117
120
  in_declaration = true;
118
121
  if (output_style() == NESTED)
119
122
  indentation += dec->tabs();
@@ -128,7 +131,7 @@ namespace Sass {
128
131
  append_delimiter();
129
132
  if (output_style() == NESTED)
130
133
  indentation -= dec->tabs();
131
- in_declaration = false;
134
+ in_declaration = was_decl;
132
135
  }
133
136
 
134
137
  void Inspect::operator()(Assignment* assn)
@@ -346,8 +349,20 @@ namespace Sass {
346
349
  else if (in_media_block && sep != " ") sep += " "; // verified
347
350
  if (list->empty()) return;
348
351
  bool items_output = false;
349
- in_declaration_list = in_declaration;
350
- for (size_t i = 0, L = list->length(); i < L; ++i) {
352
+
353
+ bool was_space_array = in_space_array;
354
+ bool was_comma_array = in_comma_array;
355
+ if (!in_declaration && (
356
+ (list->separator() == List::SPACE && in_space_array) ||
357
+ (list->separator() == List::COMMA && in_comma_array)
358
+ )) {
359
+ append_string("(");
360
+ }
361
+
362
+ if (list->separator() == List::SPACE) in_space_array = true;
363
+ else if (list->separator() == List::COMMA) in_comma_array = true;
364
+
365
+ for (size_t i = 0, L = list->size(); i < L; ++i) {
351
366
  Expression* list_item = (*list)[i];
352
367
  if (list_item->is_invisible()) {
353
368
  continue;
@@ -360,7 +375,16 @@ namespace Sass {
360
375
  list_item->perform(this);
361
376
  items_output = true;
362
377
  }
363
- in_declaration_list = false;
378
+
379
+ in_comma_array = was_comma_array;
380
+ in_space_array = was_space_array;
381
+ if (!in_declaration && (
382
+ (list->separator() == List::SPACE && in_space_array) ||
383
+ (list->separator() == List::COMMA && in_comma_array)
384
+ )) {
385
+ append_string(")");
386
+ }
387
+
364
388
  }
365
389
 
366
390
  void Inspect::operator()(Binary_Expression* expr)
@@ -416,43 +440,91 @@ namespace Sass {
416
440
 
417
441
  void Inspect::operator()(Number* n)
418
442
  {
443
+
444
+ string res;
445
+
446
+ // init stuff
419
447
  n->normalize();
448
+ int precision = 5;
449
+ double value = n->value();
450
+ // get option from optional context
451
+ if (ctx) precision = ctx->precision;
452
+
453
+ // check if the fractional part of the value equals to zero
454
+ // neat trick from http://stackoverflow.com/a/1521682/1550314
455
+ // double int_part; bool is_int = modf(value, &int_part) == 0.0;
456
+
457
+ // this all cannot be done with one run only, since fixed
458
+ // output differs from normal output and regular output
459
+ // can contain scientific notation which we do not want!
460
+
461
+ // first sample
420
462
  stringstream ss;
421
- ss.precision(ctx ? ctx->precision : 5);
422
- ss << fixed << n->value();
423
- string d(ss.str());
424
- // store if the value did not equal zero
425
- // if after applying precsision, the value gets
426
- // truncated to zero, sass emits 0.0 instead of 0
427
- bool nonzero = n->value() != 0;
428
- size_t decimal = d.find('.');
429
- if (decimal != string::npos) {
430
- for (size_t i = d.length()-1; d[i] == '0' && i >= decimal; --i) {
431
- d.resize(d.length()-1);
432
- }
433
- }
434
- if (d[d.length()-1] == '.') d.resize(d.length()-1);
463
+ ss.precision(12);
464
+ ss << value;
465
+
466
+ // check if we got scientific notation in result
467
+ if (ss.str().find_first_of("e") != string::npos) {
468
+ ss.clear(); ss.str(string());
469
+ ss.precision(max(12, precision));
470
+ ss << fixed << value;
471
+ }
472
+
473
+ string tmp = ss.str();
474
+ size_t pos_point = tmp.find_first_of(".,");
475
+ size_t pos_fract = tmp.find_last_not_of("0");
476
+ bool is_int = pos_point == pos_fract ||
477
+ pos_point == string::npos;
478
+
479
+ // reset stream for another run
480
+ ss.clear(); ss.str(string());
481
+
482
+ // take a shortcut for integers
483
+ if (is_int)
484
+ {
485
+ ss.precision(0);
486
+ ss << fixed << value;
487
+ res = string(ss.str());
488
+ }
489
+ // process floats
490
+ else
491
+ {
492
+ // do we have have too much precision?
493
+ if (pos_fract < precision + pos_point)
494
+ { precision = pos_fract - pos_point; }
495
+ // round value again
496
+ ss.precision(precision);
497
+ ss << fixed << value;
498
+ res = string(ss.str());
499
+ // maybe we truncated up to decimal point
500
+ size_t pos = res.find_last_not_of("0");
501
+ bool at_dec_point = res[pos] == '.' ||
502
+ res[pos] == ',';
503
+ // don't leave a blank point
504
+ if (at_dec_point) ++ pos;
505
+ res.resize (pos + 1);
506
+ }
507
+
508
+ // some final cosmetics
509
+ if (res == "-0.0") res.erase(0, 1);
510
+ else if (res == "-0") res.erase(0, 1);
511
+
512
+ // add unit now
513
+ res += n->unit();
514
+
515
+ // check for a valid unit here
516
+ // includes result for reporting
435
517
  if (n->numerator_units().size() > 1 ||
436
518
  n->denominator_units().size() > 0 ||
437
519
  (n->numerator_units().size() && n->numerator_units()[0].find_first_of('/') != string::npos) ||
438
520
  (n->numerator_units().size() && n->numerator_units()[0].find_first_of('*') != string::npos)
439
521
  ) {
440
- error(d + n->unit() + " isn't a valid CSS value.", n->pstate());
522
+ error(res + " isn't a valid CSS value.", n->pstate());
441
523
  }
442
- if (!n->zero() && !in_declaration_list) {
443
- if (d.substr(0, 3) == "-0.") d.erase(1, 1);
444
- if (d.substr(0, 2) == "0.") d.erase(0, 1);
445
- }
446
- // remove the leading minus
447
- if (d == "-0") d.erase(0, 1);
448
- // use fractional output if we had
449
- // a value before it got truncated
450
- if (d == "0" && nonzero) d = "0.0";
451
- // if the precision is 0 sass cast
452
- // casts to a float with precision 1
453
- if (ctx->precision == 0) d += ".0";
454
- // append number and unit
455
- append_token(d + n->unit(), n);
524
+
525
+ // output the final token
526
+ append_token(res, n);
527
+
456
528
  }
457
529
 
458
530
  // helper function for serializing colors
@@ -569,7 +641,7 @@ namespace Sass {
569
641
  void Inspect::operator()(String_Quoted* s)
570
642
  {
571
643
  if (s->quote_mark()) {
572
- append_token(quote(s->value(), s->quote_mark()), s);
644
+ append_token(quote(s->value(), s->quote_mark(), true), s);
573
645
  } else {
574
646
  append_token(s->value(), s);
575
647
  }
@@ -31,25 +31,25 @@ namespace Sass {
31
31
  // this even seems to improve performance by quite a bit
32
32
  //####################################
33
33
 
34
- const bool is_alpha(const char& chr)
34
+ bool is_alpha(const char& chr)
35
35
  {
36
36
  return unsigned(chr - 'A') <= 'Z' - 'A' ||
37
37
  unsigned(chr - 'a') <= 'z' - 'a';
38
38
  }
39
39
 
40
- const bool is_space(const char& chr)
40
+ bool is_space(const char& chr)
41
41
  {
42
42
  // adapted the technique from is_alpha
43
43
  return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t';
44
44
  }
45
45
 
46
- const bool is_digit(const char& chr)
46
+ bool is_digit(const char& chr)
47
47
  {
48
48
  // adapted the technique from is_alpha
49
49
  return unsigned(chr - '0') <= '9' - '0';
50
50
  }
51
51
 
52
- const bool is_xdigit(const char& chr)
52
+ bool is_xdigit(const char& chr)
53
53
  {
54
54
  // adapted the technique from is_alpha
55
55
  return unsigned(chr - '0') <= '9' - '0' ||
@@ -57,26 +57,26 @@ namespace Sass {
57
57
  unsigned(chr - 'A') <= 'F' - 'A';
58
58
  }
59
59
 
60
- const bool is_punct(const char& chr)
60
+ bool is_punct(const char& chr)
61
61
  {
62
62
  // locale independent
63
63
  return chr == '.';
64
64
  }
65
65
 
66
- const bool is_alnum(const char& chr)
66
+ bool is_alnum(const char& chr)
67
67
  {
68
68
  return is_alpha(chr) || is_digit(chr);
69
69
  }
70
70
 
71
71
  // check if char is outside ascii range
72
- const bool is_unicode(const char& chr)
72
+ bool is_unicode(const char& chr)
73
73
  {
74
74
  // check for unicode range
75
75
  return unsigned(chr) > 127;
76
76
  }
77
77
 
78
78
  // Match word character (look ahead)
79
- const bool is_character(const char& chr)
79
+ bool is_character(const char& chr)
80
80
  {
81
81
  // valid alpha, numeric or unicode char (plus hyphen)
82
82
  return is_alnum(chr) || is_unicode(chr) || chr == '-';
@@ -25,14 +25,14 @@ namespace Sass {
25
25
  //####################################
26
26
 
27
27
  // These are locale independant
28
- const bool is_space(const char& src);
29
- const bool is_alpha(const char& src);
30
- const bool is_punct(const char& src);
31
- const bool is_digit(const char& src);
32
- const bool is_alnum(const char& src);
33
- const bool is_xdigit(const char& src);
34
- const bool is_unicode(const char& src);
35
- const bool is_character(const char& src);
28
+ bool is_space(const char& src);
29
+ bool is_alpha(const char& src);
30
+ bool is_punct(const char& src);
31
+ bool is_digit(const char& src);
32
+ bool is_alnum(const char& src);
33
+ bool is_xdigit(const char& src);
34
+ bool is_unicode(const char& src);
35
+ bool is_character(const char& src);
36
36
 
37
37
  // Match a single ctype predicate.
38
38
  const char* space(const char* src);
@@ -349,11 +349,13 @@ namespace Sass {
349
349
 
350
350
  append_scope_opener();
351
351
 
352
+ bool format = kwd != "@font-face";;
353
+
352
354
  for (size_t i = 0, L = b->length(); i < L; ++i) {
353
355
  Statement* stm = (*b)[i];
354
356
  if (!stm->is_hoistable()) {
355
357
  stm->perform(this);
356
- if (i < L - 1) append_special_linefeed();
358
+ if (i < L - 1 && format) append_special_linefeed();
357
359
  }
358
360
  }
359
361
 
@@ -361,7 +363,7 @@ namespace Sass {
361
363
  Statement* stm = (*b)[i];
362
364
  if (stm->is_hoistable()) {
363
365
  stm->perform(this);
364
- if (i < L - 1) append_special_linefeed();
366
+ if (i < L - 1 && format) append_special_linefeed();
365
367
  }
366
368
  }
367
369
 
@@ -383,10 +385,16 @@ namespace Sass {
383
385
  {
384
386
  if (String_Quoted* quoted = dynamic_cast<String_Quoted*>(s)) {
385
387
  return Output::operator()(quoted);
386
- } else if (!in_comment) {
387
- append_token(string_to_output(s->value()), s);
388
388
  } else {
389
- append_token(s->value(), s);
389
+ string value(s->value());
390
+ if (s->can_compress_whitespace() && output_style() == COMPRESSED) {
391
+ value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());
392
+ }
393
+ if (!in_comment) {
394
+ append_token(string_to_output(value), s);
395
+ } else {
396
+ append_token(value, s);
397
+ }
390
398
  }
391
399
  }
392
400
 
@@ -40,6 +40,14 @@ namespace Sass {
40
40
  return p;
41
41
  }
42
42
 
43
+ Selector_List* Parser::parse_selector(const char* src, Context& ctx, ParserState pstate)
44
+ {
45
+ Parser p = Parser::from_c_str(src, ctx, pstate);
46
+ // ToDo: ruby sass errors on parent references
47
+ // ToDo: remap the source-map entries somehow
48
+ return p.parse_selector_group();
49
+ }
50
+
43
51
  bool Parser::peek_newline(const char* start)
44
52
  {
45
53
  return peek_linefeed(start ? start : position);
@@ -269,10 +277,18 @@ namespace Sass {
269
277
  else if (lex< uri_prefix >()) {
270
278
  Arguments* args = new (ctx.mem) Arguments(pstate);
271
279
  Function_Call* result = new (ctx.mem) Function_Call(pstate, "url", args);
272
- if (lex < uri_value >()) { // chunk seems to work too!
280
+ if (lex< quoted_string >()) {
281
+ Expression* the_url = parse_string();
282
+ *args << new (ctx.mem) Argument(the_url->pstate(), the_url);
283
+ }
284
+ else if (lex < uri_value >(position)) { // chunk seems to work too!
273
285
  String* the_url = parse_interpolated_chunk(lexed);
274
286
  *args << new (ctx.mem) Argument(the_url->pstate(), the_url);
275
287
  }
288
+ else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) {
289
+ Expression* the_url = parse_list(); // parse_interpolated_chunk(lexed);
290
+ *args << new (ctx.mem) Argument(the_url->pstate(), the_url);
291
+ }
276
292
  else {
277
293
  error("malformed URL", pstate);
278
294
  }
@@ -415,7 +431,13 @@ namespace Sass {
415
431
  string name(Util::normalize_underscores(lexed));
416
432
  ParserState var_source_position = pstate;
417
433
  if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate);
418
- Expression* val = parse_list();
434
+ Expression* val;
435
+ Selector_Lookahead lookahead = lookahead_for_value(position);
436
+ if (lookahead.has_interpolants && lookahead.found) {
437
+ val = parse_value_schema(lookahead.found);
438
+ } else {
439
+ val = parse_list();
440
+ }
419
441
  val->is_delayed(false);
420
442
  bool is_default = false;
421
443
  bool is_global = false;
@@ -482,6 +504,9 @@ namespace Sass {
482
504
  // accumulate the preceding segment if the position has advanced
483
505
  if (i < p) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, p));
484
506
  // skip to the delimiter by skipping occurences in quoted strings
507
+ if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
508
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
509
+ }
485
510
  const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, end_of_selector);
486
511
  Expression* interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list();
487
512
  interpolant->is_interpolant(true);
@@ -707,18 +732,9 @@ namespace Sass {
707
732
  if (lex< alternatives< even, odd > >()) {
708
733
  expr = new (ctx.mem) String_Quoted(p, lexed);
709
734
  }
710
- else if (peek< binomial >(position)) {
711
- lex< sequence< optional< coefficient >, exactly<'n'> > >();
712
- String_Constant* var_coef = new (ctx.mem) String_Quoted(p, lexed);
713
- lex< sign >();
714
- String_Constant* op = new (ctx.mem) String_Quoted(p, lexed);
715
- // Binary_Expression::Type op = (lexed == "+" ? Binary_Expression::ADD : Binary_Expression::SUB);
716
- lex< one_plus < digit > >();
717
- String_Constant* constant = new (ctx.mem) String_Quoted(p, lexed);
718
- // expr = new (ctx.mem) Binary_Expression(p, op, var_coef, constant);
719
- String_Schema* schema = new (ctx.mem) String_Schema(p, 3);
720
- *schema << var_coef << op << constant;
721
- expr = schema;
735
+ else if (lex< binomial >(position)) {
736
+ expr = new (ctx.mem) String_Constant(p, lexed);
737
+ ((String_Constant*)expr)->can_compress_whitespace(true);
722
738
  }
723
739
  else if (peek< sequence< optional<sign>,
724
740
  zero_plus<digit>,
@@ -980,13 +996,25 @@ namespace Sass {
980
996
  return new (ctx.mem) Declaration(prop->pstate(), prop, parse_static_value()/*, lex<important>()*/);
981
997
  }
982
998
  else {
983
- Expression* list_ex = parse_list();
984
- if (List* list = dynamic_cast<List*>(list_ex)) {
985
- if (list->length() == 0 && !peek< exactly <'{'> >()) {
986
- css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
999
+ Expression* value;
1000
+ Selector_Lookahead lookahead = lookahead_for_value(position);
1001
+ if (lookahead.found) {
1002
+ if (lookahead.has_interpolants) {
1003
+ value = parse_value_schema(lookahead.found);
1004
+ } else {
1005
+ value = parse_list();
1006
+ }
1007
+ }
1008
+ else {
1009
+ value = parse_list();
1010
+ if (List* list = dynamic_cast<List*>(value)) {
1011
+ if (list->length() == 0 && !peek< exactly <'{'> >()) {
1012
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
1013
+ }
987
1014
  }
988
1015
  }
989
- return new (ctx.mem) Declaration(prop->pstate(), prop, list_ex/*, lex<important>()*/);
1016
+
1017
+ return new (ctx.mem) Declaration(prop->pstate(), prop, value/*, lex<important>()*/);
990
1018
  }
991
1019
  }
992
1020
 
@@ -1008,8 +1036,18 @@ namespace Sass {
1008
1036
 
1009
1037
  Expression* Parser::parse_map()
1010
1038
  {
1011
- To_String to_string(&ctx);
1039
+ ParserState opstate = pstate;
1012
1040
  Expression* key = parse_list();
1041
+ if (String_Quoted* str = dynamic_cast<String_Quoted*>(key)) {
1042
+ if (!str->quote_mark() && !str->is_delayed()) {
1043
+ if (ctx.names_to_colors.count(str->value())) {
1044
+ Color* c = new (ctx.mem) Color(*ctx.names_to_colors[str->value()]);
1045
+ c->pstate(str->pstate());
1046
+ c->disp(str->value());
1047
+ key = c;
1048
+ }
1049
+ }
1050
+ }
1013
1051
 
1014
1052
  // it's not a map so return the lexed value as a list value
1015
1053
  if (!peek< exactly<':'> >())
@@ -1019,7 +1057,7 @@ namespace Sass {
1019
1057
 
1020
1058
  Expression* value = parse_space_list();
1021
1059
 
1022
- Map* map = new (ctx.mem) Map(pstate, 1);
1060
+ Map* map = new (ctx.mem) Map(opstate, 1);
1023
1061
  (*map) << make_pair(key, value);
1024
1062
 
1025
1063
  while (lex_css< exactly<','> >())
@@ -1029,6 +1067,16 @@ namespace Sass {
1029
1067
  { break; }
1030
1068
 
1031
1069
  Expression* key = parse_list();
1070
+ if (String_Quoted* str = dynamic_cast<String_Quoted*>(key)) {
1071
+ if (!str->quote_mark() && !str->is_delayed()) {
1072
+ if (ctx.names_to_colors.count(str->value())) {
1073
+ Color* c = new (ctx.mem) Color(*ctx.names_to_colors[str->value()]);
1074
+ c->pstate(str->pstate());
1075
+ c->disp(str->value());
1076
+ key = c;
1077
+ }
1078
+ }
1079
+ }
1032
1080
 
1033
1081
  if (!(lex< exactly<':'> >()))
1034
1082
  { error("invalid syntax", pstate); }
@@ -1038,8 +1086,15 @@ namespace Sass {
1038
1086
  (*map) << make_pair(key, value);
1039
1087
  }
1040
1088
 
1041
- if (map->has_duplicate_key())
1042
- { error("Duplicate key \"" + map->get_duplicate_key()->perform(&to_string) + "\" in map " + map->perform(&to_string) + ".", pstate); }
1089
+ // Check was moved to eval step
1090
+ // if (map->has_duplicate_key()) {
1091
+ // To_String to_string(&ctx);
1092
+ // error("Duplicate key \"" + map->get_duplicate_key()->perform(&to_string) + "\" in map " + map->perform(&to_string) + ".", pstate);
1093
+ // }
1094
+
1095
+ ParserState ps = map->pstate();
1096
+ ps.offset = pstate - ps + pstate.offset;
1097
+ map->pstate(ps);
1043
1098
 
1044
1099
  return map;
1045
1100
  }
@@ -1211,6 +1266,11 @@ namespace Sass {
1211
1266
  }
1212
1267
  // if it's a singleton, return it directly; don't wrap it
1213
1268
  if (!peek< class_char< static_ops > >(position)) return factor;
1269
+ return parse_operators(factor);
1270
+ }
1271
+
1272
+ Expression* Parser::parse_operators(Expression* factor)
1273
+ {
1214
1274
  // parse more factors and operators
1215
1275
  vector<Expression*> operands; // factors
1216
1276
  vector<Binary_Expression::Type> operators; // ops
@@ -1367,6 +1427,9 @@ namespace Sass {
1367
1427
  }
1368
1428
  // we need to skip anything inside strings
1369
1429
  // create a new target in parser/prelexer
1430
+ if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
1431
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
1432
+ }
1370
1433
  const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, chunk.end); // find the closing brace
1371
1434
  if (j) { --j;
1372
1435
  // parse the interpolant and accumulate it
@@ -1415,28 +1478,26 @@ namespace Sass {
1415
1478
  Token str(lexed);
1416
1479
  const char* i = str.begin;
1417
1480
  // see if there any interpolants
1418
- const char* q;
1419
1481
  const char* p = find_first_in_interval< exactly<hash_lbrace> >(str.begin, str.end);
1420
1482
  if (!p) {
1421
1483
  String_Constant* str_node = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(str.begin, str.end)));
1422
1484
  str_node->is_delayed(true);
1485
+ str_node->quote_mark('*');
1423
1486
  return str_node;
1424
1487
  }
1425
1488
 
1426
1489
  String_Schema* schema = new (ctx.mem) String_Schema(pstate);
1427
1490
  while (i < str.end) {
1428
- q = find_first_in_interval< alternatives< exactly<'"'>, exactly<'\''> > >(i, str.end);
1429
1491
  p = find_first_in_interval< exactly<hash_lbrace> >(i, str.end);
1430
- if (q && (!p || p > q)) {
1431
- if (i < q) {
1432
- (*schema) << new (ctx.mem) String_Constant(pstate, string(i, q)); // accumulate the preceding segment if it's nonempty
1433
- }
1434
- (*schema) << new (ctx.mem) String_Constant(pstate, string(q, q+1)); // capture the quote mark separately
1435
- i = q+1;
1436
- }
1437
- else if (p) {
1492
+ if (p) {
1438
1493
  if (i < p) {
1439
- (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p)); // accumulate the preceding segment if it's nonempty
1494
+ String_Constant* part = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(i, p))); // accumulate the preceding segment if it's nonempty
1495
+ part->is_delayed(true);
1496
+ part->quote_mark('*'); // avoid unquote in interpolation
1497
+ (*schema) << part;
1498
+ }
1499
+ if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
1500
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
1440
1501
  }
1441
1502
  const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, str.end); // find the closing brace
1442
1503
  if (j) {
@@ -1452,7 +1513,12 @@ namespace Sass {
1452
1513
  }
1453
1514
  }
1454
1515
  else { // no interpolants left; add the last segment if nonempty
1455
- if (i < str.end) (*schema) << new (ctx.mem) String_Constant(pstate, string(i, str.end));
1516
+ if (i < str.end) {
1517
+ String_Constant* part = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(i, str.end)));
1518
+ part->is_delayed(true);
1519
+ part->quote_mark('*'); // avoid unquote in interpolation
1520
+ (*schema) << part;
1521
+ }
1456
1522
  break;
1457
1523
  }
1458
1524
  }
@@ -1466,15 +1532,13 @@ namespace Sass {
1466
1532
  *kwd_arg << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed));
1467
1533
  } else {
1468
1534
  lex< alternatives< identifier_schema, identifier > >();
1469
- *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);
1535
+ *kwd_arg << new (ctx.mem) String_Constant(pstate, lexed);
1470
1536
  }
1471
1537
  lex< exactly<'='> >();
1472
- *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);
1538
+ *kwd_arg << new (ctx.mem) String_Constant(pstate, lexed);
1473
1539
  if (peek< variable >()) *kwd_arg << parse_list();
1474
1540
  else if (lex< number >()) *kwd_arg << new (ctx.mem) Textual(pstate, Textual::NUMBER, Util::normalize_decimals(lexed));
1475
- else if (lex< alternatives< identifier_schema, identifier, number, hexa, hex > >()) {
1476
- *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);
1477
- }
1541
+ else if (peek < ie_keyword_arg_value >()) { *kwd_arg << parse_list(); }
1478
1542
  return kwd_arg;
1479
1543
  }
1480
1544
 
@@ -1482,8 +1546,14 @@ namespace Sass {
1482
1546
  {
1483
1547
  String_Schema* schema = new (ctx.mem) String_Schema(pstate);
1484
1548
  size_t num_items = 0;
1549
+ if (peek<exactly<'}'>>()) {
1550
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
1551
+ }
1485
1552
  while (position < stop) {
1486
- if (lex< interpolant >()) {
1553
+ if (lex< spaces >() && num_items) {
1554
+ (*schema) << new (ctx.mem) String_Constant(pstate, " ");
1555
+ }
1556
+ else if (lex< interpolant >()) {
1487
1557
  Token insides(Token(lexed.begin + 2, lexed.end - 1));
1488
1558
  Expression* interp_node = Parser::from_token(insides, ctx, pstate).parse_list();
1489
1559
  interp_node->is_interpolant(true);
@@ -1502,17 +1572,28 @@ namespace Sass {
1502
1572
  (*schema) << new (ctx.mem) Textual(pstate, Textual::DIMENSION, lexed);
1503
1573
  }
1504
1574
  else if (lex< number >()) {
1505
- (*schema) << new (ctx.mem) Textual(pstate, Textual::NUMBER, lexed);
1575
+ Expression* factor = new (ctx.mem) Textual(pstate, Textual::NUMBER, lexed);
1576
+ if (peek< class_char< static_ops > >()) {
1577
+ (*schema) << parse_operators(factor);
1578
+ } else {
1579
+ (*schema) << factor;
1580
+ }
1506
1581
  }
1507
1582
  else if (lex< hex >()) {
1508
1583
  (*schema) << new (ctx.mem) Textual(pstate, Textual::HEX, unquote(lexed));
1509
1584
  }
1585
+ else if (lex < exactly < '-' > >()) {
1586
+ (*schema) << new (ctx.mem) String_Constant(pstate, lexed);
1587
+ }
1510
1588
  else if (lex< quoted_string >()) {
1511
1589
  (*schema) << new (ctx.mem) String_Quoted(pstate, lexed);
1512
1590
  }
1513
1591
  else if (lex< variable >()) {
1514
1592
  (*schema) << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed));
1515
1593
  }
1594
+ else if (peek< parenthese_scope >()) {
1595
+ (*schema) << parse_factor();
1596
+ }
1516
1597
  else {
1517
1598
  error("error parsing interpolated value", pstate);
1518
1599
  }
@@ -1575,6 +1656,9 @@ namespace Sass {
1575
1656
  }
1576
1657
  // we need to skip anything inside strings
1577
1658
  // create a new target in parser/prelexer
1659
+ if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
1660
+ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
1661
+ }
1578
1662
  const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, id.end); // find the closing brace
1579
1663
  if (j) {
1580
1664
  // parse the interpolant and accumulate it
@@ -1988,6 +2072,7 @@ namespace Sass {
1988
2072
  (q = peek< class_name >(p)) ||
1989
2073
  (q = peek< sequence< pseudo_prefix, identifier > >(p)) ||
1990
2074
  (q = peek< percentage >(p)) ||
2075
+ (q = peek< variable >(p)) ||
1991
2076
  (q = peek< dimension >(p)) ||
1992
2077
  (q = peek< quoted_string >(p)) ||
1993
2078
  (q = peek< exactly<'*'> >(p)) ||
@@ -2050,6 +2135,7 @@ namespace Sass {
2050
2135
  (q = peek< sequence< pseudo_prefix, identifier > >(p)) ||
2051
2136
  (q = peek< percentage >(p)) ||
2052
2137
  (q = peek< dimension >(p)) ||
2138
+ (q = peek< variable >(p)) ||
2053
2139
  (q = peek< quoted_string >(p)) ||
2054
2140
  (q = peek< exactly<'*'> >(p)) ||
2055
2141
  (q = peek< exactly<'('> >(p)) ||
@@ -2097,6 +2183,56 @@ namespace Sass {
2097
2183
  return result;
2098
2184
  }
2099
2185
 
2186
+
2187
+ Selector_Lookahead Parser::lookahead_for_value(const char* start)
2188
+ {
2189
+ const char* p = start ? start : position;
2190
+ const char* q;
2191
+ bool saw_interpolant = false;
2192
+ bool saw_stuff = false;
2193
+
2194
+ while ((q = peek< identifier >(p)) ||
2195
+ (q = peek< percentage >(p)) ||
2196
+ (q = peek< dimension >(p)) ||
2197
+ (q = peek< quoted_string >(p)) ||
2198
+ (q = peek< variable >(p)) ||
2199
+ (q = peek< exactly<'*'> >(p)) ||
2200
+ (q = peek< exactly<'+'> >(p)) ||
2201
+ (q = peek< exactly<'~'> >(p)) ||
2202
+ (q = peek< exactly<'>'> >(p)) ||
2203
+ (q = peek< exactly<','> >(p)) ||
2204
+ (q = peek< sequence<parenthese_scope, interpolant>>(p)) ||
2205
+ (saw_stuff && (q = peek< exactly<'-'> >(p))) ||
2206
+ (q = peek< binomial >(p)) ||
2207
+ (q = peek< block_comment >(p)) ||
2208
+ (q = peek< sequence< optional<sign>,
2209
+ zero_plus<digit>,
2210
+ exactly<'n'> > >(p)) ||
2211
+ (q = peek< sequence< optional<sign>,
2212
+ one_plus<digit> > >(p)) ||
2213
+ (q = peek< number >(p)) ||
2214
+ (q = peek< sequence< exactly<'&'>,
2215
+ identifier_alnums > >(p)) ||
2216
+ (q = peek< exactly<'&'> >(p)) ||
2217
+ (q = peek< exactly<'%'> >(p)) ||
2218
+ (q = peek< sequence< exactly<'.'>, interpolant > >(p)) ||
2219
+ (q = peek< sequence< exactly<'#'>, interpolant > >(p)) ||
2220
+ (q = peek< sequence< one_plus< exactly<'-'> >, interpolant > >(p)) ||
2221
+ (q = peek< sequence< pseudo_prefix, interpolant > >(p)) ||
2222
+ (q = peek< interpolant >(p)) ||
2223
+ (q = peek< optional >(p))) {
2224
+ p = q;
2225
+ if (*(p - 1) == '}') saw_interpolant = true;
2226
+ saw_stuff = true;
2227
+ }
2228
+
2229
+ Selector_Lookahead result;
2230
+ result.found = peek< alternatives< exactly<';'>, exactly<'}'>, exactly<'{'> > >(p) && saw_stuff ? p : 0;
2231
+ result.has_interpolants = saw_interpolant;
2232
+
2233
+ return result;
2234
+ }
2235
+
2100
2236
  void Parser::read_bom()
2101
2237
  {
2102
2238
  size_t skip = 0;
@@ -2209,14 +2345,16 @@ namespace Sass {
2209
2345
  const char* pos = peek < optional_spaces >();
2210
2346
  bool ellipsis_left = false;
2211
2347
  const char* pos_left(pos);
2212
- while (*pos_left && pos_left >= source) {
2348
+ while (*pos_left && pos_left > source) {
2213
2349
  if (pos - pos_left > max_len) {
2214
2350
  ellipsis_left = true;
2215
2351
  break;
2216
2352
  }
2217
- if (*pos_left == '\r') break;
2218
- if (*pos_left == '\n') break;
2219
- -- pos_left;
2353
+ const char* prev = pos_left - 1;
2354
+ if (*prev == '\r') break;
2355
+ if (*prev == '\n') break;
2356
+ if (*prev == 10) break;
2357
+ pos_left = prev;
2220
2358
  }
2221
2359
  bool ellipsis_right = false;
2222
2360
  const char* pos_right(pos);
@@ -2227,6 +2365,7 @@ namespace Sass {
2227
2365
  }
2228
2366
  if (*pos_right == '\r') break;
2229
2367
  if (*pos_right == '\n') break;
2368
+ if (*pos_left == 10) break;
2230
2369
  ++ pos_right;
2231
2370
  }
2232
2371
  string left(pos_left, pos);