1use std::{
2 cell::Cell,
3 collections::{BTreeMap, HashSet},
4 ffi::OsString,
5 mem,
6 path::{Path, PathBuf},
7 sync::Arc,
8};
9
10use codemap::{Span, Spanned};
11
12use crate::{
13 ast::*,
14 common::{unvendor, Identifier, QuoteKind},
15 error::SassResult,
16 lexer::Lexer,
17 utils::{is_name, is_name_start, is_plain_css_import, opposite_bracket},
18 ContextFlags, Options, Token,
19};
20
21use super::{
22 value::{Predicate, ValueParser},
23 BaseParser, DeclarationOrBuffer, ScssParser, VariableDeclOrInterpolation, RESERVED_IDENTIFIERS,
24};
25
26pub(crate) trait StylesheetParser<'a>: BaseParser + Sized {
29 fn is_plain_css(&self) -> bool;
31 fn is_indented(&self) -> bool;
33 fn options(&self) -> &Options;
34 fn path(&self) -> &Path;
35 fn empty_span(&self) -> Span;
36 fn current_indentation(&self) -> usize;
37 fn flags(&self) -> &ContextFlags;
38 fn flags_mut(&mut self) -> &mut ContextFlags;
39
40 #[allow(clippy::type_complexity)]
41 const IDENTIFIER_LIKE: Option<fn(&mut Self) -> SassResult<Spanned<AstExpr>>> = None;
42
43 fn parse_style_rule_selector(&mut self) -> SassResult<Interpolation> {
44 self.almost_any_value(false)
45 }
46
47 fn expect_statement_separator(&mut self, _name: Option<&str>) -> SassResult<()> {
48 self.whitespace_without_comments();
49 match self.toks().peek() {
50 Some(Token {
51 kind: ';' | '}', ..
52 })
53 | None => Ok(()),
54 _ => {
55 self.expect_char(';')?;
56 unreachable!();
57 }
58 }
59 }
60
61 fn at_end_of_statement(&self) -> bool {
62 matches!(
63 self.toks().peek(),
64 Some(Token {
65 kind: ';' | '}' | '{',
66 ..
67 }) | None
68 )
69 }
70
71 fn looking_at_children(&mut self) -> SassResult<bool> {
72 Ok(matches!(self.toks().peek(), Some(Token { kind: '{', .. })))
73 }
74
75 fn scan_else(&mut self, _if_indentation: usize) -> SassResult<bool> {
76 let start = self.toks().cursor();
77
78 self.whitespace()?;
79
80 if self.scan_char('@') {
81 if self.scan_identifier("else", true)? {
82 return Ok(true);
83 }
84
85 if self.scan_identifier("elseif", true)? {
86 let new_cursor = self.toks().cursor() - 2;
88 self.toks_mut().set_cursor(new_cursor);
89 return Ok(true);
90 }
91 }
92
93 self.toks_mut().set_cursor(start);
94
95 Ok(false)
96 }
97
98 fn parse_children(
99 &mut self,
100 child: fn(&mut Self) -> SassResult<AstStmt>,
101 ) -> SassResult<Vec<AstStmt>> {
102 self.expect_char('{')?;
103 self.whitespace_without_comments();
104 let mut children = Vec::new();
105
106 let mut found_matching_brace = false;
107
108 while let Some(tok) = self.toks().peek() {
109 match tok.kind {
110 '$' => children.push(AstStmt::VariableDecl(
111 self.parse_variable_declaration_without_namespace(None, None)?,
112 )),
113 '/' => match self.toks().peek_n(1) {
114 Some(Token { kind: '/', .. }) => {
115 children.push(self.parse_silent_comment()?);
116 self.whitespace_without_comments();
117 }
118 Some(Token { kind: '*', .. }) => {
119 children.push(AstStmt::LoudComment(self.parse_loud_comment()?));
120 self.whitespace_without_comments();
121 }
122 _ => children.push(child(self)?),
123 },
124 ';' => {
125 self.toks_mut().next();
126 self.whitespace_without_comments();
127 }
128 '}' => {
129 self.expect_char('}')?;
130 found_matching_brace = true;
131 break;
132 }
133 _ => children.push(child(self)?),
134 }
135 }
136
137 if !found_matching_brace {
138 return Err(("expected \"}\".", self.toks().current_span()).into());
139 }
140
141 Ok(children)
142 }
143
144 fn parse_statements(
145 &mut self,
146 statement: fn(&mut Self) -> SassResult<Option<AstStmt>>,
147 ) -> SassResult<Vec<AstStmt>> {
148 let mut stmts = Vec::new();
149 self.whitespace_without_comments();
150 while let Some(tok) = self.toks().peek() {
151 match tok.kind {
152 '$' => stmts.push(AstStmt::VariableDecl(
153 self.parse_variable_declaration_without_namespace(None, None)?,
154 )),
155 '/' => match self.toks().peek_n(1) {
156 Some(Token { kind: '/', .. }) => {
157 stmts.push(self.parse_silent_comment()?);
158 self.whitespace_without_comments();
159 }
160 Some(Token { kind: '*', .. }) => {
161 stmts.push(AstStmt::LoudComment(self.parse_loud_comment()?));
162 self.whitespace_without_comments();
163 }
164 _ => {
165 if let Some(stmt) = statement(self)? {
166 stmts.push(stmt);
167 }
168 }
169 },
170 ';' => {
171 self.toks_mut().next();
172 self.whitespace_without_comments();
173 }
174 _ => {
175 if let Some(stmt) = statement(self)? {
176 stmts.push(stmt);
177 }
178 }
179 }
180 }
181
182 Ok(stmts)
183 }
184
185 fn __parse(&mut self) -> SassResult<StyleSheet> {
187 let mut style_sheet = StyleSheet::new(
188 self.is_plain_css(),
189 self.options()
190 .fs
191 .canonicalize(self.path())
192 .unwrap_or_else(|_| self.path().to_path_buf()),
193 );
194
195 self.scan_char('\u{feff}');
197
198 style_sheet.body = self.parse_statements(|parser| {
199 if parser.next_matches("@charset") {
200 parser.expect_char('@')?;
201 parser.expect_identifier("charset", false)?;
202 parser.whitespace()?;
203 parser.parse_string()?;
204 return Ok(None);
205 }
206
207 Ok(Some(parser.parse_statement()?))
208 })?;
209
210 for (idx, child) in style_sheet.body.iter().enumerate() {
211 match child {
212 AstStmt::VariableDecl(_) | AstStmt::LoudComment(_) | AstStmt::SilentComment(_) => {
213 continue
214 }
215 AstStmt::Use(..) => style_sheet.uses.push(idx),
216 AstStmt::Forward(..) => style_sheet.forwards.push(idx),
217 _ => break,
218 }
219 }
220
221 Ok(style_sheet)
222 }
223
224 fn looking_at_expression(&mut self) -> bool {
225 let character = if let Some(c) = self.toks().peek() {
226 c
227 } else {
228 return false;
229 };
230
231 match character.kind {
232 '.' => !matches!(self.toks().peek_n(1), Some(Token { kind: '.', .. })),
233 '!' => match self.toks().peek_n(1) {
234 Some(Token {
235 kind: 'i' | 'I', ..
236 })
237 | None => true,
238 Some(Token { kind, .. }) => kind.is_ascii_whitespace(),
239 },
240 '(' | '/' | '[' | '\'' | '"' | '#' | '+' | '-' | '\\' | '$' | '&' => true,
241 c => is_name_start(c) || c.is_ascii_digit(),
242 }
243 }
244
245 fn parse_argument_declaration(&mut self) -> SassResult<ArgumentDeclaration> {
246 self.expect_char('(')?;
247 self.whitespace()?;
248
249 let mut arguments = Vec::new();
250 let mut named = HashSet::new();
251
252 let mut rest_argument: Option<Identifier> = None;
253
254 while self.toks_mut().next_char_is('$') {
255 let name_start = self.toks().cursor();
256 let name = Identifier::from(self.parse_variable_name()?);
257 let name_span = self.toks_mut().span_from(name_start);
258 self.whitespace()?;
259
260 let mut default_value: Option<AstExpr> = None;
261
262 if self.scan_char(':') {
263 self.whitespace()?;
264 default_value = Some(self.parse_expression_until_comma(false)?.node);
265 } else if self.scan_char('.') {
266 self.expect_char('.')?;
267 self.expect_char('.')?;
268 self.whitespace()?;
269 rest_argument = Some(name);
270 break;
271 }
272
273 arguments.push(Argument {
274 name,
275 default: default_value,
276 });
277
278 if !named.insert(name) {
279 return Err(("Duplicate argument.", name_span).into());
280 }
281
282 if !self.scan_char(',') {
283 break;
284 }
285 self.whitespace()?;
286 }
287 self.expect_char(')')?;
288
289 Ok(ArgumentDeclaration {
290 args: arguments,
291 rest: rest_argument,
292 })
293 }
294
295 fn plain_at_rule_name(&mut self) -> SassResult<String> {
296 self.expect_char('@')?;
297 let name = self.parse_identifier(false, false)?;
298 self.whitespace()?;
299 Ok(name)
300 }
301
302 fn with_children(
303 &mut self,
304 child: fn(&mut Self) -> SassResult<AstStmt>,
305 ) -> SassResult<Spanned<Vec<AstStmt>>> {
306 let start = self.toks().cursor();
307 let children = self.parse_children(child)?;
308 let span = self.toks_mut().span_from(start);
309 self.whitespace_without_comments();
310 Ok(Spanned {
311 node: children,
312 span,
313 })
314 }
315
316 fn parse_at_root_query(&mut self) -> SassResult<Interpolation> {
317 let mut buffer = Interpolation::new();
318 self.expect_char('(')?;
319 buffer.add_char('(');
320
321 self.whitespace()?;
322
323 buffer.add_expr(self.parse_expression(None, None, None)?);
324
325 if self.scan_char(':') {
326 self.whitespace()?;
327 buffer.add_char(':');
328 buffer.add_char(' ');
329 buffer.add_expr(self.parse_expression(None, None, None)?);
330 }
331
332 self.expect_char(')')?;
333 self.whitespace()?;
334 buffer.add_char(')');
335
336 Ok(buffer)
337 }
338
339 fn parse_at_root_rule(&mut self, start: usize) -> SassResult<AstStmt> {
340 Ok(AstStmt::AtRootRule(if self.toks_mut().next_char_is('(') {
341 let query_start = self.toks().cursor();
342 let query = self.parse_at_root_query()?;
343 let query_span = self.toks_mut().span_from(query_start);
344 self.whitespace()?;
345 let children = self.with_children(Self::parse_statement)?.node;
346
347 AstAtRootRule {
348 query: Some(Spanned {
349 node: query,
350 span: query_span,
351 }),
352 body: children,
353 span: self.toks_mut().span_from(start),
354 }
355 } else if self.looking_at_children()? {
356 let children = self.with_children(Self::parse_statement)?.node;
357 AstAtRootRule {
358 query: None,
359 body: children,
360 span: self.toks_mut().span_from(start),
361 }
362 } else {
363 let child = self.parse_style_rule(None, None)?;
364 AstAtRootRule {
365 query: None,
366 body: vec![child],
367 span: self.toks_mut().span_from(start),
368 }
369 }))
370 }
371
372 fn parse_content_rule(&mut self, start: usize) -> SassResult<AstStmt> {
373 if !self.flags().in_mixin() {
374 return Err((
375 "@content is only allowed within mixin declarations.",
376 self.toks_mut().span_from(start),
377 )
378 .into());
379 }
380
381 self.whitespace()?;
382
383 let args = if self.toks_mut().next_char_is('(') {
384 self.parse_argument_invocation(true, false)?
385 } else {
386 ArgumentInvocation::empty(self.toks().current_span())
387 };
388
389 self.expect_statement_separator(Some("@content rule"))?;
390
391 self.flags_mut().set(ContextFlags::FOUND_CONTENT_RULE, true);
392
393 Ok(AstStmt::ContentRule(AstContentRule { args }))
394 }
395
396 fn parse_debug_rule(&mut self) -> SassResult<AstStmt> {
397 let value = self.parse_expression(None, None, None)?;
398 self.expect_statement_separator(Some("@debug rule"))?;
399
400 Ok(AstStmt::Debug(AstDebugRule {
401 value: value.node,
402 span: value.span,
403 }))
404 }
405
406 fn parse_each_rule(
407 &mut self,
408 child: fn(&mut Self) -> SassResult<AstStmt>,
409 ) -> SassResult<AstStmt> {
410 let was_in_control_directive = self.flags().in_control_flow();
411 self.flags_mut().set(ContextFlags::IN_CONTROL_FLOW, true);
412
413 let mut variables = vec![Identifier::from(self.parse_variable_name()?)];
414 self.whitespace()?;
415 while self.scan_char(',') {
416 self.whitespace()?;
417 variables.push(Identifier::from(self.parse_variable_name()?));
418 self.whitespace()?;
419 }
420
421 self.expect_identifier("in", false)?;
422 self.whitespace()?;
423
424 let list = self.parse_expression(None, None, None)?.node;
425
426 let body = self.with_children(child)?.node;
427
428 self.flags_mut()
429 .set(ContextFlags::IN_CONTROL_FLOW, was_in_control_directive);
430
431 Ok(AstStmt::Each(AstEach {
432 variables,
433 list,
434 body,
435 }))
436 }
437
438 fn parse_disallowed_at_rule(&mut self, start: usize) -> SassResult<AstStmt> {
439 self.almost_any_value(false)?;
440 Err((
441 "This at-rule is not allowed here.",
442 self.toks_mut().span_from(start),
443 )
444 .into())
445 }
446
447 fn parse_error_rule(&mut self) -> SassResult<AstStmt> {
448 let value = self.parse_expression(None, None, None)?;
449 self.expect_statement_separator(Some("@error rule"))?;
450 Ok(AstStmt::ErrorRule(AstErrorRule {
451 value: value.node,
452 span: value.span,
453 }))
454 }
455
456 fn parse_extend_rule(&mut self, start: usize) -> SassResult<AstStmt> {
457 if !self.flags().in_style_rule()
458 && !self.flags().in_mixin()
459 && !self.flags().in_content_block()
460 {
461 return Err((
462 "@extend may only be used within style rules.",
463 self.toks_mut().span_from(start),
464 )
465 .into());
466 }
467
468 let value = self.almost_any_value(false)?;
469
470 let is_optional = self.scan_char('!');
471
472 if is_optional {
473 self.expect_identifier("optional", false)?;
474 }
475
476 self.expect_statement_separator(Some("@extend rule"))?;
477
478 Ok(AstStmt::Extend(AstExtendRule {
479 value,
480 is_optional,
481 span: self.toks_mut().span_from(start),
482 }))
483 }
484
485 fn parse_for_rule(
486 &mut self,
487 child: fn(&mut Self) -> SassResult<AstStmt>,
488 ) -> SassResult<AstStmt> {
489 let was_in_control_directive = self.flags().in_control_flow();
490 self.flags_mut().set(ContextFlags::IN_CONTROL_FLOW, true);
491
492 let var_start = self.toks().cursor();
493 let variable = Spanned {
494 node: Identifier::from(self.parse_variable_name()?),
495 span: self.toks_mut().span_from(var_start),
496 };
497 self.whitespace()?;
498
499 self.expect_identifier("from", false)?;
500 self.whitespace()?;
501
502 let exclusive: Cell<Option<bool>> = Cell::new(None);
503
504 let from = self.parse_expression(
505 Some(&|parser| {
506 if !parser.looking_at_identifier() {
507 return Ok(false);
508 }
509 Ok(if parser.scan_identifier("to", false)? {
510 exclusive.set(Some(true));
511 true
512 } else if parser.scan_identifier("through", false)? {
513 exclusive.set(Some(false));
514 true
515 } else {
516 false
517 })
518 }),
519 None,
520 None,
521 )?;
522
523 let is_exclusive = match exclusive.get() {
524 Some(b) => b,
525 None => {
526 return Err((
527 "Expected \"to\" or \"through\".",
528 self.toks().current_span(),
529 )
530 .into())
531 }
532 };
533
534 self.whitespace()?;
535
536 let to = self.parse_expression(None, None, None)?;
537
538 let body = self.with_children(child)?.node;
539
540 self.flags_mut()
541 .set(ContextFlags::IN_CONTROL_FLOW, was_in_control_directive);
542
543 Ok(AstStmt::For(AstFor {
544 variable,
545 from,
546 to,
547 is_exclusive,
548 body,
549 }))
550 }
551
552 fn parse_function_rule(&mut self, start: usize) -> SassResult<AstStmt> {
553 let name_start = self.toks().cursor();
554 let name = self.parse_identifier(true, false)?;
555 let name_span = self.toks_mut().span_from(name_start);
556 self.whitespace()?;
557 let arguments = self.parse_argument_declaration()?;
558
559 if self.flags().in_mixin() || self.flags().in_content_block() {
560 return Err((
561 "Mixins may not contain function declarations.",
562 self.toks_mut().span_from(start),
563 )
564 .into());
565 } else if self.flags().in_control_flow() {
566 return Err((
567 "Functions may not be declared in control directives.",
568 self.toks_mut().span_from(start),
569 )
570 .into());
571 }
572
573 if RESERVED_IDENTIFIERS.contains(&unvendor(&name)) {
574 return Err(("Invalid function name.", self.toks_mut().span_from(start)).into());
575 }
576
577 self.whitespace()?;
578
579 let children = self.with_children(Self::function_child)?.node;
580
581 Ok(AstStmt::FunctionDecl(AstFunctionDecl {
582 name: Spanned {
583 node: Identifier::from(name),
584 span: name_span,
585 },
586 arguments,
587 body: children,
588 }))
589 }
590
591 fn parse_variable_declaration_with_namespace(&mut self) -> SassResult<AstVariableDecl> {
592 let start = self.toks().cursor();
593 let namespace = self.parse_identifier(false, false)?;
594 let namespace_span = self.toks_mut().span_from(start);
595 self.expect_char('.')?;
596 self.parse_variable_declaration_without_namespace(
597 Some(Spanned {
598 node: Identifier::from(namespace),
599 span: namespace_span,
600 }),
601 Some(start),
602 )
603 }
604
605 fn function_child(&mut self) -> SassResult<AstStmt> {
606 let start = self.toks().cursor();
607 if !self.toks_mut().next_char_is('@') {
608 match self.parse_variable_declaration_with_namespace() {
609 Ok(decl) => return Ok(AstStmt::VariableDecl(decl)),
610 Err(e) => {
611 self.toks_mut().set_cursor(start);
612 let stmt = match self.parse_declaration_or_style_rule() {
613 Ok(stmt) => stmt,
614 Err(..) => return Err(e),
615 };
616
617 let (is_style_rule, span) = match stmt {
618 AstStmt::RuleSet(ruleset) => (true, ruleset.span),
619 AstStmt::Style(style) => (false, style.span),
620 _ => unreachable!(),
621 };
622
623 return Err((
624 format!(
625 "@function rules may not contain {}.",
626 if is_style_rule {
627 "style rules"
628 } else {
629 "declarations"
630 }
631 ),
632 span,
633 )
634 .into());
635 }
636 }
637 }
638
639 return match self.plain_at_rule_name()?.as_str() {
640 "debug" => self.parse_debug_rule(),
641 "each" => self.parse_each_rule(Self::function_child),
642 "else" => self.parse_disallowed_at_rule(start),
643 "error" => self.parse_error_rule(),
644 "for" => self.parse_for_rule(Self::function_child),
645 "if" => self.parse_if_rule(Self::function_child),
646 "return" => self.parse_return_rule(),
647 "warn" => self.parse_warn_rule(),
648 "while" => self.parse_while_rule(Self::function_child),
649 _ => self.parse_disallowed_at_rule(start),
650 };
651 }
652
653 fn parse_if_rule(
654 &mut self,
655 child: fn(&mut Self) -> SassResult<AstStmt>,
656 ) -> SassResult<AstStmt> {
657 let if_indentation = self.current_indentation();
658
659 let was_in_control_directive = self.flags().in_control_flow();
660 self.flags_mut().set(ContextFlags::IN_CONTROL_FLOW, true);
661 let condition = self.parse_expression(None, None, None)?.node;
662 let body = self.parse_children(child)?;
663 self.whitespace_without_comments();
664
665 let mut clauses = vec![AstIfClause { condition, body }];
666
667 let mut last_clause: Option<Vec<AstStmt>> = None;
668
669 while self.scan_else(if_indentation)? {
670 self.whitespace()?;
671 if self.scan_identifier("if", false)? {
672 self.whitespace()?;
673 let condition = self.parse_expression(None, None, None)?.node;
674 let body = self.parse_children(child)?;
675 clauses.push(AstIfClause { condition, body });
676 } else {
677 last_clause = Some(self.parse_children(child)?);
678 break;
679 }
680 }
681
682 self.flags_mut()
683 .set(ContextFlags::IN_CONTROL_FLOW, was_in_control_directive);
684 self.whitespace_without_comments();
685
686 Ok(AstStmt::If(AstIf {
687 if_clauses: clauses,
688 else_clause: last_clause,
689 }))
690 }
691
692 fn try_parse_import_supports_function(&mut self) -> SassResult<Option<AstSupportsCondition>> {
693 if !self.looking_at_interpolated_identifier() {
694 return Ok(None);
695 }
696
697 let start = self.toks().cursor();
698 let name = self.parse_interpolated_identifier()?;
699 debug_assert!(name.as_plain() != Some("not"));
700
701 if !self.scan_char('(') {
702 self.toks_mut().set_cursor(start);
703 return Ok(None);
704 }
705
706 let value = self.parse_interpolated_declaration_value(true, true, true)?;
707 self.expect_char(')')?;
708
709 Ok(Some(AstSupportsCondition::Function { name, args: value }))
710 }
711
712 fn parse_import_supports_query(&mut self) -> SassResult<AstSupportsCondition> {
713 Ok(if self.scan_identifier("not", false)? {
714 self.whitespace()?;
715 AstSupportsCondition::Negation(Box::new(self.supports_condition_in_parens()?))
716 } else if self.toks_mut().next_char_is('(') {
717 self.parse_supports_condition()?
718 } else {
719 match self.try_parse_import_supports_function()? {
720 Some(function) => function,
721 None => {
722 let start = self.toks().cursor();
723 let name = self.parse_expression(None, None, None)?;
724 self.expect_char(':')?;
725 self.supports_declaration_value(name.node, start)?
726 }
727 }
728 })
729 }
730
731 fn try_import_modifiers(&mut self) -> SassResult<Option<Interpolation>> {
732 if !self.looking_at_interpolated_identifier() && !self.toks_mut().next_char_is('(') {
735 return Ok(None);
736 }
737
738 let mut buffer = Interpolation::new();
739
740 loop {
741 if self.looking_at_interpolated_identifier() {
742 if !buffer.is_empty() {
743 buffer.add_char(' ');
744 }
745
746 let identifier = self.parse_interpolated_identifier()?;
747 let name = identifier.as_plain().map(str::to_ascii_lowercase);
748 buffer.add_interpolation(identifier);
749
750 if name.as_deref() != Some("and") && self.scan_char('(') {
751 if name.as_deref() == Some("supports") {
752 let query = self.parse_import_supports_query()?;
753 let is_declaration =
754 matches!(query, AstSupportsCondition::Declaration { .. });
755
756 if !is_declaration {
757 buffer.add_char('(');
758 }
759
760 buffer.add_expr(AstExpr::Supports(Arc::new(query)).span(self.empty_span()));
761
762 if !is_declaration {
763 buffer.add_char(')');
764 }
765 } else {
766 buffer.add_char('(');
767 buffer.add_interpolation(
768 self.parse_interpolated_declaration_value(true, true, true)?,
769 );
770 buffer.add_char(')');
771 }
772
773 self.expect_char(')')?;
774 self.whitespace()?;
775 } else {
776 self.whitespace()?;
777 if self.scan_char(',') {
778 buffer.add_char(',');
779 buffer.add_char(' ');
780 buffer.add_interpolation(self.parse_media_query_list()?);
781 return Ok(Some(buffer));
782 }
783 }
784 } else if self.toks_mut().next_char_is('(') {
785 if !buffer.is_empty() {
786 buffer.add_char(' ');
787 }
788
789 buffer.add_interpolation(self.parse_media_query_list()?);
790 return Ok(Some(buffer));
791 } else {
792 return Ok(Some(buffer));
793 }
794 }
795 }
796
797 fn try_url_contents(&mut self, name: Option<&str>) -> SassResult<Option<Interpolation>> {
798 let start = self.toks().cursor();
799 if !self.scan_char('(') {
800 return Ok(None);
801 }
802 self.whitespace_without_comments();
803
804 let mut buffer = Interpolation::new();
807 buffer.add_string(name.unwrap_or("url").to_owned());
808 buffer.add_char('(');
809
810 while let Some(next) = self.toks().peek() {
811 match next.kind {
812 '\\' => buffer.add_string(self.parse_escape(false)?),
813 '!' | '%' | '&' | '*'..='~' | '\u{80}'..=char::MAX => {
814 self.toks_mut().next();
815 buffer.add_char(next.kind);
816 }
817 '#' => {
818 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) {
819 let interpolation = self.parse_single_interpolation()?;
820 buffer.add_interpolation(interpolation);
821 } else {
822 self.toks_mut().next();
823 buffer.add_char(next.kind);
824 }
825 }
826 ')' => {
827 self.toks_mut().next();
828 buffer.add_char(next.kind);
829 return Ok(Some(buffer));
830 }
831 ' ' | '\t' | '\n' | '\r' => {
832 self.whitespace_without_comments();
833 if !self.toks_mut().next_char_is(')') {
834 break;
835 }
836 }
837 _ => break,
838 }
839 }
840
841 self.toks_mut().set_cursor(start);
842
843 Ok(None)
844 }
845
846 fn parse_dynamic_url(&mut self) -> SassResult<AstExpr> {
847 let start = self.toks().cursor();
848 self.expect_identifier("url", false)?;
849
850 Ok(match self.try_url_contents(None)? {
851 Some(contents) => AstExpr::String(
852 StringExpr(contents, QuoteKind::None),
853 self.toks_mut().span_from(start),
854 ),
855 None => AstExpr::InterpolatedFunction(Arc::new(InterpolatedFunction {
856 name: Interpolation::new_plain("url".to_owned()),
857 arguments: self.parse_argument_invocation(false, false)?,
858 span: self.toks_mut().span_from(start),
859 })),
860 })
861 }
862
863 fn parse_import_argument(&mut self, start: usize) -> SassResult<AstImport> {
864 if self.toks_mut().next_char_is('u') || self.toks_mut().next_char_is('U') {
865 let url = self.parse_dynamic_url()?;
866 self.whitespace()?;
867 let modifiers = self.try_import_modifiers()?;
868 return Ok(AstImport::Plain(AstPlainCssImport {
869 url: Interpolation::new_with_expr(url.span(self.toks_mut().span_from(start))),
870 modifiers,
871 span: self.toks_mut().span_from(start),
872 }));
873 }
874
875 let start = self.toks().cursor();
876 let url = self.parse_string()?;
877 let raw_url = self.toks().raw_text(start);
878 self.whitespace()?;
879 let modifiers = self.try_import_modifiers()?;
880
881 let span = self.toks_mut().span_from(start);
882
883 if is_plain_css_import(&url) || modifiers.is_some() {
884 Ok(AstImport::Plain(AstPlainCssImport {
885 url: Interpolation::new_plain(raw_url),
886 modifiers,
887 span,
888 }))
889 } else {
890 Ok(AstImport::Sass(AstSassImport { url, span }))
892 }
893 }
894
895 fn parse_import_rule(&mut self, start: usize) -> SassResult<AstStmt> {
896 let mut imports = Vec::new();
897
898 loop {
899 self.whitespace()?;
900 let argument = self.parse_import_argument(self.toks().cursor())?;
901
902 if (self.flags().in_control_flow() || self.flags().in_mixin()) && argument.is_dynamic()
904 {
905 self.parse_disallowed_at_rule(start)?;
906 }
907
908 imports.push(argument);
909 self.whitespace()?;
910
911 if !self.scan_char(',') {
912 break;
913 }
914 }
915
916 Ok(AstStmt::ImportRule(AstImportRule { imports }))
917 }
918
919 fn parse_public_identifier(&mut self) -> SassResult<String> {
920 let start = self.toks().cursor();
921 let ident = self.parse_identifier(true, false)?;
922 Self::assert_public(&ident, self.toks_mut().span_from(start))?;
923
924 Ok(ident)
925 }
926
927 fn parse_include_rule(&mut self) -> SassResult<AstStmt> {
928 let mut namespace: Option<Spanned<Identifier>> = None;
929
930 let name_start = self.toks().cursor();
931 let mut name = self.parse_identifier(false, false)?;
932
933 if self.scan_char('.') {
934 let namespace_span = self.toks_mut().span_from(name_start);
935 namespace = Some(Spanned {
936 node: Identifier::from(name),
937 span: namespace_span,
938 });
939 name = self.parse_public_identifier()?;
940 } else {
941 name = name.replace('_', "-");
942 }
943
944 let name = Identifier::from(name);
945 let name_span = self.toks_mut().span_from(name_start);
946
947 self.whitespace()?;
948
949 let args = if self.toks_mut().next_char_is('(') {
950 self.parse_argument_invocation(true, false)?
951 } else {
952 ArgumentInvocation::empty(self.toks().current_span())
953 };
954
955 self.whitespace()?;
956
957 let content_args = if self.scan_identifier("using", false)? {
958 self.whitespace()?;
959 let args = self.parse_argument_declaration()?;
960 self.whitespace()?;
961 Some(args)
962 } else {
963 None
964 };
965
966 let mut content_block: Option<AstContentBlock> = None;
967
968 if content_args.is_some() || self.looking_at_children()? {
969 let content_args = content_args.unwrap_or_else(ArgumentDeclaration::empty);
970 let was_in_content_block = self.flags().in_content_block();
971 self.flags_mut().set(ContextFlags::IN_CONTENT_BLOCK, true);
972 let body = self.with_children(Self::parse_statement)?.node;
973 content_block = Some(AstContentBlock {
974 args: content_args,
975 body,
976 });
977 self.flags_mut()
978 .set(ContextFlags::IN_CONTENT_BLOCK, was_in_content_block);
979 } else {
980 self.expect_statement_separator(None)?;
981 }
982
983 Ok(AstStmt::Include(AstInclude {
984 namespace,
985 name: Spanned {
986 node: name,
987 span: name_span,
988 },
989 args,
990 content: content_block,
991 span: name_span,
992 }))
993 }
994
995 fn parse_media_rule(&mut self, start: usize) -> SassResult<AstStmt> {
996 let query_start = self.toks().cursor();
997 let query = self.parse_media_query_list()?;
998 let query_span = self.toks_mut().span_from(query_start);
999
1000 let body = self.with_children(Self::parse_statement)?.node;
1001
1002 Ok(AstStmt::Media(AstMedia {
1003 query,
1004 query_span,
1005 body,
1006 span: self.toks_mut().span_from(start),
1007 }))
1008 }
1009
1010 fn parse_interpolated_string(&mut self) -> SassResult<Spanned<StringExpr>> {
1011 let start = self.toks().cursor();
1012 let quote = match self.toks_mut().next() {
1013 Some(Token {
1014 kind: kind @ ('"' | '\''),
1015 ..
1016 }) => kind,
1017 Some(..) | None => unreachable!("Expected string."),
1018 };
1019
1020 let mut buffer = Interpolation::new();
1021
1022 let mut found_match = false;
1023
1024 while let Some(next) = self.toks().peek() {
1025 match next.kind {
1026 c if c == quote => {
1027 self.toks_mut().next();
1028 found_match = true;
1029 break;
1030 }
1031 '\n' => break,
1032 '\\' => {
1033 match self.toks().peek_n(1) {
1034 Some(Token { kind: '\n', .. }) => {
1037 self.toks_mut().next();
1038 self.toks_mut().next();
1039 }
1040 _ => buffer.add_char(self.consume_escaped_char()?),
1041 }
1042 }
1043 '#' => {
1044 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) {
1045 buffer.add_interpolation(self.parse_single_interpolation()?);
1046 } else {
1047 self.toks_mut().next();
1048 buffer.add_char(next.kind);
1049 }
1050 }
1051 _ => {
1052 buffer.add_char(next.kind);
1053 self.toks_mut().next();
1054 }
1055 }
1056 }
1057
1058 if !found_match {
1059 return Err((
1060 format!("Expected {quote}.", quote = quote),
1061 self.toks().current_span(),
1062 )
1063 .into());
1064 }
1065
1066 Ok(Spanned {
1067 node: StringExpr(buffer, QuoteKind::Quoted),
1068 span: self.toks_mut().span_from(start),
1069 })
1070 }
1071
1072 fn parse_return_rule(&mut self) -> SassResult<AstStmt> {
1073 let value = self.parse_expression(None, None, None)?;
1074 self.expect_statement_separator(None)?;
1075 Ok(AstStmt::Return(AstReturn {
1076 val: value.node,
1077 span: value.span,
1078 }))
1079 }
1080
1081 fn parse_mixin_rule(&mut self, start: usize) -> SassResult<AstStmt> {
1082 let name = Identifier::from(self.parse_identifier(true, false)?);
1083 self.whitespace()?;
1084 let args = if self.toks_mut().next_char_is('(') {
1085 self.parse_argument_declaration()?
1086 } else {
1087 ArgumentDeclaration::empty()
1088 };
1089
1090 if self.flags().in_mixin() || self.flags().in_content_block() {
1091 return Err((
1092 "Mixins may not contain mixin declarations.",
1093 self.toks_mut().span_from(start),
1094 )
1095 .into());
1096 } else if self.flags().in_control_flow() {
1097 return Err((
1098 "Mixins may not be declared in control directives.",
1099 self.toks_mut().span_from(start),
1100 )
1101 .into());
1102 }
1103
1104 self.whitespace()?;
1105
1106 let old_found_content_rule = self.flags().found_content_rule();
1107 self.flags_mut()
1108 .set(ContextFlags::FOUND_CONTENT_RULE, false);
1109 self.flags_mut().set(ContextFlags::IN_MIXIN, true);
1110
1111 let body = self.with_children(Self::parse_statement)?.node;
1112
1113 let has_content = self.flags_mut().found_content_rule();
1114
1115 self.flags_mut()
1116 .set(ContextFlags::FOUND_CONTENT_RULE, old_found_content_rule);
1117 self.flags_mut().set(ContextFlags::IN_MIXIN, false);
1118
1119 Ok(AstStmt::Mixin(AstMixin {
1120 name,
1121 args,
1122 body,
1123 has_content,
1124 }))
1125 }
1126
1127 fn _parse_moz_document_rule(&mut self, _name: Interpolation) -> SassResult<AstStmt> {
1128 todo!("special cased @-moz-document not yet implemented")
1129 }
1130
1131 fn unknown_at_rule(&mut self, name: Interpolation, start: usize) -> SassResult<AstStmt> {
1132 let was_in_unknown_at_rule = self.flags().in_unknown_at_rule();
1133 self.flags_mut().set(ContextFlags::IN_UNKNOWN_AT_RULE, true);
1134
1135 let value: Option<Interpolation> =
1136 if !self.toks_mut().next_char_is('!') && !self.at_end_of_statement() {
1137 Some(self.almost_any_value(false)?)
1138 } else {
1139 None
1140 };
1141
1142 let children = if self.looking_at_children()? {
1143 Some(self.with_children(Self::parse_statement)?.node)
1144 } else {
1145 self.expect_statement_separator(None)?;
1146 None
1147 };
1148
1149 self.flags_mut()
1150 .set(ContextFlags::IN_UNKNOWN_AT_RULE, was_in_unknown_at_rule);
1151
1152 Ok(AstStmt::UnknownAtRule(AstUnknownAtRule {
1153 name,
1154 value,
1155 body: children,
1156 span: self.toks_mut().span_from(start),
1157 }))
1158 }
1159
1160 fn try_supports_operation(
1161 &mut self,
1162 interpolation: &Interpolation,
1163 _start: usize,
1164 ) -> SassResult<Option<AstSupportsCondition>> {
1165 if interpolation.contents.len() != 1 {
1166 return Ok(None);
1167 }
1168
1169 let expression = match interpolation.contents.first() {
1170 Some(InterpolationPart::Expr(e)) => e,
1171 Some(InterpolationPart::String(..)) => return Ok(None),
1172 None => unreachable!(),
1173 };
1174
1175 let before_whitespace = self.toks().cursor();
1176 self.whitespace()?;
1177
1178 let mut operation: Option<AstSupportsCondition> = None;
1179 let mut operator: Option<String> = None;
1180
1181 while self.looking_at_identifier() {
1182 if let Some(operator) = &operator {
1183 self.expect_identifier(operator, false)?;
1184 } else if self.scan_identifier("and", false)? {
1185 operator = Some("and".to_owned());
1186 } else if self.scan_identifier("or", false)? {
1187 operator = Some("or".to_owned());
1188 } else {
1189 self.toks_mut().set_cursor(before_whitespace);
1190 return Ok(None);
1191 }
1192
1193 self.whitespace()?;
1194
1195 let right = self.supports_condition_in_parens()?;
1196 operation = Some(AstSupportsCondition::Operation {
1197 left: Box::new(operation.unwrap_or_else(|| {
1198 AstSupportsCondition::Interpolation(expression.clone().node)
1199 })),
1200 operator: operator.clone(),
1201 right: Box::new(right),
1202 });
1203 self.whitespace()?;
1204 }
1205
1206 Ok(operation)
1207 }
1208
1209 fn supports_declaration_value(
1210 &mut self,
1211 name: AstExpr,
1212 start: usize,
1213 ) -> SassResult<AstSupportsCondition> {
1214 let value = match &name {
1215 AstExpr::String(StringExpr(text, QuoteKind::None), ..)
1216 if text.initial_plain().starts_with("--") =>
1217 {
1218 let text = self.parse_interpolated_declaration_value(false, false, true)?;
1219 AstExpr::String(
1220 StringExpr(text, QuoteKind::None),
1221 self.toks_mut().span_from(start),
1222 )
1223 }
1224 _ => {
1225 self.whitespace()?;
1226 self.parse_expression(None, None, None)?.node
1227 }
1228 };
1229
1230 Ok(AstSupportsCondition::Declaration { name, value })
1231 }
1232
1233 fn supports_condition_in_parens(&mut self) -> SassResult<AstSupportsCondition> {
1234 let start = self.toks().cursor();
1235
1236 if self.looking_at_interpolated_identifier() {
1237 let identifier = self.parse_interpolated_identifier()?;
1238 let ident_span = self.toks_mut().span_from(start);
1239
1240 if identifier.as_plain().unwrap_or("").to_ascii_lowercase() == "not" {
1241 return Err((r#""not" is not a valid identifier here."#, ident_span).into());
1242 }
1243
1244 if self.scan_char('(') {
1245 let arguments = self.parse_interpolated_declaration_value(true, true, true)?;
1246 self.expect_char(')')?;
1247 return Ok(AstSupportsCondition::Function {
1248 name: identifier,
1249 args: arguments,
1250 });
1251 } else if identifier.contents.len() != 1
1252 || !matches!(
1253 identifier.contents.first(),
1254 Some(InterpolationPart::Expr(..))
1255 )
1256 {
1257 return Err(("Expected @supports condition.", ident_span).into());
1258 } else {
1259 match identifier.contents.first() {
1260 Some(InterpolationPart::Expr(e)) => {
1261 return Ok(AstSupportsCondition::Interpolation(e.clone().node))
1262 }
1263 _ => unreachable!(),
1264 }
1265 }
1266 }
1267
1268 self.expect_char('(')?;
1269 self.whitespace()?;
1270
1271 if self.scan_identifier("not", false)? {
1272 self.whitespace()?;
1273 let condition = self.supports_condition_in_parens()?;
1274 self.expect_char(')')?;
1275 return Ok(AstSupportsCondition::Negation(Box::new(condition)));
1276 } else if self.toks_mut().next_char_is('(') {
1277 let condition = self.parse_supports_condition()?;
1278 self.expect_char(')')?;
1279 return Ok(condition);
1280 }
1281
1282 let name: AstExpr;
1298 let name_start = self.toks().cursor();
1299 let was_in_parens = self.flags().in_parens();
1300
1301 let expr = self.parse_expression(None, None, None);
1302 let found_colon = self.expect_char(':');
1303 match (expr, found_colon) {
1304 (Ok(val), Ok(..)) => {
1305 name = val.node;
1306 }
1307 (Ok(..), Err(e)) | (Err(e), Ok(..)) | (Err(e), Err(..)) => {
1308 self.toks_mut().set_cursor(name_start);
1309 self.flags_mut().set(ContextFlags::IN_PARENS, was_in_parens);
1310
1311 let identifier = self.parse_interpolated_identifier()?;
1312
1313 if let Some(operation) = self.try_supports_operation(&identifier, name_start)? {
1315 self.expect_char(')')?;
1316 return Ok(operation);
1317 }
1318
1319 let mut contents = Interpolation::new();
1324 contents.add_interpolation(identifier);
1325 contents.add_interpolation(
1326 self.parse_interpolated_declaration_value(true, true, false)?,
1327 );
1328
1329 if self.toks_mut().next_char_is(':') {
1330 return Err(e);
1331 }
1332
1333 self.expect_char(')')?;
1334
1335 return Ok(AstSupportsCondition::Anything { contents });
1336 }
1337 }
1338
1339 let declaration = self.supports_declaration_value(name, start)?;
1340 self.expect_char(')')?;
1341
1342 Ok(declaration)
1343 }
1344
1345 fn parse_supports_condition(&mut self) -> SassResult<AstSupportsCondition> {
1346 if self.scan_identifier("not", false)? {
1347 self.whitespace()?;
1348 return Ok(AstSupportsCondition::Negation(Box::new(
1349 self.supports_condition_in_parens()?,
1350 )));
1351 }
1352
1353 let mut condition = self.supports_condition_in_parens()?;
1354 self.whitespace()?;
1355
1356 let mut operator: Option<String> = None;
1357
1358 while self.looking_at_identifier() {
1359 if let Some(operator) = &operator {
1360 self.expect_identifier(operator, false)?;
1361 } else if self.scan_identifier("or", false)? {
1362 operator = Some("or".to_owned());
1363 } else {
1364 self.expect_identifier("and", false)?;
1365 operator = Some("and".to_owned());
1366 }
1367
1368 self.whitespace()?;
1369 let right = self.supports_condition_in_parens()?;
1370 condition = AstSupportsCondition::Operation {
1371 left: Box::new(condition),
1372 operator: operator.clone(),
1373 right: Box::new(right),
1374 };
1375 self.whitespace()?;
1376 }
1377
1378 Ok(condition)
1379 }
1380
1381 fn parse_supports_rule(&mut self) -> SassResult<AstStmt> {
1382 let condition = self.parse_supports_condition()?;
1383 self.whitespace()?;
1384 let children = self.with_children(Self::parse_statement)?;
1385
1386 Ok(AstStmt::Supports(AstSupportsRule {
1387 condition,
1388 body: children.node,
1389 span: children.span,
1390 }))
1391 }
1392
1393 fn parse_warn_rule(&mut self) -> SassResult<AstStmt> {
1394 let value = self.parse_expression(None, None, None)?;
1395 self.expect_statement_separator(Some("@warn rule"))?;
1396 Ok(AstStmt::Warn(AstWarn {
1397 value: value.node,
1398 span: value.span,
1399 }))
1400 }
1401
1402 fn parse_while_rule(
1403 &mut self,
1404 child: fn(&mut Self) -> SassResult<AstStmt>,
1405 ) -> SassResult<AstStmt> {
1406 let was_in_control_directive = self.flags().in_control_flow();
1407 self.flags_mut().set(ContextFlags::IN_CONTROL_FLOW, true);
1408
1409 let condition = self.parse_expression(None, None, None)?.node;
1410
1411 let body = self.with_children(child)?.node;
1412
1413 self.flags_mut()
1414 .set(ContextFlags::IN_CONTROL_FLOW, was_in_control_directive);
1415
1416 Ok(AstStmt::While(AstWhile { condition, body }))
1417 }
1418 fn parse_forward_rule(&mut self, start: usize) -> SassResult<AstStmt> {
1419 let url = PathBuf::from(self.parse_url_string()?);
1420 self.whitespace()?;
1421
1422 let prefix = if self.scan_identifier("as", false)? {
1423 self.whitespace()?;
1424 let prefix = self.parse_identifier(true, false)?;
1425 self.expect_char('*')?;
1426 self.whitespace()?;
1427 Some(prefix)
1428 } else {
1429 None
1430 };
1431
1432 let mut shown_mixins_and_functions: Option<HashSet<Identifier>> = None;
1433 let mut shown_variables: Option<HashSet<Identifier>> = None;
1434 let mut hidden_mixins_and_functions: Option<HashSet<Identifier>> = None;
1435 let mut hidden_variables: Option<HashSet<Identifier>> = None;
1436
1437 if self.scan_identifier("show", false)? {
1438 let members = self.parse_member_list()?;
1439 shown_mixins_and_functions = Some(members.0);
1440 shown_variables = Some(members.1);
1441 } else if self.scan_identifier("hide", false)? {
1442 let members = self.parse_member_list()?;
1443 hidden_mixins_and_functions = Some(members.0);
1444 hidden_variables = Some(members.1);
1445 }
1446
1447 let config = self.parse_configuration(true)?;
1448
1449 self.expect_statement_separator(Some("@forward rule"))?;
1450 let span = self.toks_mut().span_from(start);
1451
1452 if !self.flags().is_use_allowed() {
1453 return Err((
1454 "@forward rules must be written before any other rules.",
1455 span,
1456 )
1457 .into());
1458 }
1459
1460 Ok(AstStmt::Forward(
1461 if let (Some(shown_mixins_and_functions), Some(shown_variables)) =
1462 (shown_mixins_and_functions, shown_variables)
1463 {
1464 AstForwardRule::show(
1465 url,
1466 shown_mixins_and_functions,
1467 shown_variables,
1468 prefix,
1469 config,
1470 span,
1471 )
1472 } else if let (Some(hidden_mixins_and_functions), Some(hidden_variables)) =
1473 (hidden_mixins_and_functions, hidden_variables)
1474 {
1475 AstForwardRule::hide(
1476 url,
1477 hidden_mixins_and_functions,
1478 hidden_variables,
1479 prefix,
1480 config,
1481 span,
1482 )
1483 } else {
1484 AstForwardRule::new(url, prefix, config, span)
1485 },
1486 ))
1487 }
1488
1489 fn parse_member_list(&mut self) -> SassResult<(HashSet<Identifier>, HashSet<Identifier>)> {
1490 let mut identifiers = HashSet::new();
1491 let mut variables = HashSet::new();
1492
1493 loop {
1494 self.whitespace()?;
1495
1496 if self.toks_mut().next_char_is('$') {
1498 variables.insert(Identifier::from(self.parse_variable_name()?));
1499 } else {
1500 identifiers.insert(Identifier::from(self.parse_identifier(true, false)?));
1501 }
1502
1503 self.whitespace()?;
1504
1505 if !self.scan_char(',') {
1506 break;
1507 }
1508 }
1509
1510 Ok((identifiers, variables))
1511 }
1512
1513 fn parse_url_string(&mut self) -> SassResult<String> {
1514 self.parse_string()
1516 }
1517
1518 fn use_namespace(
1519 &mut self,
1520 url: &Path,
1521 _start: usize,
1522 url_span: Span,
1523 ) -> SassResult<Option<String>> {
1524 if self.scan_identifier("as", false)? {
1525 self.whitespace()?;
1526 return Ok(if self.scan_char('*') {
1527 None
1528 } else {
1529 Some(self.parse_identifier(false, false)?)
1530 });
1531 }
1532
1533 let base_name = url
1534 .file_name()
1535 .map_or_else(OsString::new, ToOwned::to_owned);
1536 let base_name = base_name.to_string_lossy();
1537 let dot = base_name.find('.');
1538
1539 let start = if base_name.starts_with('_') { 1 } else { 0 };
1540 let end = dot.unwrap_or(base_name.len());
1541 let namespace = if url.to_string_lossy().starts_with("sass:") {
1542 return Ok(Some(url.to_string_lossy().into_owned()));
1543 } else {
1544 &base_name[start..end]
1545 };
1546
1547 let mut toks = Lexer::new_from_string(namespace, url_span);
1548
1549 let identifier = if namespace.is_empty() {
1552 Err(("", self.empty_span()).into())
1553 } else {
1554 mem::swap(self.toks_mut(), &mut toks);
1555 let ident = self.parse_identifier(false, false);
1556 mem::swap(self.toks_mut(), &mut toks);
1557 ident
1558 };
1559
1560 match (identifier, toks.peek().is_none()) {
1561 (Ok(i), true) => Ok(Some(i)),
1562 _ => {
1563 Err((
1564 format!(
1565 "The default namespace \"{namespace}\" is not a valid Sass identifier.\n\nRecommendation: add an \"as\" clause to define an explicit namespace.",
1566 namespace = namespace
1567 ),
1568 self.toks_mut().span_from(start)
1569 ).into())
1570 }
1571 }
1572 }
1573
1574 fn parse_configuration(
1575 &mut self,
1576 allow_guarded: bool,
1578 ) -> SassResult<Option<Vec<ConfiguredVariable>>> {
1579 if !self.scan_identifier("with", false)? {
1580 return Ok(None);
1581 }
1582
1583 let mut variable_names = HashSet::new();
1584 let mut configuration = Vec::new();
1585 self.whitespace()?;
1586 self.expect_char('(')?;
1587
1588 loop {
1589 self.whitespace()?;
1590 let var_start = self.toks().cursor();
1591 let name = Identifier::from(self.parse_variable_name()?);
1592 let name_span = self.toks_mut().span_from(var_start);
1593 self.whitespace()?;
1594 self.expect_char(':')?;
1595 self.whitespace()?;
1596 let expr = self.parse_expression_until_comma(false)?;
1597
1598 let mut is_guarded = false;
1599 let flag_start = self.toks().cursor();
1600 if allow_guarded && self.scan_char('!') {
1601 let flag = self.parse_identifier(false, false)?;
1602 if flag == "default" {
1603 is_guarded = true;
1604 self.whitespace()?;
1605 } else {
1606 return Err(
1607 ("Invalid flag name.", self.toks_mut().span_from(flag_start)).into(),
1608 );
1609 }
1610 }
1611
1612 let span = self.toks_mut().span_from(var_start);
1613 if variable_names.contains(&name) {
1614 return Err(("The same variable may only be configured once.", span).into());
1615 }
1616
1617 variable_names.insert(name);
1618 configuration.push(ConfiguredVariable {
1619 name: Spanned {
1620 node: name,
1621 span: name_span,
1622 },
1623 expr,
1624 is_guarded,
1625 });
1626
1627 if !self.scan_char(',') {
1628 break;
1629 }
1630 self.whitespace()?;
1631 if !self.looking_at_expression() {
1632 break;
1633 }
1634 }
1635
1636 self.expect_char(')')?;
1637
1638 Ok(Some(configuration))
1639 }
1640
1641 fn parse_use_rule(&mut self, start: usize) -> SassResult<AstStmt> {
1642 let url_start = self.toks().cursor();
1643 let url = self.parse_url_string()?;
1644 let url_span = self.toks().span_from(url_start);
1645 self.whitespace()?;
1646
1647 let path = PathBuf::from(url);
1648
1649 let namespace = self.use_namespace(path.as_ref(), start, url_span)?;
1650 self.whitespace()?;
1651 let configuration = self.parse_configuration(false)?;
1652
1653 self.expect_statement_separator(Some("@use rule"))?;
1654
1655 let span = self.toks_mut().span_from(start);
1656
1657 if !self.flags().is_use_allowed() {
1658 return Err((
1659 "@use rules must be written before any other rules.",
1660 self.toks_mut().span_from(start),
1661 )
1662 .into());
1663 }
1664
1665 self.expect_statement_separator(Some("@use rule"))?;
1666
1667 Ok(AstStmt::Use(AstUseRule {
1668 url: path,
1669 namespace,
1670 configuration: configuration.unwrap_or_default(),
1671 span,
1672 }))
1673 }
1674
1675 fn parse_at_rule(
1676 &mut self,
1677 child: fn(&mut Self) -> SassResult<AstStmt>,
1678 ) -> SassResult<AstStmt> {
1679 let start = self.toks().cursor();
1680
1681 self.expect_char('@')?;
1682 let name = self.parse_interpolated_identifier()?;
1683 self.whitespace()?;
1684
1685 let was_use_allowed = self.flags().is_use_allowed();
1690 self.flags_mut().set(ContextFlags::IS_USE_ALLOWED, false);
1691
1692 match name.as_plain() {
1693 Some("at-root") => self.parse_at_root_rule(start),
1694 Some("content") => self.parse_content_rule(start),
1695 Some("debug") => self.parse_debug_rule(),
1696 Some("each") => self.parse_each_rule(child),
1697 Some("else") | Some("return") => self.parse_disallowed_at_rule(start),
1698 Some("error") => self.parse_error_rule(),
1699 Some("extend") => self.parse_extend_rule(start),
1700 Some("for") => self.parse_for_rule(child),
1701 Some("forward") => {
1702 self.flags_mut()
1703 .set(ContextFlags::IS_USE_ALLOWED, was_use_allowed);
1704 self.parse_forward_rule(start)
1708 }
1709 Some("function") => self.parse_function_rule(start),
1710 Some("if") => self.parse_if_rule(child),
1711 Some("import") => self.parse_import_rule(start),
1712 Some("include") => self.parse_include_rule(),
1713 Some("media") => self.parse_media_rule(start),
1714 Some("mixin") => self.parse_mixin_rule(start),
1715 Some("supports") => self.parse_supports_rule(),
1718 Some("use") => {
1719 self.flags_mut()
1720 .set(ContextFlags::IS_USE_ALLOWED, was_use_allowed);
1721 self.parse_use_rule(start)
1725 }
1726 Some("warn") => self.parse_warn_rule(),
1727 Some("while") => self.parse_while_rule(child),
1728 Some(..) | None => self.unknown_at_rule(name, start),
1729 }
1730 }
1731
1732 fn parse_statement(&mut self) -> SassResult<AstStmt> {
1733 match self.toks().peek() {
1734 Some(Token { kind: '@', .. }) => self.parse_at_rule(Self::parse_statement),
1735 Some(Token { kind: '+', .. }) => {
1736 if !self.is_indented() {
1737 return self.parse_style_rule(None, None);
1738 }
1739
1740 let start = self.toks().cursor();
1741
1742 self.toks_mut().next();
1743
1744 if !self.looking_at_identifier() {
1745 self.toks_mut().set_cursor(start);
1746 return self.parse_style_rule(None, None);
1747 }
1748
1749 self.flags_mut().set(ContextFlags::IS_USE_ALLOWED, false);
1750 self.parse_include_rule()
1751 }
1752 Some(Token { kind: '=', .. }) => {
1753 if !self.is_indented() {
1754 return self.parse_style_rule(None, None);
1755 }
1756
1757 self.flags_mut().set(ContextFlags::IS_USE_ALLOWED, false);
1758 let start = self.toks().cursor();
1759 self.toks_mut().next();
1760 self.whitespace()?;
1761 self.parse_mixin_rule(start)
1762 }
1763 Some(Token { kind: '}', .. }) => {
1764 Err(("unmatched \"}\".", self.toks().current_span()).into())
1765 }
1766 _ => {
1767 if self.flags().in_style_rule()
1768 || self.flags().in_unknown_at_rule()
1769 || self.flags().in_mixin()
1770 || self.flags().in_content_block()
1771 {
1772 self.parse_declaration_or_style_rule()
1773 } else {
1774 self.parse_variable_declaration_or_style_rule()
1775 }
1776 }
1777 }
1778 }
1779
1780 fn parse_declaration_or_style_rule(&mut self) -> SassResult<AstStmt> {
1781 let start = self.toks().cursor();
1782
1783 if self.is_plain_css() && self.flags().in_style_rule() && !self.flags().in_unknown_at_rule()
1784 {
1785 return self.parse_property_or_variable_declaration(true);
1786 }
1787
1788 if self.is_indented() && self.scan_char('\\') {
1792 return self.parse_style_rule(None, None);
1793 };
1794
1795 match self.parse_declaration_or_buffer()? {
1796 DeclarationOrBuffer::Stmt(s) => Ok(s),
1797 DeclarationOrBuffer::Buffer(existing_buffer) => {
1798 self.parse_style_rule(Some(existing_buffer), Some(start))
1799 }
1800 }
1801 }
1802
1803 fn parse_property_or_variable_declaration(
1804 &mut self,
1805 parse_custom_properties: bool,
1807 ) -> SassResult<AstStmt> {
1808 let start = self.toks().cursor();
1809
1810 let name = if matches!(
1811 self.toks().peek(),
1812 Some(Token {
1813 kind: ':' | '*' | '.',
1814 ..
1815 })
1816 ) || (matches!(self.toks().peek(), Some(Token { kind: '#', .. }))
1817 && !matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })))
1818 {
1819 let mut name_buffer = Interpolation::new();
1822 name_buffer.add_char(self.toks_mut().next().unwrap().kind);
1823 name_buffer.add_string(self.raw_text(Self::whitespace));
1824 name_buffer.add_interpolation(self.parse_interpolated_identifier()?);
1825 name_buffer
1826 } else if !self.is_plain_css() {
1827 match self.parse_variable_declaration_or_interpolation()? {
1828 VariableDeclOrInterpolation::Interpolation(interpolation) => interpolation,
1829 VariableDeclOrInterpolation::VariableDecl(decl) => {
1830 return Ok(AstStmt::VariableDecl(decl))
1831 }
1832 }
1833 } else {
1834 self.parse_interpolated_identifier()?
1835 };
1836
1837 self.whitespace()?;
1838 self.expect_char(':')?;
1839
1840 if parse_custom_properties && name.initial_plain().starts_with("--") {
1841 let interpolation = self.parse_interpolated_declaration_value(false, false, true)?;
1842 let value_span = self.toks_mut().span_from(start);
1843 let value = AstExpr::String(StringExpr(interpolation, QuoteKind::None), value_span)
1844 .span(value_span);
1845 self.expect_statement_separator(Some("custom property"))?;
1846 return Ok(AstStmt::Style(AstStyle {
1847 name,
1848 value: Some(value),
1849 body: Vec::new(),
1850 span: value_span,
1851 }));
1852 }
1853
1854 self.whitespace()?;
1855
1856 if self.looking_at_children()? {
1857 if self.is_plain_css() {
1858 return Err((
1859 "Nested declarations aren't allowed in plain CSS.",
1860 self.toks().current_span(),
1861 )
1862 .into());
1863 }
1864
1865 if name.initial_plain().starts_with("--") {
1866 return Err((
1867 "Declarations whose names begin with \"--\" may not be nested",
1868 self.toks_mut().span_from(start),
1869 )
1870 .into());
1871 }
1872
1873 let children = self.with_children(Self::parse_declaration_child)?.node;
1874
1875 return Ok(AstStmt::Style(AstStyle {
1876 name,
1877 value: None,
1878 body: children,
1879 span: self.toks_mut().span_from(start),
1880 }));
1881 }
1882
1883 let value = self.parse_expression(None, None, None)?;
1884 if self.looking_at_children()? {
1885 if self.is_plain_css() {
1886 return Err((
1887 "Nested declarations aren't allowed in plain CSS.",
1888 self.toks().current_span(),
1889 )
1890 .into());
1891 }
1892
1893 if name.initial_plain().starts_with("--") && !matches!(value.node, AstExpr::String(..))
1894 {
1895 return Err((
1896 "Declarations whose names begin with \"--\" may not be nested",
1897 self.toks_mut().span_from(start),
1898 )
1899 .into());
1900 }
1901
1902 let children = self.with_children(Self::parse_declaration_child)?.node;
1903
1904 Ok(AstStmt::Style(AstStyle {
1905 name,
1906 value: Some(value),
1907 body: children,
1908 span: self.toks_mut().span_from(start),
1909 }))
1910 } else {
1911 self.expect_statement_separator(None)?;
1912 Ok(AstStmt::Style(AstStyle {
1913 name,
1914 value: Some(value),
1915 body: Vec::new(),
1916 span: self.toks_mut().span_from(start),
1917 }))
1918 }
1919 }
1920
1921 fn parse_single_interpolation(&mut self) -> SassResult<Interpolation> {
1922 self.expect_char('#')?;
1923 self.expect_char('{')?;
1924 self.whitespace()?;
1925 let contents = self.parse_expression(None, None, None)?;
1926 self.expect_char('}')?;
1927
1928 if self.is_plain_css() {
1929 return Err(("Interpolation isn't allowed in plain CSS.", contents.span).into());
1930 }
1931
1932 let mut interpolation = Interpolation::new();
1933 interpolation
1934 .contents
1935 .push(InterpolationPart::Expr(contents));
1936
1937 Ok(interpolation)
1938 }
1939
1940 fn parse_interpolated_identifier_body(&mut self, buffer: &mut Interpolation) -> SassResult<()> {
1941 while let Some(next) = self.toks().peek() {
1942 match next.kind {
1943 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\u{80}'..=std::char::MAX => {
1944 buffer.add_char(next.kind);
1945 self.toks_mut().next();
1946 }
1947 '\\' => {
1948 buffer.add_string(self.parse_escape(false)?);
1949 }
1950 '#' if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) => {
1951 buffer.add_interpolation(self.parse_single_interpolation()?);
1952 }
1953 _ => break,
1954 }
1955 }
1956
1957 Ok(())
1958 }
1959
1960 fn parse_interpolated_identifier(&mut self) -> SassResult<Interpolation> {
1961 let mut buffer = Interpolation::new();
1962
1963 if self.scan_char('-') {
1964 buffer.add_char('-');
1965
1966 if self.scan_char('-') {
1967 buffer.add_char('-');
1968 self.parse_interpolated_identifier_body(&mut buffer)?;
1969 return Ok(buffer);
1970 }
1971 }
1972
1973 match self.toks().peek() {
1974 Some(tok) if is_name_start(tok.kind) => {
1975 buffer.add_char(tok.kind);
1976 self.toks_mut().next();
1977 }
1978 Some(Token { kind: '\\', .. }) => {
1979 buffer.add_string(self.parse_escape(true)?);
1980 }
1981 Some(Token { kind: '#', .. })
1982 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) =>
1983 {
1984 buffer.add_interpolation(self.parse_single_interpolation()?);
1985 }
1986 Some(..) | None => {
1987 return Err(("Expected identifier.", self.toks().current_span()).into())
1988 }
1989 }
1990
1991 self.parse_interpolated_identifier_body(&mut buffer)?;
1992
1993 Ok(buffer)
1994 }
1995
1996 fn looking_at_interpolated_identifier(&mut self) -> bool {
1997 let first = match self.toks().peek() {
1998 Some(Token { kind: '\\', .. }) => return true,
1999 Some(Token { kind: '#', .. }) => {
2000 return matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. }))
2001 }
2002 Some(Token { kind, .. }) if is_name_start(kind) => return true,
2003 Some(tok) => tok,
2004 None => return false,
2005 };
2006
2007 if first.kind != '-' {
2008 return false;
2009 }
2010
2011 match self.toks().peek_n(1) {
2012 Some(Token { kind: '#', .. }) => {
2013 matches!(self.toks().peek_n(2), Some(Token { kind: '{', .. }))
2014 }
2015 Some(Token {
2016 kind: '\\' | '-', ..
2017 }) => true,
2018 Some(Token { kind, .. }) => is_name_start(kind),
2019 None => false,
2020 }
2021 }
2022
2023 fn parse_loud_comment(&mut self) -> SassResult<AstLoudComment> {
2024 let start = self.toks().cursor();
2025 self.expect_char('/')?;
2026 self.expect_char('*')?;
2027
2028 let mut buffer = Interpolation::new_plain("/*".to_owned());
2029
2030 while let Some(tok) = self.toks().peek() {
2031 match tok.kind {
2032 '#' => {
2033 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) {
2034 buffer.add_interpolation(self.parse_single_interpolation()?);
2035 } else {
2036 self.toks_mut().next();
2037 buffer.add_char(tok.kind);
2038 }
2039 }
2040 '*' => {
2041 self.toks_mut().next();
2042 buffer.add_char(tok.kind);
2043
2044 if self.scan_char('/') {
2045 buffer.add_char('/');
2046
2047 return Ok(AstLoudComment {
2048 text: buffer,
2049 span: self.toks_mut().span_from(start),
2050 });
2051 }
2052 }
2053 '\r' => {
2054 self.toks_mut().next();
2055 if !self.toks_mut().next_char_is('\n') {
2057 buffer.add_char('\n');
2058 }
2059 }
2060 _ => {
2061 buffer.add_char(tok.kind);
2062 self.toks_mut().next();
2063 }
2064 }
2065 }
2066
2067 Err(("expected more input.", self.toks().current_span()).into())
2068 }
2069
2070 fn parse_interpolated_declaration_value(
2071 &mut self,
2072 allow_semicolon: bool,
2074 allow_empty: bool,
2076 allow_colon: bool,
2078 ) -> SassResult<Interpolation> {
2079 let mut buffer = Interpolation::new();
2080
2081 let mut brackets = Vec::new();
2082 let mut wrote_newline = false;
2083
2084 while let Some(tok) = self.toks().peek() {
2085 match tok.kind {
2086 '\\' => {
2087 buffer.add_string(self.parse_escape(true)?);
2088 wrote_newline = false;
2089 }
2090 '"' | '\'' => {
2091 buffer.add_interpolation(
2092 self.parse_interpolated_string()?
2093 .node
2094 .as_interpolation(false),
2095 );
2096 wrote_newline = false;
2097 }
2098 '/' => {
2099 if matches!(self.toks().peek_n(1), Some(Token { kind: '*', .. })) {
2100 let comment = self.fallible_raw_text(Self::skip_loud_comment)?;
2101 buffer.add_string(comment);
2102 } else {
2103 self.toks_mut().next();
2104 buffer.add_char(tok.kind);
2105 }
2106
2107 wrote_newline = false;
2108 }
2109 '#' => {
2110 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) {
2111 buffer.add_interpolation(self.parse_interpolated_identifier()?);
2114 } else {
2115 self.toks_mut().next();
2116 buffer.add_char(tok.kind);
2117 }
2118
2119 wrote_newline = false;
2120 }
2121 ' ' | '\t' => {
2122 if wrote_newline
2123 || !matches!(
2124 self.toks().peek_n(1),
2125 Some(Token {
2126 kind: ' ' | '\r' | '\t' | '\n',
2127 ..
2128 })
2129 )
2130 {
2131 self.toks_mut().next();
2132 buffer.add_char(tok.kind);
2133 } else {
2134 self.toks_mut().next();
2135 }
2136 }
2137 '\n' | '\r' => {
2138 if self.is_indented() {
2139 break;
2140 }
2141 if !matches!(
2142 self.toks().peek_n_backwards(1),
2143 Some(Token {
2144 kind: '\r' | '\n',
2145 ..
2146 })
2147 ) {
2148 buffer.add_char('\n');
2149 }
2150 self.toks_mut().next();
2151 wrote_newline = true;
2152 }
2153 '(' | '{' | '[' => {
2154 self.toks_mut().next();
2155 buffer.add_char(tok.kind);
2156 brackets.push(opposite_bracket(tok.kind));
2157 wrote_newline = false;
2158 }
2159 ')' | '}' | ']' => {
2160 if brackets.is_empty() {
2161 break;
2162 }
2163 buffer.add_char(tok.kind);
2164 self.expect_char(brackets.pop().unwrap())?;
2165 wrote_newline = false;
2166 }
2167 ';' => {
2168 if !allow_semicolon && brackets.is_empty() {
2169 break;
2170 }
2171 buffer.add_char(tok.kind);
2172 self.toks_mut().next();
2173 wrote_newline = false;
2174 }
2175 ':' => {
2176 if !allow_colon && brackets.is_empty() {
2177 break;
2178 }
2179 buffer.add_char(tok.kind);
2180 self.toks_mut().next();
2181 wrote_newline = false;
2182 }
2183 'u' | 'U' => {
2184 let before_url = self.toks().cursor();
2185
2186 if !self.scan_identifier("url", false)? {
2187 buffer.add_char(tok.kind);
2188 self.toks_mut().next();
2189 wrote_newline = false;
2190 continue;
2191 }
2192
2193 match self.try_url_contents(None)? {
2194 Some(contents) => {
2195 buffer.add_interpolation(contents);
2196 }
2197 None => {
2198 self.toks_mut().set_cursor(before_url);
2199 buffer.add_char(tok.kind);
2200 self.toks_mut().next();
2201 }
2202 }
2203
2204 wrote_newline = false;
2205 }
2206 _ => {
2207 if self.looking_at_identifier() {
2208 buffer.add_string(self.parse_identifier(false, false)?);
2209 } else {
2210 buffer.add_char(tok.kind);
2211 self.toks_mut().next();
2212 }
2213 wrote_newline = false;
2214 }
2215 }
2216 }
2217
2218 if let Some(&last) = brackets.last() {
2219 self.expect_char(last)?;
2220 }
2221
2222 if !allow_empty && buffer.contents.is_empty() {
2223 return Err(("Expected token.", self.toks().current_span()).into());
2224 }
2225
2226 Ok(buffer)
2227 }
2228
2229 fn parse_expression_until_comma(
2230 &mut self,
2231 single_equals: bool,
2233 ) -> SassResult<Spanned<AstExpr>> {
2234 ValueParser::parse_expression(
2235 self,
2236 Some(&|parser| {
2237 Ok(matches!(
2238 parser.toks().peek(),
2239 Some(Token { kind: ',', .. })
2240 ))
2241 }),
2242 false,
2243 single_equals,
2244 )
2245 }
2246
2247 fn parse_argument_invocation(
2248 &mut self,
2249 for_mixin: bool,
2250 allow_empty_second_arg: bool,
2251 ) -> SassResult<ArgumentInvocation> {
2252 let start = self.toks().cursor();
2253
2254 self.expect_char('(')?;
2255 self.whitespace()?;
2256
2257 let mut positional = Vec::new();
2258 let mut named = BTreeMap::new();
2259
2260 let mut rest: Option<AstExpr> = None;
2261 let mut keyword_rest: Option<AstExpr> = None;
2262
2263 while self.looking_at_expression() {
2264 let expression = self.parse_expression_until_comma(!for_mixin)?;
2265 self.whitespace()?;
2266
2267 if expression.node.is_variable() && self.scan_char(':') {
2268 let name = match expression.node {
2269 AstExpr::Variable { name, .. } => name,
2270 _ => unreachable!(),
2271 };
2272
2273 self.whitespace()?;
2274 if named.contains_key(&name.node) {
2275 return Err(("Duplicate argument.", name.span).into());
2276 }
2277
2278 named.insert(
2279 name.node,
2280 self.parse_expression_until_comma(!for_mixin)?.node,
2281 );
2282 } else if self.scan_char('.') {
2283 self.expect_char('.')?;
2284 self.expect_char('.')?;
2285
2286 if rest.is_none() {
2287 rest = Some(expression.node);
2288 } else {
2289 keyword_rest = Some(expression.node);
2290 self.whitespace()?;
2291 break;
2292 }
2293 } else if !named.is_empty() {
2294 return Err((
2295 "Positional arguments must come before keyword arguments.",
2296 expression.span,
2297 )
2298 .into());
2299 } else {
2300 positional.push(expression.node);
2301 }
2302
2303 self.whitespace()?;
2304 if !self.scan_char(',') {
2305 break;
2306 }
2307 self.whitespace()?;
2308
2309 if allow_empty_second_arg
2310 && positional.len() == 1
2311 && named.is_empty()
2312 && rest.is_none()
2313 && matches!(self.toks().peek(), Some(Token { kind: ')', .. }))
2314 {
2315 positional.push(AstExpr::String(
2316 StringExpr(Interpolation::new(), QuoteKind::None),
2317 self.toks().current_span(),
2318 ));
2319 break;
2320 }
2321 }
2322
2323 self.expect_char(')')?;
2324
2325 Ok(ArgumentInvocation {
2326 positional,
2327 named,
2328 rest,
2329 keyword_rest,
2330 span: self.toks_mut().span_from(start),
2331 })
2332 }
2333
2334 fn parse_expression(
2335 &mut self,
2336 parse_until: Option<Predicate<'_, Self>>,
2337 inside_bracketed_list: Option<bool>,
2338 single_equals: Option<bool>,
2339 ) -> SassResult<Spanned<AstExpr>> {
2340 ValueParser::parse_expression(
2341 self,
2342 parse_until,
2343 inside_bracketed_list.unwrap_or(false),
2344 single_equals.unwrap_or(false),
2345 )
2346 }
2347
2348 fn parse_declaration_or_buffer(&mut self) -> SassResult<DeclarationOrBuffer> {
2349 let start = self.toks().cursor();
2350 let mut name_buffer = Interpolation::new();
2351
2352 let first = self.toks().peek();
2355 let mut starts_with_punctuation = false;
2356
2357 if matches!(
2358 first,
2359 Some(Token {
2360 kind: ':' | '*' | '.',
2361 ..
2362 })
2363 ) || (matches!(first, Some(Token { kind: '#', .. }))
2364 && !matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })))
2365 {
2366 starts_with_punctuation = true;
2367 name_buffer.add_char(self.toks_mut().next().unwrap().kind);
2368 name_buffer.add_string(self.raw_text(Self::whitespace));
2369 }
2370
2371 if !self.looking_at_interpolated_identifier() {
2372 return Ok(DeclarationOrBuffer::Buffer(name_buffer));
2373 }
2374
2375 let variable_or_interpolation = if starts_with_punctuation {
2376 VariableDeclOrInterpolation::Interpolation(self.parse_interpolated_identifier()?)
2377 } else {
2378 self.parse_variable_declaration_or_interpolation()?
2379 };
2380
2381 match variable_or_interpolation {
2382 VariableDeclOrInterpolation::Interpolation(int) => name_buffer.add_interpolation(int),
2383 VariableDeclOrInterpolation::VariableDecl(v) => {
2384 return Ok(DeclarationOrBuffer::Stmt(AstStmt::VariableDecl(v)))
2385 }
2386 }
2387
2388 self.flags_mut().set(ContextFlags::IS_USE_ALLOWED, false);
2389
2390 if self.next_matches("/*") {
2391 name_buffer.add_string(self.fallible_raw_text(Self::skip_loud_comment)?);
2392 }
2393
2394 let mut mid_buffer = String::new();
2395 mid_buffer.push_str(&self.raw_text(Self::whitespace));
2396
2397 if !self.scan_char(':') {
2398 if !mid_buffer.is_empty() {
2399 name_buffer.add_char(' ');
2400 }
2401 return Ok(DeclarationOrBuffer::Buffer(name_buffer));
2402 }
2403 mid_buffer.push(':');
2404
2405 if name_buffer.initial_plain().starts_with("--") {
2407 let value_start = self.toks().cursor();
2408 let value = self.parse_interpolated_declaration_value(false, false, true)?;
2409 let value_span = self.toks_mut().span_from(value_start);
2410 self.expect_statement_separator(Some("custom property"))?;
2411 return Ok(DeclarationOrBuffer::Stmt(AstStmt::Style(AstStyle {
2412 name: name_buffer,
2413 value: Some(
2414 AstExpr::String(StringExpr(value, QuoteKind::None), value_span)
2415 .span(value_span),
2416 ),
2417 span: self.toks_mut().span_from(start),
2418 body: Vec::new(),
2419 })));
2420 }
2421
2422 if self.scan_char(':') {
2423 name_buffer.add_string(mid_buffer);
2424 name_buffer.add_char(':');
2425 return Ok(DeclarationOrBuffer::Buffer(name_buffer));
2426 } else if self.is_indented() && self.looking_at_interpolated_identifier() {
2427 name_buffer.add_string(mid_buffer);
2430 return Ok(DeclarationOrBuffer::Buffer(name_buffer));
2431 }
2432
2433 let post_colon_whitespace = self.raw_text(Self::whitespace);
2434 if self.looking_at_children()? {
2435 let body = self.with_children(Self::parse_declaration_child)?.node;
2436 return Ok(DeclarationOrBuffer::Stmt(AstStmt::Style(AstStyle {
2437 name: name_buffer,
2438 value: None,
2439 span: self.toks_mut().span_from(start),
2440 body,
2441 })));
2442 }
2443
2444 mid_buffer.push_str(&post_colon_whitespace);
2445 let could_be_selector =
2446 post_colon_whitespace.is_empty() && self.looking_at_interpolated_identifier();
2447
2448 let before_decl = self.toks().cursor();
2449
2450 let mut calculate_value = || {
2451 let value = self.parse_expression(None, None, None)?;
2452
2453 if self.looking_at_children()? {
2454 if could_be_selector {
2455 self.expect_statement_separator(None)?;
2456 }
2457 } else if !self.at_end_of_statement() {
2458 self.expect_statement_separator(None)?;
2459 }
2460
2461 Ok(value)
2462 };
2463
2464 let value = match calculate_value() {
2465 Ok(v) => v,
2466 Err(e) => {
2467 if !could_be_selector {
2468 return Err(e);
2469 }
2470
2471 self.toks_mut().set_cursor(before_decl);
2472 let additional = self.almost_any_value(false)?;
2473 if !self.is_indented() && self.toks_mut().next_char_is(';') {
2474 return Err(e);
2475 }
2476
2477 name_buffer.add_string(mid_buffer);
2478 name_buffer.add_interpolation(additional);
2479 return Ok(DeclarationOrBuffer::Buffer(name_buffer));
2480 }
2481 };
2482
2483 if self.looking_at_children()? {
2484 let body = self.with_children(Self::parse_declaration_child)?.node;
2485 Ok(DeclarationOrBuffer::Stmt(AstStmt::Style(AstStyle {
2486 name: name_buffer,
2487 value: Some(value),
2488 span: self.toks_mut().span_from(start),
2489 body,
2490 })))
2491 } else {
2492 self.expect_statement_separator(None)?;
2493 Ok(DeclarationOrBuffer::Stmt(AstStmt::Style(AstStyle {
2494 name: name_buffer,
2495 value: Some(value),
2496 span: self.toks_mut().span_from(start),
2497 body: Vec::new(),
2498 })))
2499 }
2500 }
2501
2502 fn parse_declaration_child(&mut self) -> SassResult<AstStmt> {
2503 let start = self.toks().cursor();
2504
2505 if self.toks_mut().next_char_is('@') {
2506 self.parse_declaration_at_rule(start)
2507 } else {
2508 self.parse_property_or_variable_declaration(false)
2509 }
2510 }
2511
2512 fn parse_plain_at_rule_name(&mut self) -> SassResult<String> {
2513 self.expect_char('@')?;
2514 let name = self.parse_identifier(false, false)?;
2515 self.whitespace()?;
2516 Ok(name)
2517 }
2518
2519 fn parse_declaration_at_rule(&mut self, start: usize) -> SassResult<AstStmt> {
2520 let name = self.parse_plain_at_rule_name()?;
2521
2522 match name.as_str() {
2523 "content" => self.parse_content_rule(start),
2524 "debug" => self.parse_debug_rule(),
2525 "each" => self.parse_each_rule(Self::parse_declaration_child),
2526 "else" => self.parse_disallowed_at_rule(start),
2527 "error" => self.parse_error_rule(),
2528 "for" => self.parse_for_rule(Self::parse_declaration_child),
2529 "if" => self.parse_if_rule(Self::parse_declaration_child),
2530 "include" => self.parse_include_rule(),
2531 "warn" => self.parse_warn_rule(),
2532 "while" => self.parse_while_rule(Self::parse_declaration_child),
2533 _ => self.parse_disallowed_at_rule(start),
2534 }
2535 }
2536
2537 fn parse_variable_declaration_or_style_rule(&mut self) -> SassResult<AstStmt> {
2538 let start = self.toks().cursor();
2539
2540 if self.is_plain_css() {
2541 return self.parse_style_rule(None, None);
2542 }
2543
2544 if self.is_indented() && self.scan_char('\\') {
2548 return self.parse_style_rule(None, None);
2549 };
2550
2551 if !self.looking_at_identifier() {
2552 return self.parse_style_rule(None, None);
2553 }
2554
2555 match self.parse_variable_declaration_or_interpolation()? {
2556 VariableDeclOrInterpolation::VariableDecl(var) => Ok(AstStmt::VariableDecl(var)),
2557 VariableDeclOrInterpolation::Interpolation(int) => {
2558 self.parse_style_rule(Some(int), Some(start))
2559 }
2560 }
2561 }
2562
2563 fn parse_style_rule(
2564 &mut self,
2565 existing_buffer: Option<Interpolation>,
2566 start: Option<usize>,
2567 ) -> SassResult<AstStmt> {
2568 let start = start.unwrap_or_else(|| self.toks().cursor());
2569
2570 self.flags_mut().set(ContextFlags::IS_USE_ALLOWED, false);
2571 let mut interpolation = self.parse_style_rule_selector()?;
2572
2573 if let Some(mut existing_buffer) = existing_buffer {
2574 existing_buffer.add_interpolation(interpolation);
2575 interpolation = existing_buffer;
2576 }
2577
2578 if interpolation.contents.is_empty() {
2579 return Err(("expected \"}\".", self.toks().current_span()).into());
2580 }
2581
2582 let was_in_style_rule = self.flags().in_style_rule();
2583 *self.flags_mut() |= ContextFlags::IN_STYLE_RULE;
2584
2585 let selector_span = self.toks_mut().span_from(start);
2586
2587 let children = self.with_children(Self::parse_statement)?;
2588
2589 self.flags_mut()
2590 .set(ContextFlags::IN_STYLE_RULE, was_in_style_rule);
2591
2592 let span = selector_span.merge(children.span);
2593
2594 Ok(AstStmt::RuleSet(AstRuleSet {
2595 selector: interpolation,
2596 body: children.node,
2597 selector_span,
2598 span,
2599 }))
2600 }
2601
2602 fn parse_silent_comment(&mut self) -> SassResult<AstStmt> {
2603 let start = self.toks().cursor();
2604 debug_assert!(self.next_matches("//"));
2605 self.toks_mut().next();
2606 self.toks_mut().next();
2607
2608 let mut buffer = String::new();
2609
2610 while let Some(tok) = self.toks_mut().next() {
2611 if tok.kind == '\n' {
2612 self.whitespace_without_comments();
2613 if self.next_matches("//") {
2614 self.toks_mut().next();
2615 self.toks_mut().next();
2616 buffer.clear();
2617 continue;
2618 }
2619 break;
2620 }
2621
2622 buffer.push(tok.kind);
2623 }
2624
2625 if self.is_plain_css() {
2626 return Err((
2627 "Silent comments aren't allowed in plain CSS.",
2628 self.toks_mut().span_from(start),
2629 )
2630 .into());
2631 }
2632
2633 self.whitespace_without_comments();
2634
2635 Ok(AstStmt::SilentComment(AstSilentComment {
2636 text: buffer,
2637 span: self.toks_mut().span_from(start),
2638 }))
2639 }
2640
2641 fn next_is_hex(&self) -> bool {
2642 match self.toks().peek() {
2643 Some(Token { kind, .. }) => kind.is_ascii_hexdigit(),
2644 None => false,
2645 }
2646 }
2647
2648 fn assert_public(ident: &str, span: Span) -> SassResult<()> {
2649 if !ScssParser::is_private(ident) {
2650 return Ok(());
2651 }
2652
2653 Err((
2654 "Private members can't be accessed from outside their modules.",
2655 span,
2656 )
2657 .into())
2658 }
2659
2660 fn is_private(ident: &str) -> bool {
2661 ident.starts_with('-') || ident.starts_with('_')
2662 }
2663
2664 fn parse_variable_declaration_without_namespace(
2665 &mut self,
2666 namespace: Option<Spanned<Identifier>>,
2667 start: Option<usize>,
2668 ) -> SassResult<AstVariableDecl> {
2669 let start = start.unwrap_or_else(|| self.toks().cursor());
2670
2671 let name = self.parse_variable_name()?;
2672
2673 if namespace.is_some() {
2674 Self::assert_public(&name, self.toks_mut().span_from(start))?;
2675 }
2676
2677 if self.is_plain_css() {
2678 return Err((
2679 "Sass variables aren't allowed in plain CSS.",
2680 self.toks_mut().span_from(start),
2681 )
2682 .into());
2683 }
2684
2685 self.whitespace()?;
2686 self.expect_char(':')?;
2687 self.whitespace()?;
2688
2689 let value = self.parse_expression(None, None, None)?.node;
2690
2691 let mut is_guarded = false;
2692 let mut is_global = false;
2693
2694 while self.scan_char('!') {
2695 let flag_start = self.toks().cursor();
2696 let flag = self.parse_identifier(false, false)?;
2697
2698 match flag.as_str() {
2699 "default" => is_guarded = true,
2700 "global" => {
2701 if namespace.is_some() {
2702 return Err((
2703 "!global isn't allowed for variables in other modules.",
2704 self.toks_mut().span_from(flag_start),
2705 )
2706 .into());
2707 }
2708
2709 is_global = true;
2710 }
2711 _ => {
2712 return Err(
2713 ("Invalid flag name.", self.toks_mut().span_from(flag_start)).into(),
2714 )
2715 }
2716 }
2717
2718 self.whitespace()?;
2719 }
2720
2721 self.expect_statement_separator(Some("variable declaration"))?;
2722
2723 let declaration = AstVariableDecl {
2724 namespace,
2725 name: Identifier::from(name),
2726 value,
2727 is_guarded,
2728 is_global,
2729 span: self.toks_mut().span_from(start),
2730 };
2731
2732 if is_global {
2733 }
2736
2737 Ok(declaration)
2738 }
2739
2740 fn almost_any_value(
2741 &mut self,
2742 omit_comments: bool,
2744 ) -> SassResult<Interpolation> {
2745 let mut buffer = Interpolation::new();
2746
2747 while let Some(tok) = self.toks().peek() {
2748 match tok.kind {
2749 '\\' => {
2750 buffer.add_char(tok.kind);
2752 self.toks_mut().next();
2753 match self.toks_mut().next() {
2754 Some(tok) => buffer.add_char(tok.kind),
2755 None => {
2756 return Err(("expected more input.", self.toks().current_span()).into())
2757 }
2758 }
2759 }
2760 '"' | '\'' => {
2761 buffer.add_interpolation(
2762 self.parse_interpolated_string()?
2763 .node
2764 .as_interpolation(false),
2765 );
2766 }
2767 '/' => {
2768 let comment_start = self.toks().cursor();
2769 if self.scan_comment()? {
2770 if !omit_comments {
2771 buffer.add_string(self.toks().raw_text(comment_start));
2772 }
2773 } else {
2774 buffer.add_char(self.toks_mut().next().unwrap().kind);
2775 }
2776 }
2777 '#' => {
2778 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) {
2779 buffer.add_interpolation(self.parse_interpolated_identifier()?);
2782 } else {
2783 self.toks_mut().next();
2784 buffer.add_char(tok.kind);
2785 }
2786 }
2787 '\r' | '\n' => {
2788 if self.is_indented() {
2789 break;
2790 }
2791 buffer.add_char(self.toks_mut().next().unwrap().kind);
2792 }
2793 '!' | ';' | '{' | '}' => break,
2794 'u' | 'U' => {
2795 let before_url = self.toks().cursor();
2796 if !self.scan_identifier("url", false)? {
2797 self.toks_mut().next();
2798 buffer.add_char(tok.kind);
2799 continue;
2800 }
2801
2802 match self.try_url_contents(None)? {
2803 Some(contents) => buffer.add_interpolation(contents),
2804 None => {
2805 self.toks_mut().set_cursor(before_url);
2806 self.toks_mut().next();
2807 buffer.add_char(tok.kind);
2808 }
2809 }
2810 }
2811 _ => {
2812 if self.looking_at_identifier() {
2813 buffer.add_string(self.parse_identifier(false, false)?);
2814 } else {
2815 buffer.add_char(self.toks_mut().next().unwrap().kind);
2816 }
2817 }
2818 }
2819 }
2820
2821 Ok(buffer)
2822 }
2823
2824 fn parse_variable_declaration_or_interpolation(
2825 &mut self,
2826 ) -> SassResult<VariableDeclOrInterpolation> {
2827 if !self.looking_at_identifier() {
2828 return Ok(VariableDeclOrInterpolation::Interpolation(
2829 self.parse_interpolated_identifier()?,
2830 ));
2831 }
2832
2833 let start = self.toks().cursor();
2834
2835 let ident = self.parse_identifier(false, false)?;
2836 if self.next_matches(".$") {
2837 let namespace_span = self.toks_mut().span_from(start);
2838 self.expect_char('.')?;
2839 Ok(VariableDeclOrInterpolation::VariableDecl(
2840 self.parse_variable_declaration_without_namespace(
2841 Some(Spanned {
2842 node: Identifier::from(ident),
2843 span: namespace_span,
2844 }),
2845 Some(start),
2846 )?,
2847 ))
2848 } else {
2849 let mut buffer = Interpolation::new_plain(ident);
2850
2851 if self.looking_at_interpolated_identifier_body() {
2852 buffer.add_interpolation(self.parse_interpolated_identifier()?);
2853 }
2854
2855 Ok(VariableDeclOrInterpolation::Interpolation(buffer))
2856 }
2857 }
2858
2859 fn looking_at_interpolated_identifier_body(&mut self) -> bool {
2860 match self.toks().peek() {
2861 Some(Token { kind: '\\', .. }) => true,
2862 Some(Token { kind: '#', .. })
2863 if matches!(self.toks().peek_n(1), Some(Token { kind: '{', .. })) =>
2864 {
2865 true
2866 }
2867 Some(Token { kind, .. }) if is_name(kind) => true,
2868 Some(..) | None => false,
2869 }
2870 }
2871
2872 fn expression_until_comparison(&mut self) -> SassResult<Spanned<AstExpr>> {
2873 let value = self.parse_expression(
2874 Some(&|parser| {
2875 Ok(match parser.toks().peek() {
2876 Some(Token { kind: '>', .. }) | Some(Token { kind: '<', .. }) => true,
2877 Some(Token { kind: '=', .. }) => {
2878 !matches!(parser.toks().peek_n(1), Some(Token { kind: '=', .. }))
2879 }
2880 _ => false,
2881 })
2882 }),
2883 None,
2884 None,
2885 )?;
2886 Ok(value)
2887 }
2888
2889 fn parse_media_query_list(&mut self) -> SassResult<Interpolation> {
2890 let mut buf = Interpolation::new();
2891 loop {
2892 self.whitespace()?;
2893 self.parse_media_query(&mut buf)?;
2894 self.whitespace()?;
2895 if !self.scan_char(',') {
2896 break;
2897 }
2898 buf.add_char(',');
2899 buf.add_char(' ');
2900 }
2901 Ok(buf)
2902 }
2903
2904 fn parse_media_in_parens(&mut self, buf: &mut Interpolation) -> SassResult<()> {
2905 self.expect_char_with_message('(', "media condition in parentheses")?;
2906 buf.add_char('(');
2907 self.whitespace()?;
2908
2909 if matches!(self.toks().peek(), Some(Token { kind: '(', .. })) {
2910 self.parse_media_in_parens(buf)?;
2911 self.whitespace()?;
2912
2913 if self.scan_identifier("and", false)? {
2914 buf.add_string(" and ".to_owned());
2915 self.expect_whitespace()?;
2916 self.parse_media_logic_sequence(buf, "and")?;
2917 } else if self.scan_identifier("or", false)? {
2918 buf.add_string(" or ".to_owned());
2919 self.expect_whitespace()?;
2920 self.parse_media_logic_sequence(buf, "or")?;
2921 }
2922 } else if self.scan_identifier("not", false)? {
2923 buf.add_string("not ".to_owned());
2924 self.expect_whitespace()?;
2925 self.parse_media_or_interpolation(buf)?;
2926 } else {
2927 buf.add_expr(self.expression_until_comparison()?);
2928
2929 if self.scan_char(':') {
2930 self.whitespace()?;
2931 buf.add_char(':');
2932 buf.add_char(' ');
2933 buf.add_expr(self.parse_expression(None, None, None)?);
2934 } else {
2935 let next = self.toks().peek();
2936 if matches!(
2937 next,
2938 Some(Token {
2939 kind: '<' | '>' | '=',
2940 ..
2941 })
2942 ) {
2943 let next = next.unwrap().kind;
2944 buf.add_char(' ');
2945 buf.add_char(self.toks_mut().next().unwrap().kind);
2946
2947 if (next == '<' || next == '>') && self.scan_char('=') {
2948 buf.add_char('=');
2949 }
2950
2951 buf.add_char(' ');
2952
2953 self.whitespace()?;
2954
2955 buf.add_expr(self.expression_until_comparison()?);
2956
2957 if (next == '<' || next == '>') && self.scan_char(next) {
2958 buf.add_char(' ');
2959 buf.add_char(next);
2960
2961 if self.scan_char('=') {
2962 buf.add_char('=');
2963 }
2964
2965 buf.add_char(' ');
2966
2967 self.whitespace()?;
2968 buf.add_expr(self.expression_until_comparison()?);
2969 }
2970 }
2971 }
2972 }
2973
2974 self.expect_char(')')?;
2975 self.whitespace()?;
2976 buf.add_char(')');
2977
2978 Ok(())
2979 }
2980
2981 fn parse_media_logic_sequence(
2982 &mut self,
2983 buf: &mut Interpolation,
2984 operator: &'static str,
2985 ) -> SassResult<()> {
2986 loop {
2987 self.parse_media_or_interpolation(buf)?;
2988 self.whitespace()?;
2989
2990 if !self.scan_identifier(operator, false)? {
2991 return Ok(());
2992 }
2993
2994 self.expect_whitespace()?;
2995
2996 buf.add_char(' ');
2997 buf.add_string(operator.to_owned());
2998 buf.add_char(' ');
2999 }
3000 }
3001
3002 fn parse_media_or_interpolation(&mut self, buf: &mut Interpolation) -> SassResult<()> {
3003 if self.toks_mut().next_char_is('#') {
3004 buf.add_interpolation(self.parse_single_interpolation()?);
3005 } else {
3006 self.parse_media_in_parens(buf)?;
3007 }
3008
3009 Ok(())
3010 }
3011
3012 fn parse_media_query(&mut self, buf: &mut Interpolation) -> SassResult<()> {
3013 if matches!(self.toks().peek(), Some(Token { kind: '(', .. })) {
3014 self.parse_media_in_parens(buf)?;
3015 self.whitespace()?;
3016
3017 if self.scan_identifier("and", false)? {
3018 buf.add_string(" and ".to_owned());
3019 self.expect_whitespace()?;
3020 self.parse_media_logic_sequence(buf, "and")?;
3021 } else if self.scan_identifier("or", false)? {
3022 buf.add_string(" or ".to_owned());
3023 self.expect_whitespace()?;
3024 self.parse_media_logic_sequence(buf, "or")?;
3025 }
3026
3027 return Ok(());
3028 }
3029
3030 let ident1 = self.parse_interpolated_identifier()?;
3031
3032 if ident1.as_plain().unwrap_or("").to_ascii_lowercase() == "not" {
3033 self.expect_whitespace()?;
3035 if !self.looking_at_interpolated_identifier() {
3036 buf.add_string("not ".to_owned());
3037 self.parse_media_or_interpolation(buf)?;
3038 return Ok(());
3039 }
3040 }
3041
3042 self.whitespace()?;
3043 buf.add_interpolation(ident1);
3044 if !self.looking_at_interpolated_identifier() {
3045 return Ok(());
3047 }
3048
3049 buf.add_char(' ');
3050
3051 let ident2 = self.parse_interpolated_identifier()?;
3052
3053 if ident2.as_plain().unwrap_or("").to_ascii_lowercase() == "and" {
3054 self.expect_whitespace()?;
3055 buf.add_string(" and ".to_owned());
3057 } else {
3058 self.whitespace()?;
3059 buf.add_interpolation(ident2);
3060
3061 if self.scan_identifier("and", false)? {
3062 self.expect_whitespace()?;
3064 buf.add_string(" and ".to_owned());
3065 } else {
3066 return Ok(());
3068 }
3069 }
3070
3071 if self.scan_identifier("not", false)? {
3075 self.expect_whitespace()?;
3077 buf.add_string("not ".to_owned());
3078 self.parse_media_or_interpolation(buf)?;
3079 return Ok(());
3080 }
3081
3082 self.parse_media_logic_sequence(buf, "and")?;
3083
3084 Ok(())
3085 }
3086}