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