sassc 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);