1// sass.hpp must go before all system headers to get the
2// __EXTENSIONS__ fix on Solaris.
3#include "sass.hpp"
4
5#include "parser.hpp"
6#include "color_maps.hpp"
7#include "util_string.hpp"
8
9// Notes about delayed: some ast nodes can have delayed evaluation so
10// they can preserve their original semantics if needed. This is most
11// prominently exhibited by the division operation, since it is not
12// only a valid operation, but also a valid css statement (i.e. for
13// fonts, as in `16px/24px`). When parsing lists and expression we
14// unwrap single items from lists and other operations. A nested list
15// must not be delayed, only the items of the first level sometimes
16// are delayed (as with argument lists). To achieve this we need to
17// pass status to the list parser, so this can be set correctly.
18// Another case with delayed values are colors. In compressed mode
19// only processed values get compressed (other are left as written).
20
21
22namespace Sass {
23 using namespace Constants;
24 using namespace Prelexer;
25
26
27 Parser::Parser(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) :
28 SourceSpan(source),
29 ctx(ctx),
30 source(source),
31 begin(source->begin()),
32 position(source->begin()),
33 end(source->end()),
34 before_token(0, 0),
35 after_token(0, 0),
36 pstate(source->getSourceSpan()),
37 traces(traces),
38 indentation(0),
39 nestings(0),
40 allow_parent(allow_parent)
41 {
42 Block_Obj root = SASS_MEMORY_NEW(Block, pstate);
43 stack.push_back(x: Scope::Root);
44 block_stack.push_back(x: root);
45 root->is_root(is_root__: true);
46 }
47
48 void Parser::advanceToNextToken() {
49 lex < css_comments >(lazy: false);
50 // advance to position
51 pstate.position += pstate.offset;
52 pstate.offset.column = 0;
53 pstate.offset.line = 0;
54 }
55
56 SelectorListObj Parser::parse_selector(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent)
57 {
58 Parser p(source, ctx, traces, allow_parent);
59 // ToDo: remap the source-map entries somehow
60 return p.parseSelectorList(chroot: false);
61 }
62
63 bool Parser::peek_newline(const char* start)
64 {
65 return peek_linefeed(start: start ? start : position)
66 && ! peek_css<exactly<'{'>>(start);
67 }
68
69 /* main entry point to parse root block */
70 Block_Obj Parser::parse()
71 {
72
73 // consume unicode BOM
74 read_bom();
75
76 // scan the input to find invalid utf8 sequences
77 const char* it = utf8::find_invalid(start: position, end);
78
79 // report invalid utf8
80 if (it != end) {
81 pstate.position += Offset::init(beg: position, end: it);
82 traces.push_back(x: Backtrace(pstate));
83 throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence");
84 }
85
86 // create a block AST node to hold children
87 Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true);
88
89 // check seems a bit esoteric but works
90 if (ctx.resources.size() == 1) {
91 // apply headers only on very first include
92 ctx.apply_custom_headers(root, path: getPath(), pstate);
93 }
94
95 // parse children nodes
96 block_stack.push_back(x: root);
97 parse_block_nodes(is_root: true);
98 block_stack.pop_back();
99
100 // update final position
101 root->update_pstate(pstate);
102
103 if (position != end) {
104 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected selector or at-rule, was ");
105 }
106
107 return root;
108 }
109
110
111 // convenience function for block parsing
112 // will create a new block ad-hoc for you
113 // this is the base block parsing function
114 Block_Obj Parser::parse_css_block(bool is_root)
115 {
116
117 // parse comments before block
118 // lex < optional_css_comments >();
119
120 // lex mandatory opener or error out
121 if (!lex_css < exactly<'{'> >()) {
122 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"{\", was ");
123 }
124 // create new block and push to the selector stack
125 Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root);
126 block_stack.push_back(x: block);
127
128 if (!parse_block_nodes(is_root)) css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"}\", was ");
129
130 if (!lex_css < exactly<'}'> >()) {
131 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"}\", was ");
132 }
133
134 // update for end position
135 // this seems to be done somewhere else
136 // but that fixed selector schema issue
137 // block->update_pstate(pstate);
138
139 // parse comments after block
140 // lex < optional_css_comments >();
141
142 block_stack.pop_back();
143
144 return block;
145 }
146
147 // convenience function for block parsing
148 // will create a new block ad-hoc for you
149 // also updates the `in_at_root` flag
150 Block_Obj Parser::parse_block(bool is_root)
151 {
152 return parse_css_block(is_root);
153 }
154
155 // the main block parsing function
156 // parses stuff between `{` and `}`
157 bool Parser::parse_block_nodes(bool is_root)
158 {
159
160 // loop until end of string
161 while (position < end) {
162
163 // we should be able to refactor this
164 parse_block_comments();
165 lex < css_whitespace >();
166
167 if (lex < exactly<';'> >()) continue;
168 if (peek < end_of_file >()) return true;
169 if (peek < exactly<'}'> >()) return true;
170
171 if (parse_block_node(is_root)) continue;
172
173 parse_block_comments();
174
175 if (lex_css < exactly<';'> >()) continue;
176 if (peek_css < end_of_file >()) return true;
177 if (peek_css < exactly<'}'> >()) return true;
178
179 // illegal sass
180 return false;
181 }
182 // return success
183 return true;
184 }
185
186 // parser for a single node in a block
187 // semicolons must be lexed beforehand
188 bool Parser::parse_block_node(bool is_root) {
189
190 Block_Obj block = block_stack.back();
191
192 parse_block_comments();
193
194 // throw away white-space
195 // includes line comments
196 lex < css_whitespace >();
197
198 Lookahead lookahead_result;
199
200 // also parse block comments
201
202 // first parse everything that is allowed in functions
203 if (lex < variable >(lazy: true)) { block->append(element: parse_assignment()); }
204 else if (lex < kwd_err >(lazy: true)) { block->append(element: parse_error()); }
205 else if (lex < kwd_dbg >(lazy: true)) { block->append(element: parse_debug()); }
206 else if (lex < kwd_warn >(lazy: true)) { block->append(element: parse_warning()); }
207 else if (lex < kwd_if_directive >(lazy: true)) { block->append(element: parse_if_directive()); }
208 else if (lex < kwd_for_directive >(lazy: true)) { block->append(element: parse_for_directive()); }
209 else if (lex < kwd_each_directive >(lazy: true)) { block->append(element: parse_each_directive()); }
210 else if (lex < kwd_while_directive >(lazy: true)) { block->append(element: parse_while_directive()); }
211 else if (lex < kwd_return_directive >(lazy: true)) { block->append(element: parse_return_directive()); }
212
213 // parse imports to process later
214 else if (lex < kwd_import >(lazy: true)) {
215 Scope parent = stack.empty() ? Scope::Rules : stack.back();
216 if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) {
217 if (! peek_css< uri_prefix >(start: position)) { // this seems to go in ruby sass 3.4.20
218 error(msg: "Import directives may not be used within control directives or mixins.");
219 }
220 }
221 // this puts the parsed doc into sheets
222 // import stub will fetch this in expand
223 Import_Obj imp = parse_import();
224 // if it is a url, we only add the statement
225 if (!imp->urls().empty()) block->append(element: imp);
226 // process all resources now (add Import_Stub nodes)
227 for (size_t i = 0, S = imp->incs().size(); i < S; ++i) {
228 block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i]));
229 }
230 }
231
232 else if (lex < kwd_extend >(lazy: true)) {
233 Lookahead lookahead = lookahead_for_include(start: position);
234 if (!lookahead.found) css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected selector, was ");
235 SelectorListObj target;
236 if (!lookahead.has_interpolants) {
237 LOCAL_FLAG(allow_parent, false);
238 auto selector = parseSelectorList(chroot: true);
239 auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector);
240 extender->isOptional(isOptional__: selector && selector->is_optional());
241 block->append(element: extender);
242 }
243 else {
244 LOCAL_FLAG(allow_parent, false);
245 auto selector = parse_selector_schema(end_of_selector: lookahead.found, chroot: true);
246 auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector);
247 // A schema is not optional yet, check once it is evaluated
248 // extender->isOptional(selector && selector->is_optional());
249 block->append(element: extender);
250 }
251
252 }
253
254 // selector may contain interpolations which need delayed evaluation
255 else if (
256 !(lookahead_result = lookahead_for_selector(start: position)).error &&
257 !lookahead_result.is_custom_property
258 )
259 {
260 block->append(element: parse_ruleset(lookahead: lookahead_result));
261 }
262
263 // parse multiple specific keyword directives
264 else if (lex < kwd_media >(lazy: true)) { block->append(element: parseMediaRule()); }
265 else if (lex < kwd_at_root >(lazy: true)) { block->append(element: parse_at_root_block()); }
266 else if (lex < kwd_include_directive >(lazy: true)) { block->append(element: parse_include_directive()); }
267 else if (lex < kwd_content_directive >(lazy: true)) { block->append(element: parse_content_directive()); }
268 else if (lex < kwd_supports_directive >(lazy: true)) { block->append(element: parse_supports_directive()); }
269 else if (lex < kwd_mixin >(lazy: true)) { block->append(element: parse_definition(which_type: Definition::MIXIN)); }
270 else if (lex < kwd_function >(lazy: true)) { block->append(element: parse_definition(which_type: Definition::FUNCTION)); }
271
272 // ignore the @charset directive for now
273 else if (lex< kwd_charset_directive >(lazy: true)) { parse_charset_directive(); }
274
275 else if (lex < exactly < else_kwd >>(lazy: true)) { error(msg: "Invalid CSS: @else must come after @if"); }
276
277 // generic at keyword (keep last)
278 else if (lex< at_keyword >(lazy: true)) { block->append(element: parse_directive()); }
279
280 else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) {
281 lex< css_whitespace >();
282 if (position >= end) return true;
283 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected 1 selector or at-rule, was ");
284 }
285 // parse a declaration
286 else
287 {
288 // ToDo: how does it handle parse errors?
289 // maybe we are expected to parse something?
290 Declaration_Obj decl = parse_declaration();
291 decl->tabs(tabs__: indentation);
292 block->append(element: decl);
293 // maybe we have a "sub-block"
294 if (peek< exactly<'{'> >()) {
295 if (decl->is_indented()) ++ indentation;
296 // parse a propset that rides on the declaration's property
297 stack.push_back(x: Scope::Properties);
298 decl->block(block__: parse_block());
299 stack.pop_back();
300 if (decl->is_indented()) -- indentation;
301 }
302 }
303 // something matched
304 return true;
305 }
306 // EO parse_block_nodes
307
308 // parse imports inside the
309 Import_Obj Parser::parse_import()
310 {
311 Import_Obj imp = SASS_MEMORY_NEW(Import, pstate);
312 sass::vector<std::pair<sass::string,Function_Call_Obj>> to_import;
313 bool first = true;
314 do {
315 while (lex< block_comment >());
316 if (lex< quoted_string >()) {
317 to_import.push_back(x: std::pair<sass::string,Function_Call_Obj>(sass::string(lexed), {}));
318 }
319 else if (lex< uri_prefix >()) {
320 Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);
321 Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), args);
322
323 if (lex< quoted_string >()) {
324 ExpressionObj quoted_url = parse_string();
325 args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url));
326 }
327 else if (String_Obj string_url = parse_url_function_argument()) {
328 args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url));
329 }
330 else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(start: position)) {
331 ExpressionObj braced_url = parse_list(); // parse_interpolated_chunk(lexed);
332 args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url));
333 }
334 else {
335 error(msg: "malformed URL");
336 }
337 if (!lex< exactly<')'> >()) error(msg: "URI is missing ')'");
338 to_import.push_back(x: std::pair<sass::string, Function_Call_Obj>("", result));
339 }
340 else {
341 if (first) error(msg: "@import directive requires a url or quoted path");
342 else error(msg: "expecting another url or quoted path in @import list");
343 }
344 first = false;
345 } while (lex_css< exactly<','> >());
346
347 if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) {
348 List_Obj import_queries = parse_media_queries();
349 imp->import_queries(import_queries__: import_queries);
350 }
351
352 for(auto location : to_import) {
353 if (location.second) {
354 imp->urls().push_back(x: location.second);
355 }
356 // check if custom importers want to take over the handling
357 else if (!ctx.call_importers(load_path: unquote(location.first), ctx_path: getPath(), pstate, imp)) {
358 // nobody wants it, so we do our import
359 ctx.import_url(imp, load_path: location.first, ctx_path: getPath());
360 }
361 }
362
363 return imp;
364 }
365
366 Definition_Obj Parser::parse_definition(Definition::Type which_type)
367 {
368 sass::string which_str(lexed);
369 if (!lex< identifier >()) error(msg: "invalid name in " + which_str + " definition");
370 sass::string name(Util::normalize_underscores(str: lexed));
371 if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not"))
372 { error(msg: "Invalid function name \"" + name + "\"."); }
373 SourceSpan source_position_of_def = pstate;
374 Parameters_Obj params = parse_parameters();
375 if (which_type == Definition::MIXIN) stack.push_back(x: Scope::Mixin);
376 else stack.push_back(x: Scope::Function);
377 Block_Obj body = parse_block();
378 stack.pop_back();
379 return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type);
380 }
381
382 Parameters_Obj Parser::parse_parameters()
383 {
384 Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate);
385 if (lex_css< exactly<'('> >()) {
386 // if there's anything there at all
387 if (!peek_css< exactly<')'> >()) {
388 do {
389 if (peek< exactly<')'> >()) break;
390 params->append(element: parse_parameter());
391 } while (lex_css< exactly<','> >());
392 }
393 if (!lex_css< exactly<')'> >()) {
394 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \")\", was ");
395 }
396 }
397 return params;
398 }
399
400 Parameter_Obj Parser::parse_parameter()
401 {
402 if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) {
403 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected variable (e.g. $foo), was ");
404 }
405 while (lex< alternatives < spaces, block_comment > >());
406 lex < variable >();
407 sass::string name(Util::normalize_underscores(str: lexed));
408 SourceSpan pos = pstate;
409 ExpressionObj val;
410 bool is_rest = false;
411 while (lex< alternatives < spaces, block_comment > >());
412 if (lex< exactly<':'> >()) { // there's a default value
413 while (lex< block_comment >());
414 val = parse_space_list();
415 }
416 else if (lex< exactly< ellipsis > >()) {
417 is_rest = true;
418 }
419 return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest);
420 }
421
422 Arguments_Obj Parser::parse_arguments()
423 {
424 Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);
425 if (lex_css< exactly<'('> >()) {
426 // if there's anything there at all
427 if (!peek_css< exactly<')'> >()) {
428 do {
429 if (peek< exactly<')'> >()) break;
430 args->append(element: parse_argument());
431 } while (lex_css< exactly<','> >());
432 }
433 if (!lex_css< exactly<')'> >()) {
434 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
435 }
436 }
437 return args;
438 }
439
440 Argument_Obj Parser::parse_argument()
441 {
442 if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) {
443 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \")\", was ");
444 }
445 if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) {
446 position += 2;
447 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
448 }
449
450 Argument_Obj arg;
451 if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) {
452 lex_css< variable >();
453 sass::string name(Util::normalize_underscores(str: lexed));
454 SourceSpan p = pstate;
455 lex_css< exactly<':'> >();
456 ExpressionObj val = parse_space_list();
457 arg = SASS_MEMORY_NEW(Argument, p, val, name);
458 }
459 else {
460 bool is_arglist = false;
461 bool is_keyword = false;
462 ExpressionObj val = parse_space_list();
463 List* l = Cast<List>(ptr: val);
464 if (lex_css< exactly< ellipsis > >()) {
465 if (val->concrete_type() == Expression::MAP || (
466 (l != NULL && l->separator() == SASS_HASH)
467 )) is_keyword = true;
468 else is_arglist = true;
469 }
470 arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword);
471 }
472 return arg;
473 }
474
475 Assignment_Obj Parser::parse_assignment()
476 {
477 sass::string name(Util::normalize_underscores(str: lexed));
478 SourceSpan var_source_position = pstate;
479 if (!lex< exactly<':'> >()) error(msg: "expected ':' after " + name + " in assignment statement");
480 if (peek_css< alternatives < exactly<';'>, end_of_file > >()) {
481 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
482 }
483 ExpressionObj val;
484 Lookahead lookahead = lookahead_for_value(start: position);
485 if (lookahead.has_interpolants && lookahead.found) {
486 val = parse_value_schema(stop: lookahead.found);
487 } else {
488 val = parse_list();
489 }
490 bool is_default = false;
491 bool is_global = false;
492 while (peek< alternatives < default_flag, global_flag > >()) {
493 if (lex< default_flag >()) is_default = true;
494 else if (lex< global_flag >()) is_global = true;
495 }
496 return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global);
497 }
498
499 // a ruleset connects a selector and a block
500 StyleRuleObj Parser::parse_ruleset(Lookahead lookahead)
501 {
502 NESTING_GUARD(nestings);
503 // inherit is_root from parent block
504 Block_Obj parent = block_stack.back();
505 bool is_root = parent && parent->is_root();
506 // make sure to move up the the last position
507 lex < optional_css_whitespace >(lazy: false, force: true);
508 // create the connector object (add parts later)
509 StyleRuleObj ruleset = SASS_MEMORY_NEW(StyleRule, pstate);
510 // parse selector static or as schema to be evaluated later
511 if (lookahead.parsable) {
512 ruleset->selector(selector__: parseSelectorList(chroot: false));
513 }
514 else {
515 SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate);
516 auto sc = parse_selector_schema(end_of_selector: lookahead.position, chroot: false);
517 ruleset->schema(schema__: sc);
518 ruleset->selector(selector__: list);
519 }
520 // then parse the inner block
521 stack.push_back(x: Scope::Rules);
522 ruleset->block(block__: parse_block());
523 stack.pop_back();
524 // update for end position
525 ruleset->update_pstate(pstate);
526 ruleset->block()->update_pstate(pstate);
527 // need this info for sanity checks
528 ruleset->is_root(is_root__: is_root);
529 // return AST Node
530 return ruleset;
531 }
532
533 // parse a selector schema that will be evaluated in the eval stage
534 // uses a string schema internally to do the actual schema handling
535 // in the eval stage we will be re-parse it into an actual selector
536 Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot)
537 {
538 NESTING_GUARD(nestings);
539 // move up to the start
540 lex< optional_spaces >();
541 const char* i = position;
542 // selector schema re-uses string schema implementation
543 String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate);
544 // the selector schema is pretty much just a wrapper for the string schema
545 Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema);
546 selector_schema->connect_parent(connect_parent__: chroot == false);
547
548 // process until end
549 while (i < end_of_selector) {
550 // try to parse multiple interpolants
551 if (const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: i, end: end_of_selector)) {
552 // accumulate the preceding segment if the position has advanced
553 if (i < p) {
554 sass::string parsed(i, p);
555 String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
556 pstate.position += Offset(parsed);
557 str->update_pstate(pstate);
558 schema->append(element: str);
559 }
560
561 // skip over all nested inner interpolations up to our own delimiter
562 const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(src: p + 2, end: end_of_selector);
563 // check if the interpolation never ends of only contains white-space (error out)
564 if (!j || peek < sequence < optional_spaces, exactly<rbrace> > >(start: p+2)) {
565 position = p+2;
566 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
567 }
568 // pass inner expression to the parser to resolve nested interpolations
569 LocalOption<const char*> partEnd(end, j);
570 LocalOption<const char*> partBeg(position, p + 2);
571 ExpressionObj interpolant = parse_list();
572 // set status on the list expression
573 interpolant->is_interpolant(is_interpolant__: true);
574 // schema->has_interpolants(true);
575 // add to the string schema
576 schema->append(element: interpolant);
577 // advance parser state
578 pstate.position.add(begin: p+2, end: j);
579 // advance position
580 i = j;
581 }
582 // no more interpolants have been found
583 // add the last segment if there is one
584 else {
585 // make sure to add the last bits of the string up to the end (if any)
586 if (i < end_of_selector) {
587 sass::string parsed(i, end_of_selector);
588 String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
589 pstate.position += Offset(parsed);
590 str->update_pstate(pstate);
591 i = end_of_selector;
592 schema->append(element: str);
593 }
594 // exit loop
595 }
596 }
597 // EO until eos
598
599 // update position
600 position = i;
601
602 // update for end position
603 selector_schema->update_pstate(pstate);
604 schema->update_pstate(pstate);
605
606 after_token = before_token = pstate.position;
607
608 // return parsed result
609 return selector_schema.detach();
610 }
611 // EO parse_selector_schema
612
613 void Parser::parse_charset_directive()
614 {
615 lex <
616 sequence <
617 quoted_string,
618 optional_spaces,
619 exactly <';'>
620 >
621 >();
622 }
623
624 // called after parsing `kwd_include_directive`
625 Mixin_Call_Obj Parser::parse_include_directive()
626 {
627 // lex identifier into `lexed` var
628 lex_identifier(); // may error out
629 // normalize underscores to hyphens
630 sass::string name(Util::normalize_underscores(str: lexed));
631 // create the initial mixin call object
632 Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, Arguments_Obj{});
633 // parse mandatory arguments
634 call->arguments(arguments__: parse_arguments());
635 // parse using and optional block parameters
636 bool has_parameters = lex< kwd_using >() != nullptr;
637
638 if (has_parameters) {
639 if (!peek< exactly<'('> >()) css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"(\", was ");
640 } else {
641 if (peek< exactly<'('> >()) css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \";\", was ");
642 }
643
644 if (has_parameters) call->block_parameters(block_parameters__: parse_parameters());
645
646 // parse optional block
647 if (peek < exactly <'{'> >()) {
648 call->block(block__: parse_block());
649 }
650 else if (has_parameters) {
651 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"{\", was ");
652 }
653 // return ast node
654 return call.detach();
655 }
656 // EO parse_include_directive
657
658
659 SimpleSelectorObj Parser::parse_simple_selector()
660 {
661 lex < css_comments >(lazy: false);
662 if (lex< class_name >()) {
663 return SASS_MEMORY_NEW(ClassSelector, pstate, lexed);
664 }
665 else if (lex< id_name >()) {
666 return SASS_MEMORY_NEW(IDSelector, pstate, lexed);
667 }
668 else if (lex< alternatives < variable, number, static_reference_combinator > >()) {
669 return SASS_MEMORY_NEW(TypeSelector, pstate, lexed);
670 }
671 else if (peek< pseudo_not >()) {
672 return parse_negated_selector2();
673 }
674 else if (peek< re_pseudo_selector >()) {
675 return parse_pseudo_selector();
676 }
677 else if (peek< exactly<':'> >()) {
678 return parse_pseudo_selector();
679 }
680 else if (lex < exactly<'['> >()) {
681 return parse_attribute_selector();
682 }
683 else if (lex< placeholder >()) {
684 return SASS_MEMORY_NEW(PlaceholderSelector, pstate, lexed);
685 }
686 else {
687 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected selector, was ");
688 }
689 // failed
690 return {};
691 }
692
693 PseudoSelectorObj Parser::parse_negated_selector2()
694 {
695 lex< pseudo_not >();
696 sass::string name(lexed);
697 SourceSpan nsource_position = pstate;
698 SelectorListObj negated = parseSelectorList(chroot: true);
699 if (!lex< exactly<')'> >()) {
700 error(msg: "negated selector is missing ')'");
701 }
702 name.erase(pos: name.size() - 1);
703
704 PseudoSelector* sel = SASS_MEMORY_NEW(PseudoSelector, nsource_position, name.substr(1));
705 sel->selector(selector__: negated);
706 return sel;
707 }
708
709 // Helper to clean binominal string
710 bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); }
711
712 // a pseudo selector often starts with one or two colons
713 // it can contain more selectors inside parentheses
714 SimpleSelectorObj Parser::parse_pseudo_selector() {
715
716 // Lex one or two colon characters
717 if (lex<pseudo_prefix>()) {
718 sass::string colons(lexed);
719 // Check if it is a pseudo element
720 bool element = colons.size() == 2;
721
722 if (lex< sequence<
723 // we keep the space within the name, strange enough
724 // ToDo: refactor output to schedule the space for it
725 // or do we really want to keep the real white-space?
726 sequence< identifier, optional < block_comment >, exactly<'('> >
727 > >())
728 {
729
730 sass::string name(lexed);
731 name.erase(pos: name.size() - 1);
732 SourceSpan p = pstate;
733
734 // specially parse nth-child pseudo selectors
735 if (lex_css < sequence < binomial, word_boundary >>()) {
736 sass::string parsed(lexed); // always compacting binominals (as dart-sass)
737 parsed.erase(first: std::unique(first: parsed.begin(), last: parsed.end(), binary_pred: BothAreSpaces), last: parsed.end());
738 String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
739 PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
740 if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(lazy: false)) {
741 pseudo->selector(selector__: parseSelectorList(chroot: true));
742 }
743 pseudo->argument(argument__: arg);
744 if (lex_css< exactly<')'> >()) {
745 return pseudo;
746 }
747 }
748 else {
749 if (peek_css< exactly<')'>>() && Util::equalsLiteral(lit: "nth-", test: name.substr(pos: 0, n: 4))) {
750 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected An+B expression, was ");
751 }
752
753 sass::string unvendored = Util::unvendor(name);
754
755 if (unvendored == "not" || unvendored == "matches" || unvendored == "current" || unvendored == "any" || unvendored == "has" || unvendored == "host" || unvendored == "host-context" || unvendored == "slotted") {
756 if (SelectorListObj wrapped = parseSelectorList(chroot: true)) {
757 if (wrapped && lex_css< exactly<')'> >()) {
758 PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
759 pseudo->selector(selector__: wrapped);
760 return pseudo;
761 }
762 }
763 } else {
764 String_Schema_Obj arg = parse_css_variable_value();
765 PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
766 pseudo->argument(argument__: arg);
767
768 if (lex_css< exactly<')'> >()) {
769 return pseudo;
770 }
771 }
772 }
773
774 }
775 // EO if pseudo selector
776
777 else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) {
778 return SASS_MEMORY_NEW(PseudoSelector, pstate, lexed, element);
779 }
780 else if (lex < pseudo_prefix >()) {
781 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected pseudoclass or pseudoelement, was ");
782 }
783
784 }
785 else {
786 lex < identifier >(); // needed for error message?
787 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected selector, was ");
788 }
789
790
791 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \")\", was ");
792
793 // unreachable statement
794 return {};
795 }
796
797 const char* Parser::re_attr_sensitive_close(const char* src)
798 {
799 return alternatives < exactly<']'>, exactly<'/'> >(src);
800 }
801
802 const char* Parser::re_attr_insensitive_close(const char* src)
803 {
804 return sequence < insensitive<'i'>, re_attr_sensitive_close >(src);
805 }
806
807 AttributeSelectorObj Parser::parse_attribute_selector()
808 {
809 SourceSpan p = pstate;
810 if (!lex_css< attribute_name >()) error(msg: "invalid attribute name in attribute selector");
811 sass::string name(lexed);
812 if (lex_css< re_attr_sensitive_close >()) {
813 return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{});
814 }
815 else if (lex_css< re_attr_insensitive_close >()) {
816 char modifier = lexed.begin[0];
817 return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}, modifier);
818 }
819 if (!lex_css< alternatives< exact_match, class_match, dash_match,
820 prefix_match, suffix_match, substring_match > >()) {
821 error(msg: "invalid operator in attribute selector for " + name);
822 }
823 sass::string matcher(lexed);
824
825 String_Obj value;
826 if (lex_css< identifier >()) {
827 value = SASS_MEMORY_NEW(String_Constant, p, lexed);
828 }
829 else if (lex_css< quoted_string >()) {
830 value = parse_interpolated_chunk(lexed, constant: true); // needed!
831 }
832 else {
833 error(msg: "expected a string constant or identifier in attribute selector for " + name);
834 }
835
836 if (lex_css< re_attr_sensitive_close >()) {
837 return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, 0);
838 }
839 else if (lex_css< re_attr_insensitive_close >()) {
840 char modifier = lexed.begin[0];
841 return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, modifier);
842 }
843 error(msg: "unterminated attribute selector for " + name);
844 return {}; // to satisfy compilers (error must not return)
845 }
846
847 /* parse block comment and add to block */
848 void Parser::parse_block_comments(bool store)
849 {
850 Block_Obj block = block_stack.back();
851
852 while (lex< block_comment >()) {
853 bool is_important = lexed.begin[2] == '!';
854 // flag on second param is to skip loosely over comments
855 String_Obj contents = parse_interpolated_chunk(lexed, constant: true, css: false);
856 if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important));
857 }
858 }
859
860 Declaration_Obj Parser::parse_declaration() {
861 String_Obj prop;
862 bool is_custom_property = false;
863 if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) {
864 const sass::string property(lexed);
865 is_custom_property = property.compare(pos: 0, n1: 2, s: "--") == 0;
866 prop = parse_identifier_schema();
867 }
868 else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) {
869 const sass::string property(lexed);
870 is_custom_property = property.compare(pos: 0, n1: 2, s: "--") == 0;
871 prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed);
872 }
873 else {
874 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"}\", was ");
875 }
876 bool is_indented = true;
877 const sass::string property(lexed);
878 if (!lex_css< one_plus< exactly<':'> > >()) error(msg: "property \"" + escape_string(str: property) + "\" must be followed by a ':'");
879 if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error(msg: "style declaration must contain a value");
880 if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty
881 if (is_custom_property) {
882 return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true);
883 }
884 lex < css_comments >(lazy: false);
885 if (peek_css< static_value >()) {
886 return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex<kwd_important>()*/);
887 }
888 else {
889 ExpressionObj value;
890 Lookahead lookahead = lookahead_for_value(start: position);
891 if (lookahead.found) {
892 if (lookahead.has_interpolants) {
893 value = parse_value_schema(stop: lookahead.found);
894 } else {
895 value = parse_list(delayed: DELAYED);
896 }
897 }
898 else {
899 value = parse_list(delayed: DELAYED);
900 if (List* list = Cast<List>(ptr: value)) {
901 if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) {
902 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
903 }
904 }
905 }
906 lex < css_comments >(lazy: false);
907 Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex<kwd_important>()*/);
908 decl->is_indented(is_indented__: is_indented);
909 decl->update_pstate(pstate);
910 return decl;
911 }
912 }
913
914 ExpressionObj Parser::parse_map()
915 {
916 NESTING_GUARD(nestings);
917 ExpressionObj key = parse_list();
918 List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH);
919
920 // it's not a map so return the lexed value as a list value
921 if (!lex_css< exactly<':'> >())
922 { return key; }
923
924 List_Obj l = Cast<List>(ptr: key);
925 if (l && l->separator() == SASS_COMMA) {
926 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \")\", was ");
927 }
928
929 ExpressionObj value = parse_space_list();
930
931 map->append(element: key);
932 map->append(element: value);
933
934 while (lex_css< exactly<','> >())
935 {
936 // allow trailing commas - #495
937 if (peek_css< exactly<')'> >(start: position))
938 { break; }
939
940 key = parse_space_list();
941
942 if (!(lex< exactly<':'> >()))
943 { css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \":\", was "); }
944
945 value = parse_space_list();
946
947 map->append(element: key);
948 map->append(element: value);
949 }
950
951 SourceSpan ps = map->pstate();
952 ps.offset = pstate.position - ps.position + pstate.offset;
953 map->pstate(pstate__: ps);
954
955 return map;
956 }
957
958 ExpressionObj Parser::parse_bracket_list()
959 {
960 NESTING_GUARD(nestings);
961 // check if we have an empty list
962 // return the empty list as such
963 if (peek_css< list_terminator >(start: position))
964 {
965 // return an empty list (nothing to delay)
966 return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true);
967 }
968
969 bool has_paren = peek_css< exactly<'('> >() != NULL;
970
971 // now try to parse a space list
972 ExpressionObj list = parse_space_list();
973 // if it's a singleton, return it (don't wrap it)
974 if (!peek_css< exactly<','> >(start: position)) {
975 List_Obj l = Cast<List>(ptr: list);
976 if (!l || l->is_bracketed() || has_paren) {
977 List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true);
978 bracketed_list->append(element: list);
979 return bracketed_list;
980 }
981 l->is_bracketed(is_bracketed__: true);
982 return l;
983 }
984
985 // if we got so far, we actually do have a comma list
986 List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true);
987 // wrap the first expression
988 bracketed_list->append(element: list);
989
990 while (lex_css< exactly<','> >())
991 {
992 // check for abort condition
993 if (peek_css< list_terminator >(start: position)
994 ) { break; }
995 // otherwise add another expression
996 bracketed_list->append(element: parse_space_list());
997 }
998 // return the list
999 return bracketed_list;
1000 }
1001
1002 // parse list returns either a space separated list,
1003 // a comma separated list or any bare expression found.
1004 // so to speak: we unwrap items from lists if possible here!
1005 ExpressionObj Parser::parse_list(bool delayed)
1006 {
1007 NESTING_GUARD(nestings);
1008 return parse_comma_list(delayed);
1009 }
1010
1011 // will return singletons unwrapped
1012 ExpressionObj Parser::parse_comma_list(bool delayed)
1013 {
1014 NESTING_GUARD(nestings);
1015 // check if we have an empty list
1016 // return the empty list as such
1017 if (peek_css< list_terminator >(start: position))
1018 {
1019 // return an empty list (nothing to delay)
1020 return SASS_MEMORY_NEW(List, pstate, 0);
1021 }
1022
1023 // now try to parse a space list
1024 ExpressionObj list = parse_space_list();
1025 // if it's a singleton, return it (don't wrap it)
1026 if (!peek_css< exactly<','> >(start: position)) {
1027 // set_delay doesn't apply to list children
1028 // so this will only undelay single values
1029 if (!delayed) list->set_delayed(false);
1030 return list;
1031 }
1032
1033 // if we got so far, we actually do have a comma list
1034 List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA);
1035 // wrap the first expression
1036 comma_list->append(element: list);
1037
1038 while (lex_css< exactly<','> >())
1039 {
1040 // check for abort condition
1041 if (peek_css< list_terminator >(start: position)
1042 ) { break; }
1043 // otherwise add another expression
1044 comma_list->append(element: parse_space_list());
1045 }
1046 // return the list
1047 return comma_list;
1048 }
1049 // EO parse_comma_list
1050
1051 // will return singletons unwrapped
1052 ExpressionObj Parser::parse_space_list()
1053 {
1054 NESTING_GUARD(nestings);
1055 ExpressionObj disj1 = parse_disjunction();
1056 // if it's a singleton, return it (don't wrap it)
1057 if (peek_css< space_list_terminator >(start: position)
1058 ) {
1059 return disj1; }
1060
1061 List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE);
1062 space_list->append(element: disj1);
1063
1064 while (
1065 !(peek_css< space_list_terminator >(start: position)) &&
1066 peek_css< optional_css_whitespace >() != end
1067 ) {
1068 // the space is parsed implicitly?
1069 space_list->append(element: parse_disjunction());
1070 }
1071 // return the list
1072 return space_list;
1073 }
1074 // EO parse_space_list
1075
1076 // parse logical OR operation
1077 ExpressionObj Parser::parse_disjunction()
1078 {
1079 NESTING_GUARD(nestings);
1080 advanceToNextToken();
1081 SourceSpan state(pstate);
1082 // parse the left hand side conjunction
1083 ExpressionObj conj = parse_conjunction();
1084 // parse multiple right hand sides
1085 sass::vector<ExpressionObj> operands;
1086 while (lex_css< kwd_or >())
1087 operands.push_back(x: parse_conjunction());
1088 // if it's a singleton, return it directly
1089 if (operands.size() == 0) return conj;
1090 // fold all operands into one binary expression
1091 ExpressionObj ex = fold_operands(base: conj, operands, op: { Sass_OP::OR });
1092 state.offset = pstate.position - state.position + pstate.offset;
1093 ex->pstate(pstate__: state);
1094 return ex;
1095 }
1096 // EO parse_disjunction
1097
1098 // parse logical AND operation
1099 ExpressionObj Parser::parse_conjunction()
1100 {
1101 NESTING_GUARD(nestings);
1102 advanceToNextToken();
1103 SourceSpan state(pstate);
1104 // parse the left hand side relation
1105 ExpressionObj rel = parse_relation();
1106 // parse multiple right hand sides
1107 sass::vector<ExpressionObj> operands;
1108 while (lex_css< kwd_and >()) {
1109 operands.push_back(x: parse_relation());
1110 }
1111 // if it's a singleton, return it directly
1112 if (operands.size() == 0) return rel;
1113 // fold all operands into one binary expression
1114 ExpressionObj ex = fold_operands(base: rel, operands, op: { Sass_OP::AND });
1115 state.offset = pstate.position - state.position + pstate.offset;
1116 ex->pstate(pstate__: state);
1117 return ex;
1118 }
1119 // EO parse_conjunction
1120
1121 // parse comparison operations
1122 ExpressionObj Parser::parse_relation()
1123 {
1124 NESTING_GUARD(nestings);
1125 advanceToNextToken();
1126 SourceSpan state(pstate);
1127 // parse the left hand side expression
1128 ExpressionObj lhs = parse_expression();
1129 sass::vector<ExpressionObj> operands;
1130 sass::vector<Operand> operators;
1131 // if it's a singleton, return it (don't wrap it)
1132 while (peek< alternatives <
1133 kwd_eq,
1134 kwd_neq,
1135 kwd_gte,
1136 kwd_gt,
1137 kwd_lte,
1138 kwd_lt
1139 > >(start: position))
1140 {
1141 // is directly adjancent to expression?
1142 bool left_ws = peek < css_comments >() != NULL;
1143 // parse the operator
1144 enum Sass_OP op
1145 = lex<kwd_eq>() ? Sass_OP::EQ
1146 : lex<kwd_neq>() ? Sass_OP::NEQ
1147 : lex<kwd_gte>() ? Sass_OP::GTE
1148 : lex<kwd_lte>() ? Sass_OP::LTE
1149 : lex<kwd_gt>() ? Sass_OP::GT
1150 : lex<kwd_lt>() ? Sass_OP::LT
1151 // we checked the possibilities on top of fn
1152 : Sass_OP::EQ;
1153 // is directly adjacent to expression?
1154 bool right_ws = peek < css_comments >() != NULL;
1155 operators.push_back(x: { op, left_ws, right_ws });
1156 operands.push_back(x: parse_expression());
1157 }
1158 // we are called recursively for list, so we first
1159 // fold inner binary expression which has delayed
1160 // correctly set to zero. After folding we also unwrap
1161 // single nested items. So we cannot set delay on the
1162 // returned result here, as we have lost nestings ...
1163 ExpressionObj ex = fold_operands(base: lhs, operands, ops&: operators);
1164 state.offset = pstate.position - state.position + pstate.offset;
1165 ex->pstate(pstate__: state);
1166 return ex;
1167 }
1168 // parse_relation
1169
1170 // parse expression valid for operations
1171 // called from parse_relation
1172 // called from parse_for_directive
1173 // called from parse_media_expression
1174 // parse addition and subtraction operations
1175 ExpressionObj Parser::parse_expression()
1176 {
1177 NESTING_GUARD(nestings);
1178 advanceToNextToken();
1179 SourceSpan state(pstate);
1180 // parses multiple add and subtract operations
1181 // NOTE: make sure that identifiers starting with
1182 // NOTE: dashes do NOT count as subtract operation
1183 ExpressionObj lhs = parse_operators();
1184 // if it's a singleton, return it (don't wrap it)
1185 if (!(peek_css< exactly<'+'> >(start: position) ||
1186 // condition is a bit mysterious, but some combinations should not be counted as operations
1187 (peek< no_spaces >(start: position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(start: position)) ||
1188 (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(start: position))) ||
1189 peek< sequence < zero_plus < exactly <'-' > >, identifier > >(start: position))
1190 { return lhs; }
1191
1192 sass::vector<ExpressionObj> operands;
1193 sass::vector<Operand> operators;
1194 bool left_ws = peek < css_comments >() != NULL;
1195 while (
1196 lex_css< exactly<'+'> >() ||
1197
1198 (
1199 ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(start: position)
1200 && lex_css< sequence< negate< digit >, exactly<'-'> > >()
1201 )
1202
1203 ) {
1204
1205 bool right_ws = peek < css_comments >() != NULL;
1206 operators.push_back(x: { lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws });
1207 operands.push_back(x: parse_operators());
1208 left_ws = peek < css_comments >() != NULL;
1209 }
1210
1211 if (operands.size() == 0) return lhs;
1212 ExpressionObj ex = fold_operands(base: lhs, operands, ops&: operators);
1213 state.offset = pstate.position - state.position + pstate.offset;
1214 ex->pstate(pstate__: state);
1215 return ex;
1216 }
1217
1218 // parse addition and subtraction operations
1219 ExpressionObj Parser::parse_operators()
1220 {
1221 NESTING_GUARD(nestings);
1222 advanceToNextToken();
1223 SourceSpan state(pstate);
1224 ExpressionObj factor = parse_factor();
1225 // if it's a singleton, return it (don't wrap it)
1226 sass::vector<ExpressionObj> operands; // factors
1227 sass::vector<Operand> operators; // ops
1228 // lex operations to apply to lhs
1229 const char* left_ws = peek < css_comments >();
1230 while (lex_css< class_char< static_ops > >()) {
1231 const char* right_ws = peek < css_comments >();
1232 switch(*lexed.begin) {
1233 case '*': operators.push_back(x: { Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break;
1234 case '/': operators.push_back(x: { Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break;
1235 case '%': operators.push_back(x: { Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break;
1236 default: throw std::runtime_error("unknown static op parsed");
1237 }
1238 operands.push_back(x: parse_factor());
1239 left_ws = peek < css_comments >();
1240 }
1241 // operands and operators to binary expression
1242 ExpressionObj ex = fold_operands(base: factor, operands, ops&: operators);
1243 state.offset = pstate.position - state.position + pstate.offset;
1244 ex->pstate(pstate__: state);
1245 return ex;
1246 }
1247 // EO parse_operators
1248
1249
1250 // called from parse_operators
1251 // called from parse_value_schema
1252 ExpressionObj Parser::parse_factor()
1253 {
1254 NESTING_GUARD(nestings);
1255 lex < css_comments >(lazy: false);
1256 if (lex_css< exactly<'('> >()) {
1257 // parse_map may return a list
1258 ExpressionObj value = parse_map();
1259 // lex the expected closing parenthesis
1260 if (!lex_css< exactly<')'> >()) error(msg: "unclosed parenthesis");
1261 // expression can be evaluated
1262 return value;
1263 }
1264 else if (lex_css< exactly<'['> >()) {
1265 // explicit bracketed
1266 ExpressionObj value = parse_bracket_list();
1267 // lex the expected closing square bracket
1268 if (!lex_css< exactly<']'> >()) error(msg: "unclosed squared bracket");
1269 return value;
1270 }
1271 // string may be interpolated
1272 // if (lex< quoted_string >()) {
1273 // return &parse_string();
1274 // }
1275 else if (peek< ie_property >()) {
1276 return parse_ie_property();
1277 }
1278 else if (peek< ie_keyword_arg >()) {
1279 return parse_ie_keyword_arg();
1280 }
1281 else if (peek< sequence < calc_fn_call, exactly <'('> > >()) {
1282 return parse_calc_function();
1283 }
1284 else if (lex < functional_schema >()) {
1285 return parse_function_call_schema();
1286 }
1287 else if (lex< identifier_schema >()) {
1288 String_Obj string = parse_identifier_schema();
1289 if (String_Schema* schema = Cast<String_Schema>(ptr: string)) {
1290 if (lex < exactly < '(' > >()) {
1291 schema->append(element: parse_list());
1292 lex < exactly < ')' > >();
1293 }
1294 }
1295 return string;
1296 }
1297 else if (peek< sequence< uri_prefix, W, real_uri_value > >()) {
1298 return parse_url_function_string();
1299 }
1300 else if (peek< re_functional >()) {
1301 return parse_function_call();
1302 }
1303 else if (lex< exactly<'+'> >()) {
1304 Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor());
1305 if (ex && ex->operand()) ex->is_delayed(is_delayed__: ex->operand()->is_delayed());
1306 return ex;
1307 }
1308 else if (lex< exactly<'-'> >()) {
1309 Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor());
1310 if (ex && ex->operand()) ex->is_delayed(is_delayed__: ex->operand()->is_delayed());
1311 return ex;
1312 }
1313 else if (lex< exactly<'/'> >()) {
1314 Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor());
1315 if (ex && ex->operand()) ex->is_delayed(is_delayed__: ex->operand()->is_delayed());
1316 return ex;
1317 }
1318 else if (lex< sequence< kwd_not > >()) {
1319 Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor());
1320 if (ex && ex->operand()) ex->is_delayed(is_delayed__: ex->operand()->is_delayed());
1321 return ex;
1322 }
1323 else {
1324 return parse_value();
1325 }
1326 }
1327
1328 bool number_has_zero(const sass::string& parsed)
1329 {
1330 size_t L = parsed.length();
1331 return !( (L > 0 && parsed.substr(pos: 0, n: 1) == ".") ||
1332 (L > 1 && parsed.substr(pos: 0, n: 2) == "0.") ||
1333 (L > 1 && parsed.substr(pos: 0, n: 2) == "-.") ||
1334 (L > 2 && parsed.substr(pos: 0, n: 3) == "-0.") );
1335 }
1336
1337 Number* Parser::lexed_number(const SourceSpan& pstate, const sass::string& parsed)
1338 {
1339 Number* nr = SASS_MEMORY_NEW(Number,
1340 pstate,
1341 sass_strtod(parsed.c_str()),
1342 "",
1343 number_has_zero(parsed));
1344 nr->is_interpolant(is_interpolant__: false);
1345 nr->is_delayed(is_delayed__: true);
1346 return nr;
1347 }
1348
1349 Number* Parser::lexed_percentage(const SourceSpan& pstate, const sass::string& parsed)
1350 {
1351 Number* nr = SASS_MEMORY_NEW(Number,
1352 pstate,
1353 sass_strtod(parsed.c_str()),
1354 "%",
1355 true);
1356 nr->is_interpolant(is_interpolant__: false);
1357 nr->is_delayed(is_delayed__: true);
1358 return nr;
1359 }
1360
1361 Number* Parser::lexed_dimension(const SourceSpan& pstate, const sass::string& parsed)
1362 {
1363 size_t L = parsed.length();
1364 size_t num_pos = parsed.find_first_not_of(s: " \n\r\t");
1365 if (num_pos == sass::string::npos) num_pos = L;
1366 size_t unit_pos = parsed.find_first_not_of(s: "-+0123456789.", pos: num_pos);
1367 if (parsed[unit_pos] == 'e' && is_number(src: parsed[unit_pos+1]) ) {
1368 unit_pos = parsed.find_first_not_of(s: "-+0123456789.", pos: ++ unit_pos);
1369 }
1370 if (unit_pos == sass::string::npos) unit_pos = L;
1371 const sass::string& num = parsed.substr(pos: num_pos, n: unit_pos - num_pos);
1372 Number* nr = SASS_MEMORY_NEW(Number,
1373 pstate,
1374 sass_strtod(num.c_str()),
1375 Token(number(parsed.c_str())),
1376 number_has_zero(parsed));
1377 nr->is_interpolant(is_interpolant__: false);
1378 nr->is_delayed(is_delayed__: true);
1379 return nr;
1380 }
1381
1382 Value* Parser::lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed)
1383 {
1384 Color_RGBA* color = NULL;
1385 if (parsed[0] != '#') {
1386 return SASS_MEMORY_NEW(String_Quoted, pstate, parsed);
1387 }
1388 // chop off the '#'
1389 sass::string hext(parsed.substr(pos: 1));
1390 if (parsed.length() == 4) {
1391 sass::string r(2, parsed[1]);
1392 sass::string g(2, parsed[2]);
1393 sass::string b(2, parsed[3]);
1394 color = SASS_MEMORY_NEW(Color_RGBA,
1395 pstate,
1396 static_cast<double>(strtol(r.c_str(), NULL, 16)),
1397 static_cast<double>(strtol(g.c_str(), NULL, 16)),
1398 static_cast<double>(strtol(b.c_str(), NULL, 16)),
1399 1, // alpha channel
1400 parsed);
1401 }
1402 else if (parsed.length() == 5) {
1403 sass::string r(2, parsed[1]);
1404 sass::string g(2, parsed[2]);
1405 sass::string b(2, parsed[3]);
1406 sass::string a(2, parsed[4]);
1407 color = SASS_MEMORY_NEW(Color_RGBA,
1408 pstate,
1409 static_cast<double>(strtol(r.c_str(), NULL, 16)),
1410 static_cast<double>(strtol(g.c_str(), NULL, 16)),
1411 static_cast<double>(strtol(b.c_str(), NULL, 16)),
1412 static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255,
1413 parsed);
1414 }
1415 else if (parsed.length() == 7) {
1416 sass::string r(parsed.substr(pos: 1,n: 2));
1417 sass::string g(parsed.substr(pos: 3,n: 2));
1418 sass::string b(parsed.substr(pos: 5,n: 2));
1419 color = SASS_MEMORY_NEW(Color_RGBA,
1420 pstate,
1421 static_cast<double>(strtol(r.c_str(), NULL, 16)),
1422 static_cast<double>(strtol(g.c_str(), NULL, 16)),
1423 static_cast<double>(strtol(b.c_str(), NULL, 16)),
1424 1, // alpha channel
1425 parsed);
1426 }
1427 else if (parsed.length() == 9) {
1428 sass::string r(parsed.substr(pos: 1,n: 2));
1429 sass::string g(parsed.substr(pos: 3,n: 2));
1430 sass::string b(parsed.substr(pos: 5,n: 2));
1431 sass::string a(parsed.substr(pos: 7,n: 2));
1432 color = SASS_MEMORY_NEW(Color_RGBA,
1433 pstate,
1434 static_cast<double>(strtol(r.c_str(), NULL, 16)),
1435 static_cast<double>(strtol(g.c_str(), NULL, 16)),
1436 static_cast<double>(strtol(b.c_str(), NULL, 16)),
1437 static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255,
1438 parsed);
1439 }
1440 color->is_interpolant(is_interpolant__: false);
1441 color->is_delayed(is_delayed__: false);
1442 return color;
1443 }
1444
1445 Value* Parser::color_or_string(const sass::string& lexed) const
1446 {
1447 if (auto color = name_to_color(lexed)) {
1448 auto c = SASS_MEMORY_NEW(Color_RGBA, color);
1449 c->is_delayed(is_delayed__: true);
1450 c->pstate(pstate__: pstate);
1451 c->disp(disp__: lexed);
1452 return c;
1453 } else {
1454 return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
1455 }
1456 }
1457
1458 // parse one value for a list
1459 ExpressionObj Parser::parse_value()
1460 {
1461 lex< css_comments >(lazy: false);
1462 if (lex< ampersand >())
1463 {
1464 if (match< ampersand >()) {
1465 warning(msg: "In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate);
1466 }
1467 return SASS_MEMORY_NEW(Parent_Reference, pstate); }
1468
1469 if (lex< kwd_important >())
1470 { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); }
1471
1472 // parse `10%4px` into separated items and not a schema
1473 if (lex< sequence < percentage, lookahead < number > > >())
1474 { return lexed_percentage(parsed: lexed); }
1475
1476 if (lex< sequence < number, lookahead< sequence < op, number > > > >())
1477 { return lexed_number(parsed: lexed); }
1478
1479 // string may be interpolated
1480 if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >())
1481 { return parse_string(); }
1482
1483 if (const char* stop = peek< value_schema >())
1484 { return parse_value_schema(stop); }
1485
1486 // string may be interpolated
1487 if (lex< quoted_string >())
1488 { return parse_string(); }
1489
1490 if (lex< kwd_true >())
1491 { return SASS_MEMORY_NEW(Boolean, pstate, true); }
1492
1493 if (lex< kwd_false >())
1494 { return SASS_MEMORY_NEW(Boolean, pstate, false); }
1495
1496 if (lex< kwd_null >())
1497 { return SASS_MEMORY_NEW(Null, pstate); }
1498
1499 if (lex< identifier >()) {
1500 return color_or_string(lexed);
1501 }
1502
1503 if (lex< percentage >())
1504 { return lexed_percentage(parsed: lexed); }
1505
1506 // match hex number first because 0x000 looks like a number followed by an identifier
1507 if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >())
1508 { return lexed_hex_color(parsed: lexed); }
1509
1510 if (lex< hexa >())
1511 { return lexed_hex_color(parsed: lexed); }
1512
1513 if (lex< sequence < exactly <'#'>, identifier > >())
1514 { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); }
1515
1516 // also handle the 10em- foo special case
1517 // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression
1518 if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >())
1519 { return lexed_dimension(parsed: lexed); }
1520
1521 if (lex< sequence< static_component, one_plus< strict_identifier > > >())
1522 { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); }
1523
1524 if (lex< number >())
1525 { return lexed_number(parsed: lexed); }
1526
1527 if (lex< variable >())
1528 { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); }
1529
1530 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1531
1532 // unreachable statement
1533 return {};
1534 }
1535
1536 // this parses interpolation inside other strings
1537 // means the result should later be quoted again
1538 String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css)
1539 {
1540 const char* i = chunk.begin;
1541 // see if there any interpolants
1542 const char* p = constant ? find_first_in_interval< exactly<hash_lbrace> >(beg: i, end: chunk.end) :
1543 find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: i, end: chunk.end);
1544
1545 if (!p) {
1546 String_Quoted* str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(i, chunk.end), 0, false, false, true, css);
1547 if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark(quote_mark__: '*');
1548 return str_quoted;
1549 }
1550
1551 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css);
1552 schema->is_interpolant(is_interpolant__: true);
1553 while (i < chunk.end) {
1554 p = constant ? find_first_in_interval< exactly<hash_lbrace> >(beg: i, end: chunk.end) :
1555 find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: i, end: chunk.end);
1556 if (p) {
1557 if (i < p) {
1558 // accumulate the preceding segment if it's nonempty
1559 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p), css));
1560 }
1561 // we need to skip anything inside strings
1562 // create a new target in parser/prelexer
1563 if (peek < sequence < optional_spaces, exactly<rbrace> > >(start: p+2)) { position = p+2;
1564 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1565 }
1566 const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(src: p + 2, end: chunk.end); // find the closing brace
1567 if (j) { --j;
1568 // parse the interpolant and accumulate it
1569 LocalOption<const char*> partEnd(end, j);
1570 LocalOption<const char*> partBeg(position, p + 2);
1571 ExpressionObj interp_node = parse_list();
1572 interp_node->is_interpolant(is_interpolant__: true);
1573 schema->append(element: interp_node);
1574 i = j;
1575 }
1576 else {
1577 // throw an error if the interpolant is unterminated
1578 error(msg: "unterminated interpolant inside string constant " + chunk.to_string());
1579 }
1580 }
1581 else { // no interpolants left; add the last segment if nonempty
1582 // check if we need quotes here (was not sure after merge)
1583 if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, chunk.end), css));
1584 break;
1585 }
1586 ++ i;
1587 }
1588
1589 return schema.detach();
1590 }
1591
1592 String_Schema_Obj Parser::parse_css_variable_value()
1593 {
1594 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
1595 sass::vector<char> brackets;
1596 while (true) {
1597 if (
1598 (brackets.empty() && lex< css_variable_top_level_value >(lazy: false)) ||
1599 (!brackets.empty() && lex< css_variable_value >(lazy: false))
1600 ) {
1601 Token str(lexed);
1602 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str));
1603 } else if (ExpressionObj tok = lex_interpolation()) {
1604 if (String_Schema* s = Cast<String_Schema>(ptr: tok)) {
1605 if (s->empty()) break;
1606 schema->concat(v: s);
1607 } else {
1608 schema->append(element: tok);
1609 }
1610 } else if (lex< quoted_string >()) {
1611 ExpressionObj tok = parse_string();
1612 if (tok.isNull()) break;
1613 if (String_Schema* s = Cast<String_Schema>(ptr: tok)) {
1614 if (s->empty()) break;
1615 schema->concat(v: s);
1616 } else {
1617 schema->append(element: tok);
1618 }
1619 } else if (lex< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) {
1620 const char opening_bracket = *(position - 1);
1621 brackets.push_back(x: opening_bracket);
1622 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, opening_bracket)));
1623 } else if (const char *match = peek< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >()) {
1624 if (brackets.empty()) break;
1625 const char closing_bracket = *(match - 1);
1626 if (brackets.back() != Util::opening_bracket_for(closing_bracket)) {
1627 sass::string message = ": expected \"";
1628 message += Util::closing_bracket_for(opening_bracket: brackets.back());
1629 message += "\", was ";
1630 css_error(msg: "Invalid CSS", prefix: " after ", middle: message);
1631 }
1632 lex< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >();
1633 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, closing_bracket)));
1634 brackets.pop_back();
1635 } else {
1636 break;
1637 }
1638 }
1639
1640 if (!brackets.empty()) {
1641 sass::string message = ": expected \"";
1642 message += Util::closing_bracket_for(opening_bracket: brackets.back());
1643 message += "\", was ";
1644 css_error(msg: "Invalid CSS", prefix: " after ", middle: message);
1645 }
1646
1647 if (schema->empty()) error(msg: "Custom property values may not be empty.");
1648 return schema.detach();
1649 }
1650
1651 ValueObj Parser::parse_static_value()
1652 {
1653 lex< static_value >();
1654 Token str(lexed);
1655 // static values always have trailing white-
1656 // space and end delimiter (\s*[;]$) included
1657 --pstate.offset.column;
1658 --after_token.column;
1659 --str.end;
1660 --position;
1661
1662 return color_or_string(lexed: str.time_wspace());;
1663 }
1664
1665 String_Obj Parser::parse_string()
1666 {
1667 return parse_interpolated_chunk(chunk: Token(lexed));
1668 }
1669
1670 String_Obj Parser::parse_ie_property()
1671 {
1672 lex< ie_property >();
1673 Token str(lexed);
1674 const char* i = str.begin;
1675 // see if there any interpolants
1676 const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: str.begin, end: str.end);
1677 if (!p) {
1678 return SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(str.begin, str.end));
1679 }
1680
1681 String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate);
1682 while (i < str.end) {
1683 p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: i, end: str.end);
1684 if (p) {
1685 if (i < p) {
1686 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p))); // accumulate the preceding segment if it's nonempty
1687 }
1688 if (peek < sequence < optional_spaces, exactly<rbrace> > >(start: p+2)) { position = p+2;
1689 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1690 }
1691 const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(src: p+2, end: str.end); // find the closing brace
1692 if (j) {
1693 // parse the interpolant and accumulate it
1694 LocalOption<const char*> partEnd(end, j);
1695 LocalOption<const char*> partBeg(position, p + 2);
1696 ExpressionObj interp_node = parse_list();
1697 interp_node->is_interpolant(is_interpolant__: true);
1698 schema->append(element: interp_node);
1699 i = j;
1700 }
1701 else {
1702 // throw an error if the interpolant is unterminated
1703 error(msg: "unterminated interpolant inside IE function " + str.to_string());
1704 }
1705 }
1706 else { // no interpolants left; add the last segment if nonempty
1707 if (i < str.end) {
1708 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, str.end)));
1709 }
1710 break;
1711 }
1712 }
1713 return schema;
1714 }
1715
1716 String_Obj Parser::parse_ie_keyword_arg()
1717 {
1718 String_Schema_Obj kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3);
1719 if (lex< variable >()) {
1720 kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)));
1721 } else {
1722 lex< alternatives< identifier_schema, identifier > >();
1723 kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
1724 }
1725 lex< exactly<'='> >();
1726 kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
1727 if (peek< variable >()) kwd_arg->append(element: parse_list());
1728 else if (lex< number >()) {
1729 sass::string parsed(lexed);
1730 Util::normalize_decimals(str: parsed);
1731 kwd_arg->append(element: lexed_number(parsed));
1732 }
1733 else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(element: parse_list()); }
1734 return kwd_arg;
1735 }
1736
1737 String_Schema_Obj Parser::parse_value_schema(const char* stop)
1738 {
1739 // initialize the string schema object to add tokens
1740 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
1741
1742 if (peek<exactly<'}'>>()) {
1743 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1744 }
1745
1746 const char* e;
1747 const char* ee = end;
1748 end = stop;
1749 size_t num_items = 0;
1750 bool need_space = false;
1751 while (position < stop) {
1752 // parse space between tokens
1753 if (lex< spaces >() && num_items) {
1754 need_space = true;
1755 }
1756 if (need_space) {
1757 need_space = false;
1758 // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
1759 }
1760 if ((e = peek< re_functional >()) && e < stop) {
1761 schema->append(element: parse_function_call());
1762 }
1763 // lex an interpolant /#{...}/
1764 else if (lex< exactly < hash_lbrace > >()) {
1765 // Try to lex static expression first
1766 if (peek< exactly< rbrace > >()) {
1767 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1768 }
1769 ExpressionObj ex;
1770 if (lex< re_static_expression >()) {
1771 ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed);
1772 } else {
1773 ex = parse_list(delayed: true);
1774 }
1775 ex->is_interpolant(is_interpolant__: true);
1776 schema->append(element: ex);
1777 if (!lex < exactly < rbrace > >()) {
1778 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"}\", was ");
1779 }
1780 }
1781 // lex some string constants or other valid token
1782 // Note: [-+] chars are left over from i.e. `#{3}+3`
1783 else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) {
1784 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
1785 }
1786 // lex a quoted string
1787 else if (lex< quoted_string >()) {
1788 // need_space = true;
1789 // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
1790 // else need_space = true;
1791 schema->append(element: parse_string());
1792 if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) {
1793 // need_space = true;
1794 }
1795 if (peek < exactly < '-' > >()) break;
1796 }
1797 else if (lex< identifier >()) {
1798 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
1799 if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) {
1800 // need_space = true;
1801 }
1802 }
1803 // lex (normalized) variable
1804 else if (lex< variable >()) {
1805 sass::string name(Util::normalize_underscores(str: lexed));
1806 schema->append(SASS_MEMORY_NEW(Variable, pstate, name));
1807 }
1808 // lex percentage value
1809 else if (lex< percentage >()) {
1810 schema->append(element: lexed_percentage(parsed: lexed));
1811 }
1812 // lex dimension value
1813 else if (lex< dimension >()) {
1814 schema->append(element: lexed_dimension(parsed: lexed));
1815 }
1816 // lex number value
1817 else if (lex< number >()) {
1818 schema->append(element: lexed_number(parsed: lexed));
1819 }
1820 // lex hex color value
1821 else if (lex< sequence < hex, negate < exactly < '-' > > > >()) {
1822 schema->append(element: lexed_hex_color(parsed: lexed));
1823 }
1824 else if (lex< sequence < exactly <'#'>, identifier > >()) {
1825 schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed));
1826 }
1827 // lex a value in parentheses
1828 else if (peek< parenthese_scope >()) {
1829 schema->append(element: parse_factor());
1830 }
1831 else {
1832 break;
1833 }
1834 ++num_items;
1835 }
1836 if (position != stop) {
1837 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(position, stop)));
1838 position = stop;
1839 }
1840 end = ee;
1841 return schema;
1842 }
1843
1844 // this parses interpolation outside other strings
1845 // means the result must not be quoted again later
1846 String_Obj Parser::parse_identifier_schema()
1847 {
1848 Token id(lexed);
1849 const char* i = id.begin;
1850 // see if there any interpolants
1851 const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: id.begin, end: id.end);
1852 if (!p) {
1853 return SASS_MEMORY_NEW(String_Constant, pstate, sass::string(id.begin, id.end));
1854 }
1855
1856 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
1857 while (i < id.end) {
1858 p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(beg: i, end: id.end);
1859 if (p) {
1860 if (i < p) {
1861 // accumulate the preceding segment if it's nonempty
1862 const char* o = position; position = i;
1863 schema->append(element: parse_value_schema(stop: p));
1864 position = o;
1865 }
1866 // we need to skip anything inside strings
1867 // create a new target in parser/prelexer
1868 if (peek < sequence < optional_spaces, exactly<rbrace> > >(start: p+2)) { position = p;
1869 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ");
1870 }
1871 const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(src: p+2, end: id.end); // find the closing brace
1872 if (j) {
1873 // parse the interpolant and accumulate it
1874 LocalOption<const char*> partEnd(end, j);
1875 LocalOption<const char*> partBeg(position, p + 2);
1876 ExpressionObj interp_node = parse_list(delayed: DELAYED);
1877 interp_node->is_interpolant(is_interpolant__: true);
1878 schema->append(element: interp_node);
1879 // schema->has_interpolants(true);
1880 i = j;
1881 }
1882 else {
1883 // throw an error if the interpolant is unterminated
1884 error(msg: "unterminated interpolant inside interpolated identifier " + id.to_string());
1885 }
1886 }
1887 else { // no interpolants left; add the last segment if nonempty
1888 if (i < end) {
1889 const char* o = position; position = i;
1890 schema->append(element: parse_value_schema(stop: id.end));
1891 position = o;
1892 }
1893 break;
1894 }
1895 }
1896 return schema ? schema.detach() : 0;
1897 }
1898
1899 // calc functions should preserve arguments
1900 Function_Call_Obj Parser::parse_calc_function()
1901 {
1902 lex< identifier >();
1903 sass::string name(lexed);
1904 SourceSpan call_pos = pstate;
1905 lex< exactly<'('> >();
1906 SourceSpan arg_pos = pstate;
1907 const char* arg_beg = position;
1908 parse_list();
1909 const char* arg_end = position;
1910 lex< skip_over_scopes <
1911 exactly < '(' >,
1912 exactly < ')' >
1913 > >();
1914
1915 Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end)));
1916 Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos);
1917 args->append(element: arg);
1918 return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);
1919 }
1920
1921 String_Obj Parser::parse_url_function_string()
1922 {
1923 sass::string prefix("");
1924 if (lex< uri_prefix >()) {
1925 prefix = sass::string(lexed);
1926 }
1927
1928 lex < optional_spaces >();
1929 String_Obj url_string = parse_url_function_argument();
1930
1931 sass::string suffix("");
1932 if (lex< real_uri_suffix >()) {
1933 suffix = sass::string(lexed);
1934 }
1935
1936 sass::string uri("");
1937 if (url_string) {
1938 uri = url_string->to_string(opt: { NESTED, 5 });
1939 }
1940
1941 if (String_Schema* schema = Cast<String_Schema>(ptr: url_string)) {
1942 String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate);
1943 res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix));
1944 res->append(element: schema);
1945 res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix));
1946 return res;
1947 } else {
1948 sass::string res = prefix + uri + suffix;
1949 return SASS_MEMORY_NEW(String_Constant, pstate, res);
1950 }
1951 }
1952
1953 String_Obj Parser::parse_url_function_argument()
1954 {
1955 const char* p = position;
1956
1957 sass::string uri("");
1958 if (lex< real_uri_value >(lazy: false)) {
1959 uri = lexed.to_string();
1960 }
1961
1962 if (peek< exactly< hash_lbrace > >()) {
1963 const char* pp = position;
1964 // TODO: error checking for unclosed interpolants
1965 while (pp && peek< exactly< hash_lbrace > >(start: pp)) {
1966 pp = sequence< interpolant, real_uri_value >(src: pp);
1967 }
1968 if (!pp) return {};
1969 position = pp;
1970 return parse_interpolated_chunk(chunk: Token(p, position));
1971 }
1972 else if (uri != "") {
1973 sass::string res = Util::rtrim(str: uri);
1974 return SASS_MEMORY_NEW(String_Constant, pstate, res);
1975 }
1976
1977 return {};
1978 }
1979
1980 Function_Call_Obj Parser::parse_function_call()
1981 {
1982 lex< identifier >();
1983 sass::string name(lexed);
1984
1985 if (Util::normalize_underscores(str: name) == "content-exists" && stack.back() != Scope::Mixin)
1986 { error(msg: "Cannot call content-exists() except within a mixin."); }
1987
1988 SourceSpan call_pos = pstate;
1989 Arguments_Obj args = parse_arguments();
1990 return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);
1991 }
1992
1993 Function_Call_Obj Parser::parse_function_call_schema()
1994 {
1995 String_Obj name = parse_identifier_schema();
1996 SourceSpan source_position_of_call = pstate;
1997 Arguments_Obj args = parse_arguments();
1998
1999 return SASS_MEMORY_NEW(Function_Call, source_position_of_call, name, args);
2000 }
2001
2002 Content_Obj Parser::parse_content_directive()
2003 {
2004 SourceSpan call_pos = pstate;
2005 Arguments_Obj args = parse_arguments();
2006
2007 return SASS_MEMORY_NEW(Content, call_pos, args);
2008 }
2009
2010 If_Obj Parser::parse_if_directive(bool else_if)
2011 {
2012 stack.push_back(x: Scope::Control);
2013 SourceSpan if_source_position = pstate;
2014 bool root = block_stack.back()->is_root();
2015 ExpressionObj predicate = parse_list();
2016 Block_Obj block = parse_block(is_root: root);
2017 Block_Obj alternative;
2018
2019 // only throw away comment if we parse a case
2020 // we want all other comments to be parsed
2021 if (lex_css< elseif_directive >()) {
2022 alternative = SASS_MEMORY_NEW(Block, pstate);
2023 alternative->append(element: parse_if_directive(else_if: true));
2024 }
2025 else if (lex_css< kwd_else_directive >()) {
2026 alternative = parse_block(is_root: root);
2027 }
2028 stack.pop_back();
2029 return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative);
2030 }
2031
2032 ForRuleObj Parser::parse_for_directive()
2033 {
2034 stack.push_back(x: Scope::Control);
2035 SourceSpan for_source_position = pstate;
2036 bool root = block_stack.back()->is_root();
2037 lex_variable();
2038 sass::string var(Util::normalize_underscores(str: lexed));
2039 if (!lex< kwd_from >()) error(msg: "expected 'from' keyword in @for directive");
2040 ExpressionObj lower_bound = parse_expression();
2041 bool inclusive = false;
2042 if (lex< kwd_through >()) inclusive = true;
2043 else if (lex< kwd_to >()) inclusive = false;
2044 else error(msg: "expected 'through' or 'to' keyword in @for directive");
2045 ExpressionObj upper_bound = parse_expression();
2046 Block_Obj body = parse_block(is_root: root);
2047 stack.pop_back();
2048 return SASS_MEMORY_NEW(ForRule, for_source_position, var, lower_bound, upper_bound, body, inclusive);
2049 }
2050
2051 // helper to parse a var token
2052 Token Parser::lex_variable()
2053 {
2054 // peek for dollar sign first
2055 if (!peek< exactly <'$'> >()) {
2056 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"$\", was ");
2057 }
2058 // we expect a simple identifier as the call name
2059 if (!lex< sequence < exactly <'$'>, identifier > >()) {
2060 lex< exactly <'$'> >(); // move pstate and position up
2061 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected identifier, was ");
2062 }
2063 // return object
2064 return lexed;
2065 }
2066 // helper to parse identifier
2067 Token Parser::lex_identifier()
2068 {
2069 // we expect a simple identifier as the call name
2070 if (!lex< identifier >()) { // ToDo: pstate wrong?
2071 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected identifier, was ");
2072 }
2073 // return object
2074 return lexed;
2075 }
2076
2077 EachRuleObj Parser::parse_each_directive()
2078 {
2079 stack.push_back(x: Scope::Control);
2080 SourceSpan each_source_position = pstate;
2081 bool root = block_stack.back()->is_root();
2082 sass::vector<sass::string> vars;
2083 lex_variable();
2084 vars.push_back(x: Util::normalize_underscores(str: lexed));
2085 while (lex< exactly<','> >()) {
2086 if (!lex< variable >()) error(msg: "@each directive requires an iteration variable");
2087 vars.push_back(x: Util::normalize_underscores(str: lexed));
2088 }
2089 if (!lex< kwd_in >()) error(msg: "expected 'in' keyword in @each directive");
2090 ExpressionObj list = parse_list();
2091 Block_Obj body = parse_block(is_root: root);
2092 stack.pop_back();
2093 return SASS_MEMORY_NEW(EachRule, each_source_position, vars, list, body);
2094 }
2095
2096 // called after parsing `kwd_while_directive`
2097 WhileRuleObj Parser::parse_while_directive()
2098 {
2099 stack.push_back(x: Scope::Control);
2100 bool root = block_stack.back()->is_root();
2101 // create the initial while call object
2102 WhileRuleObj call = SASS_MEMORY_NEW(WhileRule, pstate, ExpressionObj{}, Block_Obj{});
2103 // parse mandatory predicate
2104 ExpressionObj predicate = parse_list();
2105 List_Obj l = Cast<List>(ptr: predicate);
2106 if (!predicate || (l && !l->length())) {
2107 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was ", trim: false);
2108 }
2109 call->predicate(predicate__: predicate);
2110 // parse mandatory block
2111 call->block(block__: parse_block(is_root: root));
2112 // return ast node
2113 stack.pop_back();
2114 // return ast node
2115 return call.detach();
2116 }
2117
2118
2119 sass::vector<CssMediaQuery_Obj> Parser::parseCssMediaQueries()
2120 {
2121 sass::vector<CssMediaQuery_Obj> result;
2122 do {
2123 if (auto query = parseCssMediaQuery()) {
2124 result.push_back(x: query);
2125 }
2126 } while (lex<exactly<','>>());
2127 return result;
2128 }
2129
2130 sass::string Parser::parseIdentifier()
2131 {
2132 if (lex < identifier >(lazy: false)) {
2133 return sass::string(lexed);
2134 }
2135 return sass::string();
2136 }
2137
2138 CssMediaQuery_Obj Parser::parseCssMediaQuery()
2139 {
2140 CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate);
2141 lex<css_comments>(lazy: false);
2142
2143 // Check if any tokens are to parse
2144 if (!peek_css<exactly<'('>>()) {
2145
2146 sass::string token1(parseIdentifier());
2147 lex<css_comments>(lazy: false);
2148
2149 if (token1.empty()) {
2150 return {};
2151 }
2152
2153 sass::string token2(parseIdentifier());
2154 lex<css_comments>(lazy: false);
2155
2156 if (Util::equalsLiteral(lit: "and", test: token2)) {
2157 result->type(type__: token1);
2158 }
2159 else {
2160 if (token2.empty()) {
2161 result->type(type__: token1);
2162 }
2163 else {
2164 result->modifier(modifier__: token1);
2165 result->type(type__: token2);
2166 }
2167
2168 if (lex < kwd_and >()) {
2169 lex<css_comments>(lazy: false);
2170 }
2171 else {
2172 return result;
2173 }
2174
2175 }
2176
2177 }
2178
2179 sass::vector<sass::string> queries;
2180
2181 do {
2182 lex<css_comments>(lazy: false);
2183
2184 if (lex<exactly<'('>>()) {
2185 // In dart sass parser returns a pure string
2186 if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) {
2187 sass::string decl("(" + sass::string(lexed));
2188 queries.push_back(x: decl);
2189 }
2190 // Should be: parseDeclarationValue;
2191 if (!lex<exactly<')'>>()) {
2192 // Should we throw an error here?
2193 }
2194 }
2195 } while (lex < kwd_and >());
2196
2197 result->features(features__: queries);
2198
2199 if (result->features().empty()) {
2200 if (result->type().empty()) {
2201 return {};
2202 }
2203 }
2204
2205 return result;
2206 }
2207
2208
2209 // EO parse_while_directive
2210 MediaRule_Obj Parser::parseMediaRule()
2211 {
2212 MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate);
2213 stack.push_back(x: Scope::Media);
2214 rule->schema(schema__: parse_media_queries());
2215 parse_block_comments(store: false);
2216 rule->block(block__: parse_css_block());
2217 stack.pop_back();
2218 return rule;
2219 }
2220
2221 List_Obj Parser::parse_media_queries()
2222 {
2223 advanceToNextToken();
2224 List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA);
2225 if (!peek_css < exactly <'{'> >()) queries->append(element: parse_media_query());
2226 while (lex_css < exactly <','> >()) queries->append(element: parse_media_query());
2227 queries->update_pstate(pstate);
2228 return queries.detach();
2229 }
2230
2231 // Expression* Parser::parse_media_query()
2232 Media_Query_Obj Parser::parse_media_query()
2233 {
2234 advanceToNextToken();
2235 Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate);
2236 if (lex < kwd_not >()) { media_query->is_negated(is_negated__: true); lex < css_comments >(lazy: false); }
2237 else if (lex < kwd_only >()) { media_query->is_restricted(is_restricted__: true); lex < css_comments >(lazy: false); }
2238
2239 if (lex < identifier_schema >()) media_query->media_type(media_type__: parse_identifier_schema());
2240 else if (lex < identifier >()) media_query->media_type(media_type__: parse_interpolated_chunk(chunk: lexed));
2241 else media_query->append(element: parse_media_expression());
2242
2243 while (lex_css < kwd_and >()) media_query->append(element: parse_media_expression());
2244 if (lex < identifier_schema >()) {
2245 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
2246 if (media_query->media_type()) {
2247 schema->append(element: media_query->media_type());
2248 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
2249 }
2250 schema->append(element: parse_identifier_schema());
2251 media_query->media_type(media_type__: schema);
2252 }
2253 while (lex_css < kwd_and >()) media_query->append(element: parse_media_expression());
2254
2255 media_query->update_pstate(pstate);
2256
2257 return media_query;
2258 }
2259
2260 Media_Query_ExpressionObj Parser::parse_media_expression()
2261 {
2262 if (lex < identifier_schema >()) {
2263 String_Obj ss = parse_identifier_schema();
2264 return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, ExpressionObj{}, true);
2265 }
2266 if (!lex_css< exactly<'('> >()) {
2267 error(msg: "media query expression must begin with '('");
2268 }
2269 ExpressionObj feature;
2270 if (peek_css< exactly<')'> >()) {
2271 error(msg: "media feature required in media query expression");
2272 }
2273 feature = parse_expression();
2274 ExpressionObj expression;
2275 if (lex_css< exactly<':'> >()) {
2276 expression = parse_list(delayed: DELAYED);
2277 }
2278 if (!lex_css< exactly<')'> >()) {
2279 error(msg: "unclosed parenthesis in media query expression");
2280 }
2281 return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression);
2282 }
2283
2284 // lexed after `kwd_supports_directive`
2285 // these are very similar to media blocks
2286 SupportsRuleObj Parser::parse_supports_directive()
2287 {
2288 SupportsConditionObj cond = parse_supports_condition(/*top_level=*/true);
2289 // create the ast node object for the support queries
2290 SupportsRuleObj query = SASS_MEMORY_NEW(SupportsRule, pstate, cond);
2291 // additional block is mandatory
2292 // parse inner block
2293 query->block(block__: parse_block());
2294 // return ast node
2295 return query;
2296 }
2297
2298 // parse one query operation
2299 // may encounter nested queries
2300 SupportsConditionObj Parser::parse_supports_condition(bool top_level)
2301 {
2302 lex < css_whitespace >();
2303 SupportsConditionObj cond;
2304 if ((cond = parse_supports_negation())) return cond;
2305 if ((cond = parse_supports_operator(top_level))) return cond;
2306 if ((cond = parse_supports_interpolation())) return cond;
2307 return cond;
2308 }
2309
2310 SupportsConditionObj Parser::parse_supports_negation()
2311 {
2312 if (!lex < kwd_not >()) return {};
2313 SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/true);
2314 return SASS_MEMORY_NEW(SupportsNegation, pstate, cond);
2315 }
2316
2317 SupportsConditionObj Parser::parse_supports_operator(bool top_level)
2318 {
2319 SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/top_level);
2320 if (cond.isNull()) return {};
2321
2322 while (true) {
2323 SupportsOperation::Operand op = SupportsOperation::OR;
2324 if (lex < kwd_and >()) { op = SupportsOperation::AND; }
2325 else if(!lex < kwd_or >()) { break; }
2326
2327 lex < css_whitespace >();
2328 SupportsConditionObj right = parse_supports_condition_in_parens(/*parens_required=*/true);
2329
2330 // SupportsCondition* cc = SASS_MEMORY_NEW(SupportsCondition, *static_cast<SupportsCondition*>(cond));
2331 cond = SASS_MEMORY_NEW(SupportsOperation, pstate, cond, right, op);
2332 }
2333 return cond;
2334 }
2335
2336 SupportsConditionObj Parser::parse_supports_interpolation()
2337 {
2338 if (!lex < interpolant >()) return {};
2339
2340 String_Obj interp = parse_interpolated_chunk(chunk: lexed);
2341 if (!interp) return {};
2342
2343 return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp);
2344 }
2345
2346 // TODO: This needs some major work. Although feature conditions
2347 // look like declarations their semantics differ significantly
2348 SupportsConditionObj Parser::parse_supports_declaration()
2349 {
2350 SupportsCondition* cond;
2351 // parse something declaration like
2352 ExpressionObj feature = parse_expression();
2353 ExpressionObj expression;
2354 if (lex_css< exactly<':'> >()) {
2355 expression = parse_list(delayed: DELAYED);
2356 }
2357 if (!feature || !expression) error(msg: "@supports condition expected declaration");
2358 cond = SASS_MEMORY_NEW(SupportsDeclaration,
2359 feature->pstate(),
2360 feature,
2361 expression);
2362 // ToDo: maybe we need an additional error condition?
2363 return cond;
2364 }
2365
2366 SupportsConditionObj Parser::parse_supports_condition_in_parens(bool parens_required)
2367 {
2368 SupportsConditionObj interp = parse_supports_interpolation();
2369 if (interp != nullptr) return interp;
2370
2371 if (!lex < exactly <'('> >()) {
2372 if (parens_required) {
2373 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected @supports condition (e.g. (display: flexbox)), was ", /*trim=*/false);
2374 } else {
2375 return {};
2376 }
2377 }
2378 lex < css_whitespace >();
2379
2380 SupportsConditionObj cond = parse_supports_condition(/*top_level=*/false);
2381 if (cond.isNull()) cond = parse_supports_declaration();
2382 if (!lex < exactly <')'> >()) error(msg: "unclosed parenthesis in @supports declaration");
2383
2384 lex < css_whitespace >();
2385 return cond;
2386 }
2387
2388 AtRootRuleObj Parser::parse_at_root_block()
2389 {
2390 stack.push_back(x: Scope::AtRoot);
2391 SourceSpan at_source_position = pstate;
2392 Block_Obj body;
2393 At_Root_Query_Obj expr;
2394 Lookahead lookahead_result;
2395 if (lex_css< exactly<'('> >()) {
2396 expr = parse_at_root_query();
2397 }
2398 if (peek_css < exactly<'{'> >()) {
2399 lex <optional_spaces>();
2400 body = parse_block(is_root: true);
2401 }
2402 else if ((lookahead_result = lookahead_for_selector(start: position)).found) {
2403 StyleRuleObj r = parse_ruleset(lookahead: lookahead_result);
2404 body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true);
2405 body->append(element: r);
2406 }
2407 AtRootRuleObj at_root = SASS_MEMORY_NEW(AtRootRule, at_source_position, body);
2408 if (!expr.isNull()) at_root->expression(expression__: expr);
2409 stack.pop_back();
2410 return at_root;
2411 }
2412
2413 At_Root_Query_Obj Parser::parse_at_root_query()
2414 {
2415 if (peek< exactly<')'> >()) error(msg: "at-root feature required in at-root expression");
2416
2417 if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) {
2418 css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected \"with\" or \"without\", was ");
2419 }
2420
2421 ExpressionObj feature = parse_list();
2422 if (!lex_css< exactly<':'> >()) error(msg: "style declaration must contain a value");
2423 ExpressionObj expression = parse_list();
2424 List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1);
2425
2426 if (expression->concrete_type() == Expression::LIST) {
2427 value = Cast<List>(ptr: expression);
2428 }
2429 else value->append(element: expression);
2430
2431 At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query,
2432 value->pstate(),
2433 feature,
2434 value);
2435 if (!lex_css< exactly<')'> >()) error(msg: "unclosed parenthesis in @at-root expression");
2436 return cond;
2437 }
2438
2439 AtRuleObj Parser::parse_directive()
2440 {
2441 AtRuleObj directive = SASS_MEMORY_NEW(AtRule, pstate, lexed);
2442 String_Schema_Obj val = parse_almost_any_value();
2443 // strip left and right if they are of type string
2444 directive->value(value__: val);
2445 if (peek< exactly<'{'> >()) {
2446 directive->block(block__: parse_block());
2447 }
2448 return directive;
2449 }
2450
2451 ExpressionObj Parser::lex_interpolation()
2452 {
2453 if (lex < interpolant >(lazy: true) != NULL) {
2454 return parse_interpolated_chunk(chunk: lexed, constant: true);
2455 }
2456 return {};
2457 }
2458
2459 ExpressionObj Parser::lex_interp_uri()
2460 {
2461 // create a string schema by lexing optional interpolations
2462 return lex_interp< re_string_uri_open, re_string_uri_close >();
2463 }
2464
2465 ExpressionObj Parser::lex_interp_string()
2466 {
2467 ExpressionObj rv;
2468 if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv;
2469 if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv;
2470 return rv;
2471 }
2472
2473 ExpressionObj Parser::lex_almost_any_value_chars()
2474 {
2475 const char* match =
2476 lex <
2477 one_plus <
2478 alternatives <
2479 exactly <'>'>,
2480 sequence <
2481 exactly <'\\'>,
2482 any_char
2483 >,
2484 sequence <
2485 negate <
2486 sequence <
2487 exactly < url_kwd >,
2488 exactly <'('>
2489 >
2490 >,
2491 neg_class_char <
2492 almost_any_value_class
2493 >
2494 >,
2495 sequence <
2496 exactly <'/'>,
2497 negate <
2498 alternatives <
2499 exactly <'/'>,
2500 exactly <'*'>
2501 >
2502 >
2503 >,
2504 sequence <
2505 exactly <'\\'>,
2506 exactly <'#'>,
2507 negate <
2508 exactly <'{'>
2509 >
2510 >,
2511 sequence <
2512 exactly <'!'>,
2513 negate <
2514 alpha
2515 >
2516 >
2517 >
2518 >
2519 >(lazy: false);
2520 if (match) {
2521 return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
2522 }
2523 return {};
2524 }
2525
2526 ExpressionObj Parser::lex_almost_any_value_token()
2527 {
2528 ExpressionObj rv;
2529 if (*position == 0) return {};
2530 if ((rv = lex_almost_any_value_chars())) return rv;
2531 // if ((rv = lex_block_comment())) return rv;
2532 // if ((rv = lex_single_line_comment())) return rv;
2533 if ((rv = lex_interp_string())) return rv;
2534 if ((rv = lex_interp_uri())) return rv;
2535 if ((rv = lex_interpolation())) return rv;
2536 if (lex< alternatives< hex, hex0 > >())
2537 { return lexed_hex_color(parsed: lexed); }
2538 return rv;
2539 }
2540
2541 String_Schema_Obj Parser::parse_almost_any_value()
2542 {
2543
2544 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
2545 if (*position == 0) return {};
2546 lex < spaces >(lazy: false);
2547 ExpressionObj token = lex_almost_any_value_token();
2548 if (!token) return {};
2549 schema->append(element: token);
2550 if (*position == 0) {
2551 schema->rtrim();
2552 return schema.detach();
2553 }
2554
2555 while ((token = lex_almost_any_value_token())) {
2556 schema->append(element: token);
2557 }
2558
2559 lex < css_whitespace >();
2560
2561 schema->rtrim();
2562
2563 return schema.detach();
2564 }
2565
2566 WarningRuleObj Parser::parse_warning()
2567 {
2568 if (stack.back() != Scope::Root &&
2569 stack.back() != Scope::Function &&
2570 stack.back() != Scope::Mixin &&
2571 stack.back() != Scope::Control &&
2572 stack.back() != Scope::Rules) {
2573 error(msg: "Illegal nesting: Only properties may be nested beneath properties.");
2574 }
2575 return SASS_MEMORY_NEW(WarningRule, pstate, parse_list(DELAYED));
2576 }
2577
2578 ErrorRuleObj Parser::parse_error()
2579 {
2580 if (stack.back() != Scope::Root &&
2581 stack.back() != Scope::Function &&
2582 stack.back() != Scope::Mixin &&
2583 stack.back() != Scope::Control &&
2584 stack.back() != Scope::Rules) {
2585 error(msg: "Illegal nesting: Only properties may be nested beneath properties.");
2586 }
2587 return SASS_MEMORY_NEW(ErrorRule, pstate, parse_list(DELAYED));
2588 }
2589
2590 DebugRuleObj Parser::parse_debug()
2591 {
2592 if (stack.back() != Scope::Root &&
2593 stack.back() != Scope::Function &&
2594 stack.back() != Scope::Mixin &&
2595 stack.back() != Scope::Control &&
2596 stack.back() != Scope::Rules) {
2597 error(msg: "Illegal nesting: Only properties may be nested beneath properties.");
2598 }
2599 return SASS_MEMORY_NEW(DebugRule, pstate, parse_list(DELAYED));
2600 }
2601
2602 Return_Obj Parser::parse_return_directive()
2603 {
2604 // check that we do not have an empty list (ToDo: check if we got all cases)
2605 if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >())
2606 { css_error(msg: "Invalid CSS", prefix: " after ", middle: ": expected expression (e.g. 1px, bold), was "); }
2607 return SASS_MEMORY_NEW(Return, pstate, parse_list());
2608 }
2609
2610 Lookahead Parser::lookahead_for_selector(const char* start)
2611 {
2612 // init result struct
2613 Lookahead rv = Lookahead();
2614 // get start position
2615 const char* p = start ? start : position;
2616 // match in one big "regex"
2617 rv.error = p;
2618 if (const char* q =
2619 peek <
2620 re_selector_list
2621 >(start: p)
2622 ) {
2623 bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(start: p) != 0;
2624 bool could_be_escaped = false;
2625 while (p < q) {
2626 // did we have interpolations?
2627 if (*p == '#' && *(p+1) == '{') {
2628 rv.has_interpolants = true;
2629 p = q; break;
2630 }
2631 // A property that's ambiguous with a nested selector is interpreted as a
2632 // custom property.
2633 if (*p == ':' && !could_be_escaped) {
2634 rv.is_custom_property = could_be_property || p+1 == q || peek< space >(start: p+1);
2635 }
2636 could_be_escaped = *p == '\\';
2637 ++ p;
2638 }
2639 // store anyway }
2640
2641
2642 // ToDo: remove
2643 rv.error = q;
2644 rv.position = q;
2645 // check expected opening bracket
2646 // only after successful matching
2647 if (peek < exactly<'{'> >(start: q)) rv.found = q;
2648 // else if (peek < end_of_file >(q)) rv.found = q;
2649 else if (peek < exactly<'('> >(start: q)) rv.found = q;
2650 // else if (peek < exactly<';'> >(q)) rv.found = q;
2651 // else if (peek < exactly<'}'> >(q)) rv.found = q;
2652 if (rv.found || *p == 0) rv.error = 0;
2653 }
2654
2655 rv.parsable = ! rv.has_interpolants;
2656
2657 // return result
2658 return rv;
2659
2660 }
2661 // EO lookahead_for_selector
2662
2663 // used in parse_block_nodes and parse_special_directive
2664 // ToDo: actual usage is still not really clear to me?
2665 Lookahead Parser::lookahead_for_include(const char* start)
2666 {
2667 // we actually just lookahead for a selector
2668 Lookahead rv = lookahead_for_selector(start);
2669 // but the "found" rules are different
2670 if (const char* p = rv.position) {
2671 // check for additional abort condition
2672 if (peek < exactly<';'> >(start: p)) rv.found = p;
2673 else if (peek < exactly<'}'> >(start: p)) rv.found = p;
2674 }
2675 // return result
2676 return rv;
2677 }
2678 // EO lookahead_for_include
2679
2680 // look ahead for a token with interpolation in it
2681 // we mostly use the result if there is an interpolation
2682 // everything that passes here gets parsed as one schema
2683 // meaning it will not be parsed as a space separated list
2684 Lookahead Parser::lookahead_for_value(const char* start)
2685 {
2686 // init result struct
2687 Lookahead rv = Lookahead();
2688 // get start position
2689 const char* p = start ? start : position;
2690 // match in one big "regex"
2691 if (const char* q =
2692 peek <
2693 non_greedy <
2694 alternatives <
2695 // consume whitespace
2696 block_comment, // spaces,
2697 // main tokens
2698 sequence <
2699 interpolant,
2700 optional <
2701 quoted_string
2702 >
2703 >,
2704 identifier,
2705 variable,
2706 // issue #442
2707 sequence <
2708 parenthese_scope,
2709 interpolant,
2710 optional <
2711 quoted_string
2712 >
2713 >
2714 >,
2715 sequence <
2716 // optional_spaces,
2717 alternatives <
2718 // end_of_file,
2719 exactly<'{'>,
2720 exactly<'}'>,
2721 exactly<';'>
2722 >
2723 >
2724 >
2725 >(start: p)
2726 ) {
2727 if (p == q) return rv;
2728 while (p < q) {
2729 // did we have interpolations?
2730 if (*p == '#' && *(p+1) == '{') {
2731 rv.has_interpolants = true;
2732 p = q; break;
2733 }
2734 ++ p;
2735 }
2736 // store anyway
2737 // ToDo: remove
2738 rv.position = q;
2739 // check expected opening bracket
2740 // only after successful matching
2741 if (peek < exactly<'{'> >(start: q)) rv.found = q;
2742 else if (peek < exactly<';'> >(start: q)) rv.found = q;
2743 else if (peek < exactly<'}'> >(start: q)) rv.found = q;
2744 }
2745
2746 // return result
2747 return rv;
2748 }
2749 // EO lookahead_for_value
2750
2751 void Parser::read_bom()
2752 {
2753 size_t skip = 0;
2754 sass::string encoding;
2755 bool utf_8 = false;
2756 switch ((unsigned char)position[0]) {
2757 case 0xEF:
2758 skip = check_bom_chars(src: position, end, bom: utf_8_bom, len: 3);
2759 encoding = "UTF-8";
2760 utf_8 = true;
2761 break;
2762 case 0xFE:
2763 skip = check_bom_chars(src: position, end, bom: utf_16_bom_be, len: 2);
2764 encoding = "UTF-16 (big endian)";
2765 break;
2766 case 0xFF:
2767 skip = check_bom_chars(src: position, end, bom: utf_16_bom_le, len: 2);
2768 skip += (skip ? check_bom_chars(src: position, end, bom: utf_32_bom_le, len: 4) : 0);
2769 encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)");
2770 break;
2771 case 0x00:
2772 skip = check_bom_chars(src: position, end, bom: utf_32_bom_be, len: 4);
2773 encoding = "UTF-32 (big endian)";
2774 break;
2775 case 0x2B:
2776 skip = check_bom_chars(src: position, end, bom: utf_7_bom_1, len: 4)
2777 | check_bom_chars(src: position, end, bom: utf_7_bom_2, len: 4)
2778 | check_bom_chars(src: position, end, bom: utf_7_bom_3, len: 4)
2779 | check_bom_chars(src: position, end, bom: utf_7_bom_4, len: 4)
2780 | check_bom_chars(src: position, end, bom: utf_7_bom_5, len: 5);
2781 encoding = "UTF-7";
2782 break;
2783 case 0xF7:
2784 skip = check_bom_chars(src: position, end, bom: utf_1_bom, len: 3);
2785 encoding = "UTF-1";
2786 break;
2787 case 0xDD:
2788 skip = check_bom_chars(src: position, end, bom: utf_ebcdic_bom, len: 4);
2789 encoding = "UTF-EBCDIC";
2790 break;
2791 case 0x0E:
2792 skip = check_bom_chars(src: position, end, bom: scsu_bom, len: 3);
2793 encoding = "SCSU";
2794 break;
2795 case 0xFB:
2796 skip = check_bom_chars(src: position, end, bom: bocu_1_bom, len: 3);
2797 encoding = "BOCU-1";
2798 break;
2799 case 0x84:
2800 skip = check_bom_chars(src: position, end, bom: gb_18030_bom, len: 4);
2801 encoding = "GB-18030";
2802 break;
2803 default: break;
2804 }
2805 if (skip > 0 && !utf_8) error(msg: "only UTF-8 documents are currently supported; your document appears to be " + encoding);
2806 position += skip;
2807 }
2808
2809 size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len)
2810 {
2811 size_t skip = 0;
2812 if (src + len > end) return 0;
2813 for (size_t i = 0; i < len; ++i, ++skip) {
2814 if ((unsigned char) src[i] != bom[i]) return 0;
2815 }
2816 return skip;
2817 }
2818
2819
2820 ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, Operand op)
2821 {
2822 for (size_t i = 0, S = operands.size(); i < S; ++i) {
2823 base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]);
2824 }
2825 return base;
2826 }
2827
2828 ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, sass::vector<Operand>& ops, size_t i)
2829 {
2830 if (String_Schema* schema = Cast<String_Schema>(ptr: base)) {
2831 // return schema;
2832 if (schema->has_interpolants()) {
2833 if (i + 1 < operands.size() && (
2834 (ops[0].operand == Sass_OP::EQ)
2835 || (ops[0].operand == Sass_OP::ADD)
2836 || (ops[0].operand == Sass_OP::DIV)
2837 || (ops[0].operand == Sass_OP::MUL)
2838 || (ops[0].operand == Sass_OP::NEQ)
2839 || (ops[0].operand == Sass_OP::LT)
2840 || (ops[0].operand == Sass_OP::GT)
2841 || (ops[0].operand == Sass_OP::LTE)
2842 || (ops[0].operand == Sass_OP::GTE)
2843 )) {
2844 ExpressionObj rhs = fold_operands(base: operands[i], operands, ops, i: i + 1);
2845 rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs);
2846 return rhs;
2847 }
2848 // return schema;
2849 }
2850 }
2851
2852 if (operands.size() > Constants::MaxCallStack) {
2853 // XXX: this is never hit via spec tests
2854 sass::ostream stm;
2855 stm << "Stack depth exceeded max of " << Constants::MaxCallStack;
2856 error(msg: stm.str());
2857 }
2858
2859 for (size_t S = operands.size(); i < S; ++i) {
2860 if (String_Schema* schema = Cast<String_Schema>(ptr: operands[i])) {
2861 if (schema->has_interpolants()) {
2862 if (i + 1 < S) {
2863 // this whole branch is never hit via spec tests
2864 ExpressionObj rhs = fold_operands(base: operands[i+1], operands, ops, i: i + 2);
2865 rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs);
2866 base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs);
2867 return base;
2868 }
2869 base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
2870 return base;
2871 } else {
2872 base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
2873 }
2874 } else {
2875 base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
2876 }
2877 Binary_Expression* b = Cast<Binary_Expression>(ptr: base.ptr());
2878 if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {
2879 base->is_delayed(is_delayed__: true);
2880 }
2881 }
2882 // nested binary expression are never to be delayed
2883 if (Binary_Expression* b = Cast<Binary_Expression>(ptr: base)) {
2884 if (Cast<Binary_Expression>(ptr: b->left())) base->set_delayed(false);
2885 if (Cast<Binary_Expression>(ptr: b->right())) base->set_delayed(false);
2886 }
2887 return base;
2888 }
2889
2890 void Parser::error(sass::string msg)
2891 {
2892 traces.push_back(x: Backtrace(pstate));
2893 throw Exception::InvalidSass(pstate, traces, msg);
2894 }
2895
2896 // print a css parsing error with actual context information from parsed source
2897 void Parser::css_error(const sass::string& msg, const sass::string& prefix, const sass::string& middle, const bool trim)
2898 {
2899 int max_len = 18;
2900 const char* end = this->end;
2901 while (*end != 0) ++ end;
2902 const char* pos = peek < optional_spaces >();
2903 if (!pos) pos = position;
2904
2905 const char* last_pos(pos);
2906 if (last_pos > begin) {
2907 utf8::prior(it&: last_pos, start: begin);
2908 }
2909 // backup position to last significant char
2910 while (trim && last_pos > begin&& last_pos < end) {
2911 if (!Util::ascii_isspace(c: static_cast<unsigned char>(*last_pos))) break;
2912 utf8::prior(it&: last_pos, start: begin);
2913 }
2914
2915 bool ellipsis_left = false;
2916 const char* pos_left(last_pos);
2917 const char* end_left(last_pos);
2918
2919 if (*pos_left) utf8::next(it&: pos_left, end);
2920 if (*end_left) utf8::next(it&: end_left, end);
2921 while (pos_left > begin) {
2922 if (utf8::distance(first: pos_left, last: end_left) >= max_len) {
2923 utf8::prior(it&: pos_left, start: begin);
2924 ellipsis_left = *(pos_left) != '\n' &&
2925 *(pos_left) != '\r';
2926 utf8::next(it&: pos_left, end);
2927 break;
2928 }
2929
2930 const char* prev = pos_left;
2931 utf8::prior(it&: prev, start: begin);
2932 if (*prev == '\r') break;
2933 if (*prev == '\n') break;
2934 pos_left = prev;
2935 }
2936 if (pos_left < begin) {
2937 pos_left = begin;
2938 }
2939
2940 bool ellipsis_right = false;
2941 const char* end_right(pos);
2942 const char* pos_right(pos);
2943 while (end_right < end) {
2944 if (utf8::distance(first: pos_right, last: end_right) > max_len) {
2945 ellipsis_left = *(pos_right) != '\n' &&
2946 *(pos_right) != '\r';
2947 break;
2948 }
2949 if (*end_right == '\r') break;
2950 if (*end_right == '\n') break;
2951 utf8::next(it&: end_right, end);
2952 }
2953 // if (*end_right == 0) end_right ++;
2954
2955 sass::string left(pos_left, end_left);
2956 sass::string right(pos_right, end_right);
2957 size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0;
2958 size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0;
2959 if (left_subpos && ellipsis_left) left = ellipsis + left.substr(pos: left_subpos);
2960 if (right_subpos && ellipsis_right) right = right.substr(pos: right_subpos) + ellipsis;
2961 // now pass new message to the more generic error function
2962 error(msg: msg + prefix + quote(left) + middle + quote(right));
2963 }
2964
2965}
2966

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