grass_compiler/parse/
stylesheet.rs

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
26/// Default implementations are oriented towards the SCSS syntax, as both CSS and
27/// SCSS share the behavior
28pub(crate) trait StylesheetParser<'a>: BaseParser + Sized {
29    // todo: make constant?
30    fn is_plain_css(&self) -> bool;
31    // todo: make constant?
32    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                // todo: deprecation warning here
87                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    // todo: rename
186    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        // Allow a byte-order mark at the beginning of the document.
196        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        // Exit before allocating anything if we're not looking at any modifiers, as
733        // is the most common case.
734        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        // Match Ruby Sass's behavior: parse a raw URL() if possible, and if not
805        // backtrack and re-parse as a function expression.
806        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            // todo: try parseImportUrl
891            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            // todo: _inControlDirective
903            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                        // todo: if (second == $cr) scanner.scanChar($lf);
1035                        // we basically need to stop normalizing to gain parity
1036                        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        // Unfortunately, we may have to backtrack here. The grammar is:
1283        //
1284        //       Expression ":" Expression
1285        //     | InterpolatedIdentifier InterpolatedAnyValue?
1286        //
1287        // These aren't ambiguous because this `InterpolatedAnyValue` is forbidden
1288        // from containing a top-level colon, but we still have to parse the full
1289        // expression to figure out if there's a colon after it.
1290        //
1291        // We could avoid the overhead of a full expression parse by looking ahead
1292        // for a colon (outside of balanced brackets), but in practice we expect the
1293        // vast majority of real uses to be `Expression ":" Expression`, so it makes
1294        // sense to parse that case faster in exchange for less code complexity and
1295        // a slower backtracking case.
1296
1297        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                // todo: superfluous clone?
1314                if let Some(operation) = self.try_supports_operation(&identifier, name_start)? {
1315                    self.expect_char(')')?;
1316                    return Ok(operation);
1317                }
1318
1319                // If parsing an expression fails, try to parse an
1320                // `InterpolatedAnyValue` instead. But if that value runs into a
1321                // top-level colon, then this is probably intended to be a declaration
1322                // after all, so we rethrow the declaration-parsing error.
1323                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            // todo: withErrorMessage("Expected variable, mixin, or function name"
1497            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        // todo: real uri parsing
1515        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        // if namespace is empty, avoid attempting to parse an identifier from
1550        // an empty string, as there will be no span to emit
1551        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        // default=false
1577        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        // We want to set [_isUseAllowed] to `false` *unless* we're parsing
1686        // `@charset`, `@forward`, or `@use`. To avoid double-comparing the rule
1687        // name, we always set it to `false` and then set it back to its previous
1688        // value if we're parsing an allowed rule.
1689        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                // if (!root) {
1705                //     _disallowedAtRule();
1706                // }
1707                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            // todo: support -moz-document
1716            // Some("-moz-document") => self.parse_moz_document_rule(name),
1717            Some("supports") => self.parse_supports_rule(),
1718            Some("use") => {
1719                self.flags_mut()
1720                    .set(ContextFlags::IS_USE_ALLOWED, was_use_allowed);
1721                // if (!root) {
1722                //     _disallowedAtRule();
1723                // }
1724                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        // The indented syntax allows a single backslash to distinguish a style rule
1789        // from old-style property syntax. We don't support old property syntax, but
1790        // we do support the backslash because it's easy to do.
1791        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        // default=true
1806        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            // Allow the "*prop: val", ":prop: val", "#prop: val", and ".prop: val"
1820            // hacks.
1821            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                    // todo: does \r even exist at this point? (removed by lexer)
2056                    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        // default=false
2073        allow_semicolon: bool,
2074        // default=false
2075        allow_empty: bool,
2076        // default=true
2077        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                        // Add a full interpolated identifier to handle cases like
2112                        // "#{...}--1", since "--1" isn't a valid identifier on its own.
2113                        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        // default=false
2232        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        // Allow the "*prop: val", ":prop: val", "#prop: val", and ".prop: val"
2353        // hacks.
2354        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        // Parse custom properties as declarations no matter what.
2406        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            // In the indented syntax, `foo:bar` is always considered a selector
2428            // rather than a property.
2429            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        // The indented syntax allows a single backslash to distinguish a style rule
2545        // from old-style property syntax. We don't support old property syntax, but
2546        // we do support the backslash because it's easy to do.
2547        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            // todo
2734            // _globalVariables.putIfAbsent(name, () => declaration)
2735        }
2736
2737        Ok(declaration)
2738    }
2739
2740    fn almost_any_value(
2741        &mut self,
2742        // default=false
2743        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                    // Write a literal backslash because this text will be re-parsed.
2751                    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                        // Add a full interpolated identifier to handle cases like
2780                        // "#{...}--1", since "--1" isn't a valid identifier on its own.
2781                        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            // For example, "@media not (...) {"
3034            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            // For example, "@media screen {".
3046            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            // For example, "@media screen and ..."
3056            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                // For example, "@media only screen and ..."
3063                self.expect_whitespace()?;
3064                buf.add_string(" and ".to_owned());
3065            } else {
3066                // For example, "@media only screen {"
3067                return Ok(());
3068            }
3069        }
3070
3071        // We've consumed either `IDENTIFIER "and"` or
3072        // `IDENTIFIER IDENTIFIER "and"`.
3073
3074        if self.scan_identifier("not", false)? {
3075            // For example, "@media screen and not (...) {"
3076            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}