Skip to main content

orinium_browser/engine/css/
parser.rs

1//! CSS Parser
2//!
3//! Consumes tokens produced by the tokenizer and builds
4//! higher-level CSS syntax structures.
5//!
6//! ## Responsibilities
7//! - Parse token streams into structured CSS data
8//!   (selectors, declarations, component values)
9//! - Handle nesting such as blocks and functions
10//!
11//! ## Non-responsibilities
12//! - Tokenization of raw input
13//! - Semantic interpretation (length resolution, color computation, etc.)
14//!
15//! ## Design notes
16//! - No property-specific validation is performed here
17//! - Semantic meaning is assigned in later stages (style computation, layout)
18use std::collections::VecDeque;
19use std::fmt;
20
21use crate::engine::css::values::CssIdent;
22
23use super::tokenizer::{Token, Tokenizer};
24use super::values::{CssValue, Unit};
25
26/// Node kinds used in the CSS syntax tree.
27///
28/// These nodes represent **syntactic structure only**.
29/// No semantic validation or value resolution is performed here.
30#[derive(Debug, Clone)]
31pub enum CssNodeType {
32    /// Root node of a CSS document
33    Stylesheet,
34
35    /// Qualified rule (e.g. `div { ... }`)
36    Rule {
37        /// Selectors associated with this rule
38        selectors: Vec<ComplexSelector>,
39    },
40
41    /// At-rule (e.g. `@media`, `@supports`)
42    AtRule {
43        /// At-rule name without `@`
44        name: String,
45
46        params: AtQuery,
47    },
48
49    /// Declaration inside a rule block (e.g. `color: red`)
50    Declaration {
51        /// Property name
52        name: String,
53
54        value: CssValue,
55    },
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum AtQuery {
60    Keyword(String), // screen, and, not
61    Condition {
62        name: String,    // max-width
63        value: CssValue, // 600px
64    },
65    Range {
66        left: Option<(CssValue, RangeOperator)>,
67        name: String,
68        right: Option<(RangeOperator, CssValue)>,
69    },
70    Group(Vec<AtQuery>), // ( ... )
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum RangeOperator {
75    Less,
76    LessEqual,
77    Equal,
78    GreaterEqual,
79    Greater,
80}
81
82/// Node in the CSS syntax tree.
83///
84/// Each node represents a syntactic construct such as a rule,
85/// at-rule, or declaration, and may contain child nodes.
86#[derive(Debug)]
87pub struct CssNode {
88    /// Kind of this CSS node
89    node: CssNodeType,
90
91    /// Child nodes forming the tree structure
92    children: Vec<CssNode>,
93}
94
95impl CssNode {
96    pub fn node(&self) -> &CssNodeType {
97        &self.node
98    }
99    pub fn children(&self) -> &Vec<CssNode> {
100        &self.children
101    }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Hash)]
105pub struct Selector {
106    /// Nesting selector (`&`).
107    ///
108    /// When `true` this selector represents the CSS nesting selector `&`
109    /// and will be replaced with the parent rule's selector during nesting
110    /// resolution. It may carry additional simple selectors (tag, classes,
111    /// etc.) when it appears inside a compound selector such as `&.highlight`.
112    pub is_nesting: bool,
113
114    /// Type selector (e.g. `div`)
115    ///
116    /// `None` represents the absence of a type selector
117    /// (e.g. `.class`, `#id`).
118    pub tag: Option<String>,
119
120    /// ID selector (e.g. `#main`)
121    pub id: Option<String>,
122
123    /// Class selectors (e.g. `.container`)
124    pub classes: Vec<String>,
125
126    /// Attribute selectors (e.g. `[hidden]`, `[type="text"]`)
127    pub attributes: Vec<AttributeSelector>,
128
129    /// Pseudo-classes (e.g. `:hover`, `:first-child`, `:not(.hidden)`)
130    pub pseudo_classes: Vec<PseudoClass>,
131
132    /// Pseudo-element (e.g. `::before`)
133    pub pseudo_element: Option<String>,
134}
135
136impl Selector {
137    /// Returns `true` when this selector carries fields beyond the nesting
138    /// flag (i.e. it is a compound selector such as `&.highlight`).
139    fn is_compound(&self) -> bool {
140        self.tag.is_some()
141            || self.id.is_some()
142            || !self.classes.is_empty()
143            || !self.attributes.is_empty()
144            || !self.pseudo_classes.is_empty()
145            || self.pseudo_element.is_some()
146    }
147
148    /// Merges the non-nesting fields of `other` into this selector.
149    fn merge_from(&mut self, other: &Selector) {
150        if let Some(tag) = &other.tag {
151            self.tag = Some(tag.clone());
152        }
153        if let Some(id) = &other.id {
154            self.id = Some(id.clone());
155        }
156        self.classes.extend(other.classes.iter().cloned());
157        self.attributes.extend(other.attributes.iter().cloned());
158        self.pseudo_classes
159            .extend(other.pseudo_classes.iter().cloned());
160        if let Some(pe) = &other.pseudo_element {
161            self.pseudo_element = Some(pe.clone());
162        }
163    }
164}
165
166/// A pseudo-class attached to a simple selector.
167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum PseudoClass {
169    /// A non-functional pseudo-class such as `:first-child`.
170    Simple(String),
171    /// A selector-list pseudo-class such as `:is()` or `:not()`.
172    SelectorList {
173        /// Lower-level function name.
174        name: String,
175        /// Parsed selector arguments.
176        selectors: Vec<ComplexSelector>,
177    },
178    /// A structural `An+B` pseudo-class such as `:nth-child(2n+1)`.
179    Nth {
180        /// Function name (`nth-child`, `nth-last-child`, etc.).
181        name: String,
182        /// Step coefficient in `An+B`.
183        a: i32,
184        /// Offset in `An+B`.
185        b: i32,
186    },
187}
188
189/// An attribute-presence or exact-value selector.
190#[derive(Debug, Clone, PartialEq, Eq, Hash)]
191pub struct AttributeSelector {
192    /// Attribute name to match.
193    pub name: String,
194    /// Matching operation.
195    pub operator: AttributeSelectorOperator,
196    /// Required exact value, or `None` for a presence selector.
197    pub value: Option<String>,
198}
199
200/// An attribute selector matching operator.
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202pub enum AttributeSelectorOperator {
203    /// `[attr]`
204    Exists,
205    /// `[attr=value]`
206    Equals,
207    /// `[attr~=value]`
208    Includes,
209    /// `[attr|=value]`
210    DashMatch,
211    /// `[attr^=value]`
212    Prefix,
213    /// `[attr$=value]`
214    Suffix,
215    /// `[attr*=value]`
216    Substring,
217}
218
219/// Combinator defining the relationship between selectors.
220///
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222pub enum Combinator {
223    /// Descendant combinator (` `)
224    Descendant,
225    /// Child combinator (`>`)
226    Child,
227    /// Next-sibling combinator (`+`)
228    NextSibling,
229    /// Subsequent-sibling combinator (`~`)
230    SubsequentSibling,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub struct SelectorPart {
235    /// Simple selector matched at this step
236    pub selector: Selector,
237
238    /// Relationship to the next selector on the left.
239    ///
240    /// `None` indicates this is the leftmost selector
241    /// in the selector sequence.
242    pub combinator: Option<Combinator>,
243}
244
245/// A complex CSS selector composed of multiple selector parts.
246///
247/// Selector parts are stored **from right to left** to match
248/// the order used during selector matching.
249///
250/// Example:
251/// ```text
252/// A B
253/// ```
254/// is stored as:
255/// ```text
256/// [
257///   B (Descendant),
258///   A (None)
259/// ]
260/// ```
261#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262pub struct ComplexSelector {
263    pub parts: Vec<SelectorPart>,
264}
265
266impl ComplexSelector {
267    pub fn empty() -> Self {
268        Self { parts: Vec::new() }
269    }
270
271    pub fn nest(&self, child: &Self) -> Self {
272        if self.parts.is_empty() {
273            return child.clone();
274        }
275        if child.parts.is_empty() {
276            return self.clone();
277        }
278
279        let has_nesting = child.parts.iter().any(|p| p.selector.is_nesting);
280
281        if !has_nesting {
282            // No & in child – connect with a descendant combinator.
283            let mut parts = child.parts.clone();
284            parts
285                .last_mut()
286                .expect("child selector is known to be non-empty")
287                .combinator = Some(Combinator::Descendant);
288            parts.extend(self.parts.iter().cloned());
289            return Self { parts };
290        }
291
292        // Resolve & nesting selector references.
293        let mut result_parts: Vec<SelectorPart> = Vec::new();
294
295        for child_part in &child.parts {
296            if child_part.selector.is_nesting {
297                if child_part.selector.is_compound() {
298                    // Compound & (e.g. &.highlight) – merge parent subject
299                    // fields into this selector and append remaining parent
300                    // parts so the parent chain is preserved.
301                    let mut merged = self.parts[0].selector.clone();
302                    merged.merge_from(&child_part.selector);
303                    merged.is_nesting = false;
304
305                    let combinator = child_part.combinator.or(self.parts[0].combinator);
306
307                    result_parts.push(SelectorPart {
308                        selector: merged,
309                        combinator,
310                    });
311
312                    for parent_part in self.parts.iter().skip(1) {
313                        result_parts.push(parent_part.clone());
314                    }
315                } else {
316                    // Standalone & – replace with the full parent selector
317                    // chain.  The last (leftmost) parent part inherits the
318                    // combinator that & carried.
319                    let child_combinator = child_part.combinator;
320                    let parent_len = self.parts.len();
321                    for (i, parent_part) in self.parts.iter().enumerate() {
322                        let mut part = parent_part.clone();
323                        if i == parent_len - 1 {
324                            part.combinator = child_combinator;
325                        }
326                        result_parts.push(part);
327                    }
328                }
329            } else {
330                result_parts.push(child_part.clone());
331            }
332        }
333
334        Self {
335            parts: result_parts,
336        }
337    }
338}
339
340/// Parse the integer `An+B` grammar used by structural pseudo-classes.
341fn parse_an_plus_b(tokens: &[Token]) -> Option<(i32, i32)> {
342    let mut expression = String::new();
343    for token in tokens {
344        match token {
345            Token::Whitespace | Token::Comment(_) => {}
346            Token::Ident(value) => expression.push_str(&value.to_ascii_lowercase()),
347            Token::Number(value) if value.fract() == 0.0 => {
348                expression.push_str(&(*value as i32).to_string());
349            }
350            Token::Dimension(value, unit) if value.fract() == 0.0 => {
351                expression.push_str(&(*value as i32).to_string());
352                expression.push_str(&unit.to_ascii_lowercase());
353            }
354            Token::Delim(value @ ('+' | '-')) => expression.push(*value),
355            _ => return None,
356        }
357    }
358
359    match expression.as_str() {
360        "odd" => return Some((2, 1)),
361        "even" => return Some((2, 0)),
362        _ => {}
363    }
364
365    if let Some(n_index) = expression.find('n') {
366        if expression[n_index + 1..].contains('n') {
367            return None;
368        }
369        let coefficient = match &expression[..n_index] {
370            "" | "+" => 1,
371            "-" => -1,
372            value => value.parse().ok()?,
373        };
374        let offset = match &expression[n_index + 1..] {
375            "" => 0,
376            value => value.parse().ok()?,
377        };
378        Some((coefficient, offset))
379    } else {
380        Some((0, expression.parse().ok()?))
381    }
382}
383
384/// CSS parser consuming tokens and producing syntax structures.
385#[derive(Clone)]
386pub struct Parser<'a> {
387    /// Source of tokens produced by the tokenizer
388    tokenizer: Tokenizer<'a>,
389
390    /// Used to detect the start and end of rule blocks (`{}`).
391    brace_depth: usize,
392
393    /// Lookahead token (optional)
394    ///
395    /// Parser may need to peek the next token without consuming it.
396    lookahead: VecDeque<Token>,
397}
398
399/// Parser error kinds
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub enum ParserErrorKind {
402    /// Expected a token but found something else
403    UnexpectedToken {
404        expected: &'static str,
405        found: String, // Token debug or value
406    },
407
408    /// Unexpected end of file
409    UnexpectedEOF,
410
411    /// Invalid or unsupported CSS syntax
412    InvalidSyntax,
413
414    /// Mismatched braces or parentheses
415    MismatchedDelimiter { expected: char, found: char },
416}
417
418impl fmt::Display for ParserErrorKind {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        write!(f, "{:?}", self)
421    }
422}
423
424/// Parser error
425#[derive(Debug, Clone, PartialEq, Eq)]
426pub struct ParserError {
427    /// Kind of the error
428    pub kind: ParserErrorKind,
429    /// Context
430    pub context: Vec<String>,
431}
432
433impl ParserError {
434    pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
435        self.context.push(ctx.into());
436        self
437    }
438}
439
440impl fmt::Display for ParserError {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        let mut ctx = self.context.clone();
443        ctx.reverse();
444        write!(
445            f,
446            "CssParserError: {}, (Context:[{}])",
447            self.kind,
448            ctx.join(" <-")
449        )
450    }
451}
452
453impl std::error::Error for ParserError {}
454
455/// Result type for parser functions
456pub type ParseResult<T> = Result<T, ParserError>;
457
458impl<'a> Parser<'a> {
459    /// Create a new CSS parser from a source string.
460    pub fn new(input: &'a str) -> Self {
461        Self {
462            tokenizer: Tokenizer::new(input),
463            brace_depth: 0,
464            lookahead: VecDeque::new(),
465        }
466    }
467
468    fn ensure_lookahead(&mut self, n: usize) {
469        while self.lookahead.len() <= n {
470            let tok = self.tokenizer.next_token();
471            self.lookahead.push_back(tok);
472        }
473    }
474
475    fn peek_next_token(&mut self, cursor_size: usize) -> &Token {
476        self.ensure_lookahead(cursor_size);
477        &self.lookahead[cursor_size]
478    }
479
480    /// Peek at the next token without consuming it.
481    fn peek_token(&mut self) -> &Token {
482        self.peek_next_token(0)
483    }
484
485    /// Parse a bare declaration list (e.g. the value of a `style` attribute).
486    ///
487    /// Unlike `parse()`, this does not expect selectors or a surrounding block.
488    /// It consumes declarations until EOF or `}` and returns them as
489    /// `Declaration` nodes, mirroring the body of a rule.
490    pub fn parse_declarations(&mut self) -> ParseResult<Vec<CssNode>> {
491        self.parse_declaration_and_nested_rule_list()
492    }
493
494    fn consume_token(&mut self) -> Token {
495        if let Some(tok) = self.lookahead.pop_front() {
496            tok
497        } else {
498            self.tokenizer.next_token()
499        }
500    }
501
502    /// Parse the entire CSS source into a syntax tree.
503    ///
504    /// This method consumes tokens until `Token::EOF` is reached and constructs
505    /// a `CssNode` representing the stylesheet root.
506    ///
507    /// Parsing behavior:
508    /// - Whitespace tokens are ignored
509    /// - Qualified rules and at-rules are parsed into child nodes
510    /// - No semantic validation is performed
511    pub fn parse(&mut self) -> ParseResult<CssNode> {
512        let mut stylesheet = CssNode {
513            node: CssNodeType::Stylesheet,
514            children: vec![],
515        };
516
517        loop {
518            let token = self.peek_token().clone();
519
520            match token {
521                Token::EOF => break,
522                Token::Whitespace | Token::Comment(_) => {
523                    self.consume_token();
524                }
525                Token::AtKeyword(_) => {
526                    let node = self
527                        .parse_at_rule()
528                        .map_err(|e| e.with_context("parse: failed to parse at-rule"))?;
529                    log::debug!(target: "CssParser", "AtRule parsed: {:?}", node);
530                    stylesheet.children.push(node);
531                }
532                _ => {
533                    let node = self
534                        .parse_rule()
535                        .map_err(|e| e.with_context("parse: failed to parse rule"))?;
536                    log::debug!(target: "CssParser", "Rule parsed: {:?}", node);
537                    stylesheet.children.push(node);
538                }
539            }
540        }
541
542        Ok(stylesheet)
543    }
544
545    /// Parses a stylesheet while recovering from unsupported top-level items.
546    ///
547    /// Browser stylesheets are frequently generated and may contain selectors
548    /// or declarations that this engine does not support yet. Dropping the
549    /// entire stylesheet for one such rule would also discard all compatible
550    /// rules, so this mode skips the failing item and resumes at the next
551    /// top-level boundary. The strict [`Self::parse`] entry point remains
552    /// available for validation and unit tests.
553    pub fn parse_lossy(&mut self) -> CssNode {
554        let mut stylesheet = CssNode {
555            node: CssNodeType::Stylesheet,
556            children: vec![],
557        };
558
559        loop {
560            let token = self.peek_token().clone();
561            let checkpoint = self.clone();
562            match token {
563                Token::EOF => break,
564                Token::Whitespace | Token::Comment(_) => {
565                    self.consume_token();
566                }
567                Token::AtKeyword(_) => match self.parse_at_rule() {
568                    Ok(node) => stylesheet.children.push(node),
569                    Err(error) => {
570                        log::warn!(
571                            target: "CssParser",
572                            "Skipping unsupported at-rule: {error}"
573                        );
574                        *self = checkpoint;
575                        self.recover_top_level_item();
576                    }
577                },
578                _ => match self.parse_rule() {
579                    Ok(node) => stylesheet.children.push(node),
580                    Err(error) => {
581                        log::warn!(
582                            target: "CssParser",
583                            "Skipping unsupported CSS rule: {error}"
584                        );
585                        *self = checkpoint;
586                        self.recover_top_level_item();
587                    }
588                },
589            }
590        }
591
592        stylesheet
593    }
594
595    fn recover_top_level_item(&mut self) {
596        let mut depth = 0_usize;
597        let mut consumed_any = false;
598        loop {
599            match self.peek_token().clone() {
600                Token::EOF => break,
601                // An at-rule starts a new top-level item. Preserve it when an
602                // invalid selector prefix (for example a concatenated BOM)
603                // was the item that failed, instead of swallowing the whole
604                // following @media block during recovery.
605                Token::AtKeyword(_) if depth == 0 && consumed_any => break,
606                Token::Delim('{') => {
607                    depth += 1;
608                    self.consume_token();
609                    consumed_any = true;
610                }
611                Token::Delim('}') => {
612                    self.consume_token();
613                    if depth <= 1 {
614                        break;
615                    }
616                    depth -= 1;
617                    consumed_any = true;
618                }
619                Token::Delim(';') if depth == 0 => {
620                    self.consume_token();
621                    break;
622                }
623                _ => {
624                    self.consume_token();
625                    consumed_any = true;
626                }
627            }
628        }
629    }
630
631    fn parse_at_rule(&mut self) -> ParseResult<CssNode> {
632        // 1. consume '@' token
633        let at_name = if let Token::AtKeyword(name) = self.consume_token() {
634            name
635        } else {
636            return Err(ParserError {
637                kind: ParserErrorKind::UnexpectedToken {
638                    expected: "@keyword",
639                    found: format!("{:?}", self.peek_token()),
640                },
641                context: vec![],
642            });
643        };
644
645        // 2. Collect prelude tokens (until '{' or ';'), handling nested parentheses
646        let mut prelude = vec![];
647        let mut paren_depth = 0;
648
649        loop {
650            match self.peek_token() {
651                Token::Delim('{') if paren_depth == 0 => break,
652                Token::Delim(';') if paren_depth == 0 => break,
653                Token::Delim('(') => {
654                    paren_depth += 1;
655                    prelude.push(self.consume_token());
656                }
657                Token::Delim(')') => {
658                    paren_depth -= 1;
659                    prelude.push(self.consume_token());
660                }
661                Token::EOF => break,
662                _ => prelude.push(self.consume_token()),
663            }
664        }
665
666        // 3. Convert prelude tokens to CssValue (handles functions and nested parentheses)
667        let params = Self::parse_at_query(prelude).map_err(|e| {
668            e.with_context("parse_at_rule: failed to parse params via parse_at_query")
669        })?;
670
671        // 4. Block vs semicolon
672        let children = if self.peek_token() == &Token::Delim('{') {
673            self.consume_token();
674            self.brace_depth += 1;
675
676            let mut children = vec![];
677            while self.peek_token() != &Token::Delim('}') {
678                match self.peek_token() {
679                    Token::EOF => {
680                        return Err(ParserError {
681                            kind: ParserErrorKind::UnexpectedEOF,
682                            context: vec![],
683                        });
684                    }
685                    Token::Whitespace => {
686                        self.consume_token();
687                    }
688                    Token::AtKeyword(_) => {
689                        let node = self.parse_at_rule().map_err(|e| {
690                            e.with_context("parse_at_rule: failed to parse nested at-rule")
691                        })?;
692                        children.push(node);
693                    }
694                    _ => {
695                        let mut cursor = 0;
696                        let mut is_declaration = false;
697
698                        loop {
699                            match self.peek_next_token(cursor) {
700                                Token::Delim('{') => {
701                                    break;
702                                }
703                                Token::Delim('}') => {
704                                    is_declaration = true;
705                                    break;
706                                }
707                                Token::EOF => {
708                                    return Err(ParserError {
709                                        kind: ParserErrorKind::UnexpectedEOF,
710                                        context: vec![],
711                                    });
712                                }
713                                _ => {}
714                            }
715                            cursor += 1;
716                        }
717
718                        let nodes = if is_declaration {
719                            self.parse_declaration_and_nested_rule_list().map_err(|e| {
720                                e.with_context(
721                                    "parse_at_rule: failed to parse declaration in block",
722                                )
723                            })?
724                        } else {
725                            vec![self.parse_rule().map_err(|e| {
726                                e.with_context("parse_at_rule: failed to parse rule in block")
727                            })?]
728                        };
729
730                        children.extend(nodes);
731                    }
732                }
733            }
734
735            self.consume_token(); // consume '}'
736            self.brace_depth -= 1;
737            children
738        } else {
739            if self.consume_token() != Token::Delim(';') {
740                return Err(ParserError {
741                    kind: ParserErrorKind::UnexpectedToken {
742                        expected: ";",
743                        found: format!("{:?}", self.peek_token()),
744                    },
745                    context: vec![],
746                });
747            }
748            vec![]
749        };
750
751        Ok(CssNode {
752            node: CssNodeType::AtRule {
753                name: at_name,
754                params,
755            },
756            children,
757        })
758    }
759
760    fn parse_at_query(tokens: Vec<Token>) -> ParseResult<AtQuery> {
761        let mut cursor = 0;
762        let items = Self::parse_at_query_list(&tokens, &mut cursor)?;
763        Ok(AtQuery::Group(items))
764    }
765
766    fn parse_at_query_list(tokens: &[Token], cursor: &mut usize) -> ParseResult<Vec<AtQuery>> {
767        let mut items = Vec::new();
768
769        while *cursor < tokens.len() {
770            match &tokens[*cursor] {
771                Token::Whitespace => {
772                    *cursor += 1;
773                }
774
775                Token::Delim('(') => {
776                    *cursor += 1;
777                    let group = Self::parse_at_query_list(tokens, cursor)?;
778                    items.push(AtQuery::Group(group));
779                }
780
781                Token::Delim(')') => {
782                    *cursor += 1;
783                    break;
784                }
785
786                Token::Ident(_) | Token::Number(_) | Token::Dimension(_, _) => {
787                    items.push(Self::parse_at_query_item(tokens, cursor)?);
788                }
789
790                Token::Delim(',') => {
791                    items.push(AtQuery::Keyword(",".into()));
792                    *cursor += 1;
793                }
794
795                _ => {
796                    *cursor += 1;
797                }
798            }
799        }
800
801        Ok(items)
802    }
803
804    fn parse_at_query_item(tokens: &[Token], cursor: &mut usize) -> ParseResult<AtQuery> {
805        let start = *cursor;
806
807        // Try a range beginning with a media feature.
808        if let Some(range) = Self::parse_at_query_range(tokens, cursor)? {
809            return Ok(range);
810        }
811
812        *cursor = start;
813
814        let name = match &tokens[*cursor] {
815            Token::Ident(s) => s.clone(),
816            _ => {
817                return Err(ParserError {
818                    kind: ParserErrorKind::UnexpectedToken {
819                        expected: "Ident(_)",
820                        found: format!("{:?}", tokens[*cursor]),
821                    },
822                    context: vec![],
823                });
824            }
825        };
826        *cursor += 1;
827
828        let mut colon = *cursor;
829        while matches!(
830            tokens.get(colon),
831            Some(Token::Whitespace | Token::Comment(_))
832        ) {
833            colon += 1;
834        }
835        if matches!(tokens.get(colon), Some(Token::Delim(':'))) {
836            *cursor = colon + 1;
837            let value = Self::parse_at_query_value(tokens, cursor)?;
838            Ok(AtQuery::Condition { name, value })
839        } else {
840            Ok(AtQuery::Keyword(name))
841        }
842    }
843
844    fn parse_at_query_range(tokens: &[Token], cursor: &mut usize) -> ParseResult<Option<AtQuery>> {
845        let start = *cursor;
846
847        // `width <= 1044px`
848        if let Token::Ident(name) = tokens.get(*cursor).cloned().unwrap() {
849            *cursor += 1;
850            Self::skip_at_query_whitespace(tokens, cursor);
851
852            if let Some(operator) = Self::parse_range_operator(tokens, cursor) {
853                Self::skip_at_query_whitespace(tokens, cursor);
854
855                if let Some(value) = Self::try_parse_at_query_value(tokens, cursor)? {
856                    return Ok(Some(AtQuery::Range {
857                        left: None,
858                        name,
859                        right: Some((operator, value)),
860                    }));
861                }
862            }
863        }
864
865        *cursor = start;
866
867        // `600px <= width`
868        let Some(left) = Self::try_parse_at_query_value(tokens, cursor)? else {
869            return Ok(None);
870        };
871
872        Self::skip_at_query_whitespace(tokens, cursor);
873
874        let Some(left_operator) = Self::parse_range_operator(tokens, cursor) else {
875            *cursor = start;
876            return Ok(None);
877        };
878
879        Self::skip_at_query_whitespace(tokens, cursor);
880
881        let name = match tokens.get(*cursor) {
882            Some(Token::Ident(name)) => name.clone(),
883            _ => {
884                *cursor = start;
885                return Ok(None);
886            }
887        };
888        *cursor += 1;
889
890        Self::skip_at_query_whitespace(tokens, cursor);
891
892        // Optional second range: `600px <= width <= 1044px`
893        let right = if let Some(operator) = Self::parse_range_operator(tokens, cursor) {
894            Self::skip_at_query_whitespace(tokens, cursor);
895
896            let Some(value) = Self::try_parse_at_query_value(tokens, cursor)? else {
897                *cursor = start;
898                return Ok(None);
899            };
900
901            Some((operator, value))
902        } else {
903            None
904        };
905
906        Ok(Some(AtQuery::Range {
907            left: Some((left, left_operator)),
908            name,
909            right,
910        }))
911    }
912
913    fn parse_range_operator(tokens: &[Token], cursor: &mut usize) -> Option<RangeOperator> {
914        match tokens.get(*cursor) {
915            Some(Token::Delim('<')) => {
916                if matches!(tokens.get(*cursor + 1), Some(Token::Delim('='))) {
917                    *cursor += 2;
918                    Some(RangeOperator::LessEqual)
919                } else {
920                    *cursor += 1;
921                    Some(RangeOperator::Less)
922                }
923            }
924
925            Some(Token::Delim('>')) => {
926                if matches!(tokens.get(*cursor + 1), Some(Token::Delim('='))) {
927                    *cursor += 2;
928                    Some(RangeOperator::GreaterEqual)
929                } else {
930                    *cursor += 1;
931                    Some(RangeOperator::Greater)
932                }
933            }
934
935            Some(Token::Delim('=')) => {
936                *cursor += 1;
937                Some(RangeOperator::Equal)
938            }
939
940            _ => None,
941        }
942    }
943
944    fn skip_at_query_whitespace(tokens: &[Token], cursor: &mut usize) {
945        while matches!(
946            tokens.get(*cursor),
947            Some(Token::Whitespace | Token::Comment(_))
948        ) {
949            *cursor += 1;
950        }
951    }
952
953    fn try_parse_at_query_value(
954        tokens: &[Token],
955        cursor: &mut usize,
956    ) -> ParseResult<Option<CssValue>> {
957        let start = *cursor;
958
959        match tokens.get(*cursor) {
960            Some(Token::Number(_)) | Some(Token::Dimension(_, _)) | Some(Token::Function(_)) => {
961                match Self::parse_at_query_value(tokens, cursor) {
962                    Ok(value) => Ok(Some(value)),
963                    Err(_) => {
964                        *cursor = start;
965                        Ok(None)
966                    }
967                }
968            }
969
970            _ => Ok(None),
971        }
972    }
973
974    fn parse_at_query_value(tokens: &[Token], cursor: &mut usize) -> ParseResult<CssValue> {
975        let mut buf = Vec::new();
976        let mut paren_depth = 0;
977
978        while *cursor < tokens.len() {
979            match &tokens[*cursor] {
980                Token::Delim('(') => {
981                    paren_depth += 1;
982                    buf.push(tokens[*cursor].clone());
983                    *cursor += 1;
984                }
985                Token::Delim(')') if paren_depth == 0 => break,
986                Token::Delim(')') => {
987                    paren_depth -= 1;
988                    buf.push(tokens[*cursor].clone());
989                    *cursor += 1;
990                }
991                _ => {
992                    buf.push(tokens[*cursor].clone());
993                    *cursor += 1;
994                }
995            }
996        }
997
998        Self::parse_tokens_to_css_value(buf)
999    }
1000
1001    /// Parse a qualified rule (e.g., `div { color: red; }`).
1002    ///
1003    /// Parses the selector list first, then the block of declarations.
1004    fn parse_rule(&mut self) -> ParseResult<CssNode> {
1005        // 1. Parse selectors
1006        let selectors = self.parse_selector_list();
1007
1008        // 2. Expect `{`
1009        match self.consume_token() {
1010            Token::Delim('{') => self.brace_depth += 1,
1011            token => {
1012                return Err(ParserError {
1013                    kind: ParserErrorKind::UnexpectedToken {
1014                        expected: "{",
1015                        found: format!("{:?}", token),
1016                    },
1017                    context: vec![format!(
1018                        "While parsing rule with selectors: {}",
1019                        selectors
1020                            .iter()
1021                            .map(|s| format!("{:?}", s))
1022                            .collect::<Vec<_>>()
1023                            .join(", ")
1024                    )],
1025                });
1026            }
1027        }
1028
1029        // 3. Parse declarations inside the block
1030        let mut children = vec![];
1031        loop {
1032            let token = self.peek_token().clone();
1033            match token {
1034                Token::Delim('}') => {
1035                    self.consume_token();
1036                    self.brace_depth -= 1;
1037                    break;
1038                }
1039                Token::EOF => {
1040                    return Err(ParserError {
1041                        kind: ParserErrorKind::UnexpectedEOF,
1042                        context: vec![],
1043                    });
1044                }
1045                _ => {
1046                    let mut child = self.parse_declaration_and_nested_rule_list().map_err(|e| {
1047                        e.with_context("parse_rule: failed to parse declaration list")
1048                    })?;
1049                    children.append(&mut child);
1050                }
1051            }
1052        }
1053
1054        Ok(CssNode {
1055            node: CssNodeType::Rule { selectors },
1056            children,
1057        })
1058    }
1059
1060    /// Parse a comma-separated list of selectors for a rule.
1061    ///
1062    /// Each selector is represented as a `ComplexSelector`.
1063    fn parse_selector_list(&mut self) -> Vec<ComplexSelector> {
1064        self.parse_selector_list_until(None)
1065    }
1066
1067    /// Parse a selector list up to a rule block or a functional pseudo-class
1068    /// closing delimiter.
1069    fn parse_selector_list_until(&mut self, terminator: Option<char>) -> Vec<ComplexSelector> {
1070        let mut selectors = vec![];
1071        let mut parts = vec![];
1072
1073        let mut current_selector: Option<Selector> = None;
1074        let mut current_combinator: Option<Combinator> = None;
1075
1076        loop {
1077            let token = self.peek_token().clone();
1078            match token {
1079                Token::Ident(name) => {
1080                    let sel = current_selector.get_or_insert_with(|| Selector {
1081                        is_nesting: false,
1082                        tag: None,
1083                        id: None,
1084                        classes: vec![],
1085                        attributes: vec![],
1086                        pseudo_classes: vec![],
1087                        pseudo_element: None,
1088                    });
1089
1090                    if sel.tag.is_none() {
1091                        sel.tag = Some(name);
1092                    }
1093
1094                    self.consume_token();
1095                }
1096
1097                Token::Hash(id) => {
1098                    let sel = current_selector.get_or_insert_with(|| Selector {
1099                        is_nesting: false,
1100                        tag: None,
1101                        id: None,
1102                        classes: vec![],
1103                        attributes: vec![],
1104                        pseudo_classes: vec![],
1105                        pseudo_element: None,
1106                    });
1107                    sel.id = Some(id);
1108                    self.consume_token();
1109                }
1110
1111                Token::Delim('.') => {
1112                    self.consume_token();
1113                    if let Token::Ident(class) = self.consume_token() {
1114                        let sel = current_selector.get_or_insert_with(|| Selector {
1115                            is_nesting: false,
1116                            tag: None,
1117                            id: None,
1118                            classes: vec![],
1119                            attributes: vec![],
1120                            pseudo_classes: vec![],
1121                            pseudo_element: None,
1122                        });
1123                        sel.classes.push(class);
1124                    }
1125                }
1126
1127                Token::Delim(':') => {
1128                    self.consume_token();
1129                    if self.peek_token() == &Token::Delim(':') {
1130                        // pseudo-element
1131                        self.consume_token();
1132                        if let Token::Ident(name) = self.consume_token() {
1133                            let sel = current_selector.get_or_insert_with(|| Selector {
1134                                is_nesting: false,
1135                                tag: None,
1136                                id: None,
1137                                classes: vec![],
1138                                attributes: vec![],
1139                                pseudo_classes: vec![],
1140                                pseudo_element: None,
1141                            });
1142                            sel.pseudo_element = Some(name);
1143                        }
1144                    } else {
1145                        let pseudo_class = match self.consume_token() {
1146                            Token::Ident(name) => Some(PseudoClass::Simple(name)),
1147                            Token::Function(name) => {
1148                                if self.peek_token() == &Token::Delim('(') {
1149                                    self.consume_token();
1150                                }
1151                                let lower_name = name.to_ascii_lowercase();
1152                                let pseudo = match lower_name.as_str() {
1153                                    "is" | "where" | "not" => PseudoClass::SelectorList {
1154                                        name: lower_name,
1155                                        selectors: self.parse_selector_list_until(Some(')')),
1156                                    },
1157                                    "nth-child" | "nth-last-child" | "nth-of-type"
1158                                    | "nth-last-of-type" => {
1159                                        let tokens = self.consume_until_closing_parenthesis();
1160                                        let (a, b) =
1161                                            parse_an_plus_b(&tokens).unwrap_or((0, i32::MIN));
1162                                        PseudoClass::Nth {
1163                                            name: lower_name,
1164                                            a,
1165                                            b,
1166                                        }
1167                                    }
1168                                    _ => {
1169                                        self.consume_until_closing_parenthesis();
1170                                        PseudoClass::SelectorList {
1171                                            name: lower_name,
1172                                            selectors: Vec::new(),
1173                                        }
1174                                    }
1175                                };
1176                                if self.peek_token() == &Token::Delim(')') {
1177                                    self.consume_token();
1178                                }
1179                                Some(pseudo)
1180                            }
1181                            _ => None,
1182                        };
1183                        if let Some(pseudo_class) = pseudo_class {
1184                            let sel = current_selector.get_or_insert_with(|| Selector {
1185                                is_nesting: false,
1186                                tag: None,
1187                                id: None,
1188                                classes: vec![],
1189                                attributes: vec![],
1190                                pseudo_classes: vec![],
1191                                pseudo_element: None,
1192                            });
1193                            sel.pseudo_classes.push(pseudo_class);
1194                        }
1195                    }
1196                }
1197
1198                Token::Delim('[') => {
1199                    self.consume_token();
1200                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1201                        self.consume_token();
1202                    }
1203
1204                    let name = match self.consume_token() {
1205                        Token::Ident(name) => name,
1206                        _ => continue,
1207                    };
1208
1209                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1210                        self.consume_token();
1211                    }
1212
1213                    let operator = match self.peek_token() {
1214                        Token::Delim('=') => {
1215                            self.consume_token();
1216                            AttributeSelectorOperator::Equals
1217                        }
1218                        Token::Delim('~')
1219                        | Token::Delim('|')
1220                        | Token::Delim('^')
1221                        | Token::Delim('$')
1222                        | Token::Delim('*') => {
1223                            let operator = match self.consume_token() {
1224                                Token::Delim(c) => c,
1225                                _ => unreachable!(),
1226                            };
1227
1228                            if self.peek_token() != &Token::Delim('=') {
1229                                continue;
1230                            }
1231                            self.consume_token();
1232
1233                            match operator {
1234                                '~' => AttributeSelectorOperator::Includes,
1235                                '|' => AttributeSelectorOperator::DashMatch,
1236                                '^' => AttributeSelectorOperator::Prefix,
1237                                '$' => AttributeSelectorOperator::Suffix,
1238                                '*' => AttributeSelectorOperator::Substring,
1239                                _ => unreachable!(),
1240                            }
1241                        }
1242                        _ => AttributeSelectorOperator::Exists,
1243                    };
1244
1245                    let value = if operator == AttributeSelectorOperator::Exists {
1246                        None
1247                    } else {
1248                        while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1249                            self.consume_token();
1250                        }
1251
1252                        match self.consume_token() {
1253                            Token::Ident(value) | Token::String(value) => Some(value),
1254                            _ => continue,
1255                        }
1256                    };
1257
1258                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1259                        self.consume_token();
1260                    }
1261                    if self.peek_token() == &Token::Delim(']') {
1262                        self.consume_token();
1263                        let sel = current_selector.get_or_insert_with(|| Selector {
1264                            is_nesting: false,
1265                            tag: None,
1266                            id: None,
1267                            classes: vec![],
1268                            attributes: vec![],
1269                            pseudo_classes: vec![],
1270                            pseudo_element: None,
1271                        });
1272                        sel.attributes.push(AttributeSelector {
1273                            name,
1274                            operator,
1275                            value,
1276                        });
1277                    }
1278                }
1279
1280                Token::Whitespace | Token::Comment(_) => {
1281                    // descendant combinator
1282                    if let Some(sel) = current_selector.take() {
1283                        parts.push(SelectorPart {
1284                            selector: sel,
1285                            combinator: current_combinator.take(),
1286                        });
1287                    }
1288                    if current_combinator.is_none() {
1289                        current_combinator = Some(Combinator::Descendant);
1290                    }
1291                    self.consume_token();
1292                }
1293
1294                Token::Delim('>') => {
1295                    if let Some(sel) = current_selector.take() {
1296                        parts.push(SelectorPart {
1297                            selector: sel,
1298                            combinator: current_combinator.take(),
1299                        });
1300                    }
1301                    current_combinator = Some(Combinator::Child);
1302                    self.consume_token();
1303                }
1304
1305                Token::Delim('+') | Token::Delim('~') => {
1306                    if let Some(sel) = current_selector.take() {
1307                        parts.push(SelectorPart {
1308                            selector: sel,
1309                            combinator: current_combinator.take(),
1310                        });
1311                    }
1312                    current_combinator = Some(if token == Token::Delim('+') {
1313                        Combinator::NextSibling
1314                    } else {
1315                        Combinator::SubsequentSibling
1316                    });
1317                    self.consume_token();
1318                }
1319
1320                Token::Delim('*') => {
1321                    current_selector.get_or_insert_with(|| Selector {
1322                        is_nesting: false,
1323                        tag: None,
1324                        id: None,
1325                        classes: vec![],
1326                        attributes: vec![],
1327                        pseudo_classes: vec![],
1328                        pseudo_element: None,
1329                    });
1330                    self.consume_token();
1331                }
1332
1333                Token::Delim('&') => {
1334                    let sel = current_selector.get_or_insert_with(|| Selector {
1335                        is_nesting: false,
1336                        tag: None,
1337                        id: None,
1338                        classes: vec![],
1339                        attributes: vec![],
1340                        pseudo_classes: vec![],
1341                        pseudo_element: None,
1342                    });
1343                    sel.is_nesting = true;
1344                    self.consume_token();
1345                }
1346
1347                Token::Delim(',') => {
1348                    if let Some(sel) = current_selector.take() {
1349                        parts.push(SelectorPart {
1350                            selector: sel,
1351                            combinator: current_combinator.take(),
1352                        });
1353                    }
1354                    parts.reverse();
1355                    selectors.push(ComplexSelector {
1356                        parts: parts.clone(),
1357                    });
1358                    parts.clear();
1359                    current_combinator = None;
1360                    self.consume_token();
1361
1362                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1363                        self.consume_token();
1364                    }
1365                }
1366
1367                Token::Delim(')') if terminator == Some(')') => {
1368                    if let Some(sel) = current_selector.take() {
1369                        parts.push(SelectorPart {
1370                            selector: sel,
1371                            combinator: current_combinator.take(),
1372                        });
1373                    }
1374                    if !parts.is_empty() {
1375                        parts.reverse();
1376                        selectors.push(ComplexSelector { parts });
1377                    }
1378                    break;
1379                }
1380
1381                Token::Delim('{') | Token::EOF => {
1382                    if let Some(sel) = current_selector.take() {
1383                        parts.push(SelectorPart {
1384                            selector: sel,
1385                            combinator: current_combinator.take(),
1386                        });
1387                    }
1388                    if !parts.is_empty() {
1389                        parts.reverse();
1390                        selectors.push(ComplexSelector { parts });
1391                    }
1392                    break;
1393                }
1394
1395                // At-keywords cannot occur inside a qualified-rule prelude.
1396                // Leave the token untouched so parse_rule reports the invalid
1397                // prefix and lossy top-level recovery can resume at the at-rule.
1398                Token::AtKeyword(_) => break,
1399
1400                _ => {
1401                    self.consume_token();
1402                }
1403            }
1404        }
1405
1406        selectors
1407    }
1408
1409    /// Consume tokens through the matching `)` of a functional pseudo-class.
1410    fn consume_until_closing_parenthesis(&mut self) -> Vec<Token> {
1411        let mut tokens = Vec::new();
1412        let mut depth = 0;
1413        loop {
1414            match self.peek_token() {
1415                Token::EOF => break,
1416                Token::Delim(')') if depth == 0 => break,
1417                Token::Delim('(') => {
1418                    depth += 1;
1419                    tokens.push(self.consume_token());
1420                }
1421                Token::Delim(')') => {
1422                    depth -= 1;
1423                    tokens.push(self.consume_token());
1424                }
1425                _ => tokens.push(self.consume_token()),
1426            }
1427        }
1428        tokens
1429    }
1430
1431    /// Parse declarations and nested rules until `Token::Delim('}')`.
1432    fn parse_declaration_and_nested_rule_list(&mut self) -> ParseResult<Vec<CssNode>> {
1433        let mut children = vec![];
1434        let mut parsing_name = true;
1435        let mut name = String::new();
1436        let mut value_tokens = vec![];
1437
1438        loop {
1439            let mut cursor = 0;
1440
1441            loop {
1442                let token = self.peek_next_token(cursor);
1443
1444                match token {
1445                    Token::Delim(':') if parsing_name => {
1446                        for _ in 0..cursor {
1447                            if let Token::Ident(s) = self.consume_token() {
1448                                name.push_str(&s);
1449                            }
1450                        }
1451
1452                        self.consume_token(); // consume :
1453                        parsing_name = false;
1454                        break;
1455                    }
1456                    Token::Delim(';') if !parsing_name => {
1457                        for _ in 0..cursor {
1458                            value_tokens.push(self.consume_token());
1459                        }
1460
1461                        self.consume_token(); // consume ;
1462                        children.push(CssNode {
1463                            node: CssNodeType::Declaration {
1464                                name: std::mem::take(&mut name),
1465                                value: Self::parse_tokens_to_css_value(std::mem::take(
1466                                    &mut value_tokens,
1467                                ))
1468                                .map_err(|e| {
1469                                    e.with_context(
1470                                        "parse_declaration: failed to parse declaration value list",
1471                                    )
1472                                })?,
1473                            },
1474                            children: vec![],
1475                        });
1476
1477                        parsing_name = true;
1478                        break;
1479                    }
1480                    Token::Delim('{') => {
1481                        children.push(self.parse_rule()?);
1482                        cursor = 0;
1483                    }
1484                    Token::Delim('}') | Token::EOF => {
1485                        if !parsing_name && !name.is_empty() {
1486                            for _ in 0..cursor {
1487                                value_tokens.push(self.consume_token());
1488                            }
1489
1490                            children.push(CssNode {
1491                                node: CssNodeType::Declaration {
1492                                    name: std::mem::take(&mut name),
1493                                    value: Self::parse_tokens_to_css_value(std::mem::take(
1494                                        &mut value_tokens,
1495                                    ))?,
1496                                },
1497                                children: vec![],
1498                            });
1499                        } else {
1500                            for _ in 0..cursor {
1501                                // Just consume token.
1502                                self.consume_token();
1503                            }
1504                        }
1505
1506                        break;
1507                    }
1508                    Token::Ident(s) if parsing_name => {
1509                        if cursor == 0 {
1510                            name.push_str(s);
1511                            self.consume_token();
1512                            break;
1513                        }
1514
1515                        cursor += 1;
1516                    }
1517                    _ => {
1518                        cursor += 1;
1519                    }
1520                }
1521            }
1522
1523            if matches!(self.peek_next_token(0), Token::Delim('}') | Token::EOF) {
1524                break;
1525            }
1526        }
1527
1528        Ok(children)
1529    }
1530
1531    /// Parses the contents of a `(...)` functional group (excluding the outer
1532    /// parentheses) into a list of comma-separated arguments.
1533    ///
1534    /// The outer `Vec` holds comma-separated arguments and each inner `Vec`
1535    /// holds the whitespace-separated components of that argument. Keeping
1536    /// both boundaries preserves the syntactic structure so that, for example,
1537    /// `minmax(100px, 1fr)` yields two arguments while `circle(50% at 50%)`
1538    /// yields a single argument with several components.
1539    fn parse_function_arguments(tokens: Vec<Token>) -> ParseResult<Vec<Vec<CssValue>>> {
1540        // Split into comma-separated argument groups, respecting nesting.
1541        let mut arguments: Vec<Vec<Token>> = Vec::new();
1542        let mut current: Vec<Token> = Vec::new();
1543        let mut depth = 0usize;
1544
1545        for token in tokens {
1546            match token {
1547                Token::Delim('(') => {
1548                    depth += 1;
1549                    current.push(token);
1550                }
1551                Token::Delim(')') => {
1552                    depth = depth.saturating_sub(1);
1553                    current.push(token);
1554                }
1555                Token::Delim(',') if depth == 0 => {
1556                    arguments.push(std::mem::take(&mut current));
1557                }
1558                _ => current.push(token),
1559            }
1560        }
1561        if !current.is_empty() || arguments.is_empty() {
1562            arguments.push(current);
1563        }
1564
1565        // Within each argument, split on top-level whitespace into components.
1566        let mut result = Vec::new();
1567        for argument in arguments {
1568            let mut components = Vec::new();
1569            let mut component_tokens: Vec<Token> = Vec::new();
1570            let mut component_depth = 0usize;
1571
1572            for token in argument {
1573                match token {
1574                    Token::Whitespace if component_depth == 0 => {
1575                        if !component_tokens.is_empty() {
1576                            let value = Self::parse_tokens_to_css_value(std::mem::take(
1577                                &mut component_tokens,
1578                            ))?;
1579                            components.push(value);
1580                        }
1581                    }
1582                    Token::Delim('(') => {
1583                        component_depth += 1;
1584                        component_tokens.push(token);
1585                    }
1586                    Token::Delim(')') => {
1587                        component_depth = component_depth.saturating_sub(1);
1588                        component_tokens.push(token);
1589                    }
1590                    _ => component_tokens.push(token),
1591                }
1592            }
1593            if !component_tokens.is_empty() {
1594                let value = Self::parse_tokens_to_css_value(component_tokens)?;
1595                components.push(value);
1596            }
1597            result.push(components);
1598        }
1599
1600        Ok(result)
1601    }
1602
1603    pub fn parse_tokens_to_css_value(tokens: Vec<Token>) -> ParseResult<CssValue> {
1604        let mut values = vec![];
1605        let mut iter = tokens.into_iter().peekable();
1606
1607        while let Some(token) = iter.next() {
1608            log::debug!(target: "CssParser", "parse_tokens_to_css_value: token={:?}", token);
1609
1610            match token {
1611                Token::Ident(s) => values.push(CssValue::Keyword(s.into())),
1612
1613                Token::Delim(',') => {
1614                    // List separator
1615                    continue;
1616                }
1617
1618                Token::Delim('(') | Token::Delim(')') => {
1619                    // Function の構文用なので無視
1620                    continue;
1621                }
1622
1623                Token::Delim(c) => {
1624                    let mut s = [0_u8; 4];
1625                    let s = c.encode_utf8(&mut s);
1626
1627                    values.push(CssValue::Keyword(s.into()));
1628                }
1629
1630                Token::Number(n) => values.push(CssValue::Number(n)),
1631
1632                Token::String(s) => values.push(CssValue::String(s)),
1633
1634                Token::Url(raw) => values.push(CssValue::Function(
1635                    "url".to_string(),
1636                    vec![vec![CssValue::String(raw)]],
1637                )),
1638
1639                Token::Dimension(value, unit) => {
1640                    let unit = match unit.as_str() {
1641                        "px" => Unit::Px,
1642                        "cm" => Unit::Cm,
1643                        "mm" => Unit::Mm,
1644                        "in" => Unit::In,
1645                        "pt" => Unit::Pt,
1646                        "pc" => Unit::Pc,
1647                        "em" => Unit::Em,
1648                        "rem" => Unit::Rem,
1649                        "%" => Unit::Percent,
1650                        "vw" => Unit::Vw,
1651                        "vh" => Unit::Vh,
1652                        "vmin" => Unit::Vmin,
1653                        "vmax" => Unit::Vmax,
1654                        "deg" => Unit::Deg,
1655                        "fr" => Unit::Fr,
1656                        _ => Unit::Unknown,
1657                    };
1658                    values.push(CssValue::Length(value, unit));
1659                }
1660
1661                Token::Hash(s) => values.push(CssValue::Color(s)),
1662
1663                Token::Function(name) => {
1664                    // () の中を、外側の括弧を除いて集める
1665                    let mut depth = 0;
1666                    let mut func_tokens = vec![];
1667
1668                    for tok in iter.by_ref() {
1669                        match &tok {
1670                            Token::Delim('(') => {
1671                                depth += 1;
1672                                if depth > 1 {
1673                                    func_tokens.push(tok);
1674                                }
1675                            }
1676                            Token::Delim(')') => {
1677                                depth -= 1;
1678                                if depth == 0 {
1679                                    break;
1680                                }
1681                                func_tokens.push(tok);
1682                            }
1683                            _ => func_tokens.push(tok),
1684                        }
1685                    }
1686
1687                    let args = Self::parse_function_arguments(func_tokens)
1688                        .map_err(|e| e.with_context("parse function args"))?;
1689
1690                    values.push(CssValue::Function(name, args));
1691                }
1692
1693                _ => continue,
1694            }
1695        }
1696
1697        // 複数値なら List、単数ならそのまま
1698        Ok(match values.len() {
1699            0 => CssValue::Keyword(CssIdent::new_static("")),
1700            1 => values.remove(0),
1701            _ => CssValue::List(values),
1702        })
1703    }
1704}
1705
1706// ====================
1707impl fmt::Display for CssNode {
1708    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1709        fmt_tree_node(self, f, &[])
1710    }
1711}
1712
1713/// 再帰的にツリーを表示するヘルパー関数
1714fn fmt_tree_node(
1715    node: &CssNode,
1716    f: &mut fmt::Formatter<'_>,
1717    ancestors_last: &[bool],
1718) -> fmt::Result {
1719    let is_last = *ancestors_last.last().unwrap_or(&true);
1720    let connector = if ancestors_last.is_empty() {
1721        ""
1722    } else if is_last {
1723        "└── "
1724    } else {
1725        "├── "
1726    };
1727
1728    let mut prefix = String::new();
1729    for &ancestor_last in &ancestors_last[..ancestors_last.len().saturating_sub(1)] {
1730        prefix.push_str(if ancestor_last { "    " } else { "│   " });
1731    }
1732
1733    writeln!(f, "{}{}{:?}", prefix, connector, node.node())?;
1734
1735    let child_count = node.children().len();
1736    for (i, child) in node.children().iter().enumerate() {
1737        let child_is_last = i == child_count - 1;
1738        let mut new_ancestors = ancestors_last.to_vec();
1739        new_ancestors.push(child_is_last);
1740        fmt_tree_node(child, f, &new_ancestors)?;
1741    }
1742
1743    Ok(())
1744}
1745
1746impl std::fmt::Display for Combinator {
1747    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1748        match self {
1749            Combinator::Descendant => f.write_str(" "),
1750            Combinator::Child => f.write_str(" > "),
1751            Combinator::NextSibling => f.write_str(" + "),
1752            Combinator::SubsequentSibling => f.write_str(" ~ "),
1753        }
1754    }
1755}
1756
1757/// Formats the `An+B` microsyntax stored by [`PseudoClass::Nth`], e.g.
1758/// `2n+1`, `odd`-style `2n`, or a bare `3`.
1759fn write_an_plus_b(f: &mut std::fmt::Formatter<'_>, a: i32, b: i32) -> std::fmt::Result {
1760    if a == 0 {
1761        return write!(f, "{b}");
1762    }
1763    match a {
1764        1 => f.write_str("n")?,
1765        -1 => f.write_str("-n")?,
1766        _ => write!(f, "{a}n")?,
1767    }
1768    if b > 0 {
1769        write!(f, "+{b}")
1770    } else if b < 0 {
1771        write!(f, "{b}")
1772    } else {
1773        Ok(())
1774    }
1775}
1776
1777impl std::fmt::Display for PseudoClass {
1778    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1779        match self {
1780            PseudoClass::Simple(name) => write!(f, ":{name}"),
1781            PseudoClass::SelectorList { name, selectors } => {
1782                let arguments = selectors
1783                    .iter()
1784                    .map(ComplexSelector::to_string)
1785                    .collect::<Vec<_>>()
1786                    .join(", ");
1787                write!(f, ":{name}({arguments})")
1788            }
1789            PseudoClass::Nth { name, a, b } => {
1790                write!(f, ":{name}(")?;
1791                write_an_plus_b(f, *a, *b)?;
1792                f.write_str(")")
1793            }
1794        }
1795    }
1796}
1797
1798impl std::fmt::Display for Selector {
1799    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1800        if self.is_nesting {
1801            f.write_str("&")?;
1802        }
1803        if let Some(tag) = &self.tag {
1804            f.write_str(tag)?;
1805        }
1806        if let Some(id) = &self.id {
1807            write!(f, "#{id}")?;
1808        }
1809        for class in &self.classes {
1810            write!(f, ".{class}")?;
1811        }
1812        for attribute in &self.attributes {
1813            f.write_str("[")?;
1814            f.write_str(&attribute.name)?;
1815            match (&attribute.operator, &attribute.value) {
1816                (AttributeSelectorOperator::Exists, _) => {}
1817                (AttributeSelectorOperator::Equals, Some(value)) => {
1818                    write!(f, "=\"{value}\"")?;
1819                }
1820                (AttributeSelectorOperator::Includes, Some(value)) => {
1821                    write!(f, "~=\"{value}\"")?;
1822                }
1823                (AttributeSelectorOperator::DashMatch, Some(value)) => {
1824                    write!(f, "|=\"{value}\"")?;
1825                }
1826                (AttributeSelectorOperator::Prefix, Some(value)) => {
1827                    write!(f, "^=\"{value}\"")?;
1828                }
1829                (AttributeSelectorOperator::Suffix, Some(value)) => {
1830                    write!(f, "$=\"{value}\"")?;
1831                }
1832                (AttributeSelectorOperator::Substring, Some(value)) => {
1833                    write!(f, "*=\"{value}\"")?;
1834                }
1835
1836                _ => {}
1837            }
1838            f.write_str("]")?;
1839        }
1840        for pseudo_class in &self.pseudo_classes {
1841            write!(f, "{pseudo_class}")?;
1842        }
1843        if let Some(pseudo_element) = &self.pseudo_element {
1844            write!(f, "::{pseudo_element}")?;
1845        }
1846        Ok(())
1847    }
1848}
1849
1850impl std::fmt::Display for ComplexSelector {
1851    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1852        // Parts are stored right-to-left and each part carries the
1853        // combinator linking it to the part on its left (`parts[k]` holds the
1854        // relationship between `parts[k + 1]` and itself). Emitting left to
1855        // right therefore reads the combinator from the *next* part.
1856        for index in (0..self.parts.len()).rev() {
1857            write!(f, "{}", self.parts[index].selector)?;
1858            if index > 0
1859                && let Some(combinator) = self.parts[index - 1].combinator
1860            {
1861                write!(f, "{combinator}")?;
1862            }
1863        }
1864        Ok(())
1865    }
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870    use super::*;
1871
1872    #[test]
1873    fn complex_selector_display_renders_combinators_and_compounds() {
1874        let stylesheet = Parser::new("div.a > p#x + span:hover { color: red; }")
1875            .parse()
1876            .unwrap();
1877        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1878            panic!("expected a rule");
1879        };
1880        assert_eq!(selectors[0].to_string(), "div.a > p#x + span:hover");
1881
1882        let stylesheet = Parser::new("ul li ~ a[rel=\"tag\"] { color: red; }")
1883            .parse()
1884            .unwrap();
1885        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1886            panic!("expected a rule");
1887        };
1888        assert_eq!(selectors[0].to_string(), "ul li ~ a[rel=\"tag\"]");
1889    }
1890
1891    #[test]
1892    fn selector_display_renders_nth_arguments() {
1893        let stylesheet = Parser::new("li:nth-child(2n+1) { color: red; }")
1894            .parse()
1895            .unwrap();
1896        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1897            panic!("expected a rule");
1898        };
1899        assert_eq!(selectors[0].to_string(), "li:nth-child(2n+1)");
1900    }
1901
1902    #[test]
1903    fn parses_exact_attribute_selector() {
1904        let stylesheet = Parser::new(r#"input[type="hidden"] { display: none; }"#)
1905            .parse()
1906            .unwrap();
1907        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1908            panic!("expected CSS rule");
1909        };
1910        let selector = &selectors[0].parts[0].selector;
1911
1912        assert_eq!(selector.tag.as_deref(), Some("input"));
1913        assert_eq!(
1914            selector.attributes,
1915            vec![AttributeSelector {
1916                name: "type".into(),
1917                operator: AttributeSelectorOperator::Equals,
1918                value: Some("hidden".into()),
1919            }]
1920        );
1921    }
1922
1923    #[test]
1924    fn preserves_fractional_grid_units() {
1925        let stylesheet = Parser::new("main { grid-template-columns: 100px 2fr auto; }")
1926            .parse()
1927            .unwrap();
1928        let declaration = stylesheet.children()[0].children()[0].node();
1929        let CssNodeType::Declaration { value, .. } = declaration else {
1930            panic!("expected declaration");
1931        };
1932        assert_eq!(
1933            value,
1934            &CssValue::List(vec![
1935                CssValue::Length(100.0, Unit::Px),
1936                CssValue::Length(2.0, Unit::Fr),
1937                CssValue::Keyword("auto".into()),
1938            ])
1939        );
1940    }
1941
1942    #[test]
1943    fn preserves_grid_functions_and_area_strings() {
1944        let stylesheet = Parser::new(
1945            r#"main {
1946                grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
1947                grid-template-areas: "header header" "sidebar main";
1948            }"#,
1949        )
1950        .parse()
1951        .unwrap();
1952        let declarations = stylesheet.children()[0].children();
1953        let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
1954            panic!("expected declaration");
1955        };
1956        assert_eq!(
1957            value,
1958            &CssValue::Function(
1959                "repeat".into(),
1960                vec![
1961                    vec![CssValue::Keyword("auto-fit".into())],
1962                    vec![CssValue::Function(
1963                        "minmax".into(),
1964                        vec![
1965                            vec![CssValue::Length(100.0, Unit::Px)],
1966                            vec![CssValue::Length(1.0, Unit::Fr)],
1967                        ],
1968                    )],
1969                ],
1970            )
1971        );
1972        let CssNodeType::Declaration { value, .. } = declarations[1].node() else {
1973            panic!("expected declaration");
1974        };
1975        assert_eq!(
1976            value,
1977            &CssValue::List(vec![
1978                CssValue::String("header header".into()),
1979                CssValue::String("sidebar main".into()),
1980            ])
1981        );
1982    }
1983
1984    #[test]
1985    fn function_arguments_preserve_comma_and_whitespace_boundaries() {
1986        let stylesheet = Parser::new("a { width: foo(a + b, c, d); }")
1987            .parse()
1988            .unwrap();
1989        let declarations = stylesheet.children()[0].children();
1990        let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
1991            panic!("expected declaration");
1992        };
1993        assert_eq!(
1994            value,
1995            &CssValue::Function(
1996                "foo".into(),
1997                vec![
1998                    vec![
1999                        CssValue::Keyword("a".into()),
2000                        CssValue::Keyword("+".into()),
2001                        CssValue::Keyword("b".into()),
2002                    ],
2003                    vec![CssValue::Keyword("c".into())],
2004                    vec![CssValue::Keyword("d".into())],
2005                ],
2006            )
2007        );
2008    }
2009
2010    #[test]
2011    fn malformed_function_arguments_are_structurally_distinct() {
2012        let parse = |css: &str| {
2013            let stylesheet = Parser::new(&format!("a {{ width: {css}; }}"))
2014                .parse()
2015                .unwrap();
2016            let declarations = stylesheet.children()[0].children();
2017            let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
2018                panic!("expected declaration");
2019            };
2020            value.clone()
2021        };
2022
2023        // `foo(a + b, c, d)` — argument 1 is `a + b`.
2024        let comma_separated = parse("foo(a + b, c, d)");
2025        // `foo(a, + b c, d)` — argument 1 is `a`, argument 2 is `+ b c`.
2026        let whitespace_separated = parse("foo(a, + b c, d)");
2027
2028        assert_ne!(comma_separated, whitespace_separated);
2029    }
2030
2031    #[test]
2032    fn lossy_parser_resumes_after_recovering_a_failed_at_rule() {
2033        let mut parser = Parser::new("@media { @broken } .valid { color: green; }");
2034        let stylesheet = parser.parse_lossy();
2035
2036        assert_eq!(stylesheet.children().len(), 1);
2037        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
2038            panic!("expected recovered CSS rule");
2039        };
2040        assert_eq!(selectors[0].parts[0].selector.tag.as_deref(), None);
2041        assert_eq!(
2042            selectors[0].parts[0].selector.classes,
2043            vec![String::from("valid")]
2044        );
2045    }
2046
2047    #[test]
2048    fn nesting_without_ampersand_uses_descendant_combinator() {
2049        let stylesheet = Parser::new(".parent { span { color: red; } }")
2050            .parse()
2051            .unwrap();
2052        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2053            panic!("expected parent rule");
2054        };
2055        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2056        else {
2057            panic!("expected child rule");
2058        };
2059
2060        // Child selector should remain as-is (no & in child).
2061        assert_eq!(child[0].to_string(), "span");
2062
2063        // Nesting at resolver level: .parent span
2064        let resolved = parent[0].nest(&child[0]);
2065        assert_eq!(resolved.to_string(), ".parent span");
2066    }
2067
2068    #[test]
2069    fn nesting_with_standalone_ampersand() {
2070        let stylesheet = Parser::new(".parent { & { color: red; } }")
2071            .parse()
2072            .unwrap();
2073        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2074            panic!("expected parent rule");
2075        };
2076        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2077        else {
2078            panic!("expected child rule");
2079        };
2080
2081        assert!(child[0].parts[0].selector.is_nesting);
2082
2083        let resolved = parent[0].nest(&child[0]);
2084        assert_eq!(resolved.to_string(), ".parent");
2085    }
2086
2087    #[test]
2088    fn nesting_with_ampersand_class_compound() {
2089        let stylesheet = Parser::new(".parent { &.highlight { color: red; } }")
2090            .parse()
2091            .unwrap();
2092        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2093            panic!("expected parent rule");
2094        };
2095        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2096        else {
2097            panic!("expected child rule");
2098        };
2099
2100        assert!(child[0].parts[0].selector.is_nesting);
2101        assert_eq!(child[0].parts[0].selector.classes, vec!["highlight"]);
2102
2103        let resolved = parent[0].nest(&child[0]);
2104        assert_eq!(resolved.to_string(), ".parent.highlight");
2105    }
2106
2107    #[test]
2108    fn nesting_with_ampersand_at_end_of_compound() {
2109        let stylesheet = Parser::new(".parent { .sidebar& { color: red; } }")
2110            .parse()
2111            .unwrap();
2112        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2113            panic!("expected parent rule");
2114        };
2115        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2116        else {
2117            panic!("expected child rule");
2118        };
2119
2120        assert!(child[0].parts[0].selector.is_nesting);
2121        assert_eq!(child[0].parts[0].selector.classes, vec!["sidebar"]);
2122
2123        let resolved = parent[0].nest(&child[0]);
2124        assert_eq!(resolved.to_string(), ".parent.sidebar");
2125    }
2126
2127    #[test]
2128    fn nesting_with_ampersand_and_child_combinator() {
2129        let stylesheet = Parser::new(".parent { & > span { color: red; } }")
2130            .parse()
2131            .unwrap();
2132        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2133            panic!("expected parent rule");
2134        };
2135        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2136        else {
2137            panic!("expected child rule");
2138        };
2139
2140        let resolved = parent[0].nest(&child[0]);
2141        assert_eq!(resolved.to_string(), ".parent > span");
2142    }
2143
2144    #[test]
2145    fn nesting_with_multiple_selectors_using_ampersand() {
2146        let stylesheet = Parser::new(".parent { &.a, &.b { color: red; } }")
2147            .parse()
2148            .unwrap();
2149        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2150            panic!("expected parent rule");
2151        };
2152        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2153        else {
2154            panic!("expected child rule");
2155        };
2156
2157        assert_eq!(child.len(), 2);
2158        let resolved_a = parent[0].nest(&child[0]);
2159        let resolved_b = parent[0].nest(&child[1]);
2160        assert_eq!(resolved_a.to_string(), ".parent.a");
2161        assert_eq!(resolved_b.to_string(), ".parent.b");
2162    }
2163
2164    #[test]
2165    fn nesting_with_descendant_then_ampersand() {
2166        let stylesheet = Parser::new(".outer { .parent { &.highlight { color: red; } } }")
2167            .parse()
2168            .unwrap();
2169        let CssNodeType::Rule { selectors: outer } = stylesheet.children()[0].node() else {
2170            panic!("expected outer rule");
2171        };
2172        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].children()[0].node()
2173        else {
2174            panic!("expected parent rule");
2175        };
2176        let CssNodeType::Rule { selectors: child } =
2177            stylesheet.children()[0].children()[0].children()[0].node()
2178        else {
2179            panic!("expected child rule");
2180        };
2181
2182        // First level: .outer .parent
2183        let resolved_parent = outer[0].nest(&parent[0]);
2184        assert_eq!(resolved_parent.to_string(), ".outer .parent");
2185
2186        // Second level: .outer .parent.highlight
2187        let resolved_child = resolved_parent.nest(&child[0]);
2188        assert_eq!(resolved_child.to_string(), ".outer .parent.highlight");
2189    }
2190
2191    #[test]
2192    fn nesting_with_multilevel_ampersand_and_combinator() {
2193        let stylesheet = Parser::new("#id .parent { .a & .b > span { color: red; } }")
2194            .parse()
2195            .unwrap();
2196        let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2197            panic!("expected parent rule");
2198        };
2199        let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2200        else {
2201            panic!("expected child rule");
2202        };
2203
2204        let resolved = parent[0].nest(&child[0]);
2205        assert_eq!(resolved.to_string(), ".a #id .parent .b > span");
2206    }
2207
2208    #[test]
2209    fn nested_declaration_parsing() {
2210        let stylesheet = Parser::new(".parent { color: red; & { font-size: 14px; } }")
2211            .parse()
2212            .unwrap();
2213        let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
2214            panic!("expected parent rule");
2215        };
2216        assert_eq!(selectors[0].to_string(), ".parent");
2217
2218        let children = stylesheet.children()[0].children();
2219        assert_eq!(children.len(), 2);
2220
2221        // First child: declaration
2222        let CssNodeType::Declaration { name, .. } = children[0].node() else {
2223            panic!("expected declaration");
2224        };
2225        assert_eq!(name, "color");
2226
2227        // Second child: nested rule
2228        let CssNodeType::Rule { selectors: nested } = children[1].node() else {
2229            panic!("expected nested rule");
2230        };
2231        assert!(nested[0].parts[0].selector.is_nesting);
2232    }
2233}