Skip to main content

orinium_browser/engine/css/
parser.rs

1//! CSS Parser
2//!
3//! Consumes tokens produced by the tokenizer and builds
4//! higher-level CSS syntax structures.
5//!
6//! ## Responsibilities
7//! - Parse token streams into structured CSS data
8//!   (selectors, declarations, component values)
9//! - Handle nesting such as blocks and functions
10//!
11//! ## Non-responsibilities
12//! - Tokenization of raw input
13//! - Semantic interpretation (length resolution, color computation, etc.)
14//!
15//! ## Design notes
16//! - No property-specific validation is performed here
17//! - Semantic meaning is assigned in later stages (style computation, layout)
18use std::collections::VecDeque;
19use std::fmt;
20
21use crate::engine::css::values::CssIdent;
22
23use super::tokenizer::{Token, Tokenizer};
24use super::values::{CssValue, Unit};
25
26/// Node kinds used in the CSS syntax tree.
27///
28/// These nodes represent **syntactic structure only**.
29/// No semantic validation or value resolution is performed here.
30#[derive(Debug, Clone)]
31pub enum CssNodeType {
32    /// Root node of a CSS document
33    Stylesheet,
34
35    /// Qualified rule (e.g. `div { ... }`)
36    Rule {
37        /// Selectors associated with this rule
38        selectors: Vec<ComplexSelector>,
39    },
40
41    /// At-rule (e.g. `@media`, `@supports`)
42    AtRule {
43        /// At-rule name without `@`
44        name: String,
45
46        params: AtQuery,
47    },
48
49    /// Declaration inside a rule block (e.g. `color: red`)
50    Declaration {
51        /// Property name
52        name: String,
53
54        value: CssValue,
55    },
56}
57
58#[derive(Debug, Clone)]
59pub enum AtQuery {
60    Keyword(String), // screen, and, not
61    Condition {
62        name: String,    // max-width
63        value: CssValue, // 600px
64    },
65    Group(Vec<AtQuery>), // ( ... )
66}
67
68/// Node in the CSS syntax tree.
69///
70/// Each node represents a syntactic construct such as a rule,
71/// at-rule, or declaration, and may contain child nodes.
72#[derive(Debug)]
73pub struct CssNode {
74    /// Kind of this CSS node
75    node: CssNodeType,
76
77    /// Child nodes forming the tree structure
78    children: Vec<CssNode>,
79}
80
81impl CssNode {
82    pub fn node(&self) -> &CssNodeType {
83        &self.node
84    }
85    pub fn children(&self) -> &Vec<CssNode> {
86        &self.children
87    }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub struct Selector {
92    /// Type selector (e.g. `div`)
93    ///
94    /// `None` represents the absence of a type selector
95    /// (e.g. `.class`, `#id`).
96    pub tag: Option<String>,
97
98    /// ID selector (e.g. `#main`)
99    pub id: Option<String>,
100
101    /// Class selectors (e.g. `.container`)
102    pub classes: Vec<String>,
103
104    /// Pseudo-class (e.g. `:hover`)
105    pub pseudo_class: Option<String>,
106
107    /// Pseudo-element (e.g. `::before`)
108    pub pseudo_element: Option<String>,
109}
110
111/// Combinator defining the relationship between selectors.
112///
113/// Additional combinators (`>`, `+`, `~`) may be added later.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum Combinator {
116    /// Descendant combinator (` `)
117    Descendant,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
121pub struct SelectorPart {
122    /// Simple selector matched at this step
123    pub selector: Selector,
124
125    /// Relationship to the next selector on the left.
126    ///
127    /// `None` indicates this is the leftmost selector
128    /// in the selector sequence.
129    pub combinator: Option<Combinator>,
130}
131
132/// A complex CSS selector composed of multiple selector parts.
133///
134/// Selector parts are stored **from right to left** to match
135/// the order used during selector matching.
136///
137/// Example:
138/// ```text
139/// A B
140/// ```
141/// is stored as:
142/// ```text
143/// [
144///   B (Descendant),
145///   A (None)
146/// ]
147/// ```
148#[derive(Debug, Clone, PartialEq, Eq, Hash)]
149pub struct ComplexSelector {
150    pub parts: Vec<SelectorPart>,
151}
152
153/// CSS parser consuming tokens and producing syntax structures.
154pub struct Parser<'a> {
155    /// Source of tokens produced by the tokenizer
156    tokenizer: Tokenizer<'a>,
157
158    /// Used to detect the start and end of rule blocks (`{}`).
159    brace_depth: usize,
160
161    /// Lookahead token (optional)
162    ///
163    /// Parser may need to peek the next token without consuming it.
164    lookahead: VecDeque<Token>,
165}
166
167/// Parser error kinds
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub enum ParserErrorKind {
170    /// Expected a token but found something else
171    UnexpectedToken {
172        expected: &'static str,
173        found: String, // Token debug or value
174    },
175
176    /// Unexpected end of file
177    UnexpectedEOF,
178
179    /// Invalid or unsupported CSS syntax
180    InvalidSyntax,
181
182    /// Mismatched braces or parentheses
183    MismatchedDelimiter { expected: char, found: char },
184}
185
186impl fmt::Display for ParserErrorKind {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        write!(f, "{:?}", self)
189    }
190}
191
192/// Parser error
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct ParserError {
195    /// Kind of the error
196    pub kind: ParserErrorKind,
197    /// Context
198    pub context: Vec<String>,
199}
200
201impl ParserError {
202    pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
203        self.context.push(ctx.into());
204        self
205    }
206}
207
208impl fmt::Display for ParserError {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        let mut ctx = self.context.clone();
211        ctx.reverse();
212        write!(
213            f,
214            "CssParserError: {}, (Context:[{}])",
215            self.kind,
216            ctx.join(" <-")
217        )
218    }
219}
220
221impl std::error::Error for ParserError {}
222
223/// Result type for parser functions
224pub type ParseResult<T> = Result<T, ParserError>;
225
226impl<'a> Parser<'a> {
227    /// Create a new CSS parser from a source string.
228    pub fn new(input: &'a str) -> Self {
229        Self {
230            tokenizer: Tokenizer::new(input),
231            brace_depth: 0,
232            lookahead: VecDeque::new(),
233        }
234    }
235
236    fn ensure_lookahead(&mut self, n: usize) {
237        while self.lookahead.len() <= n {
238            let tok = self.tokenizer.next_token();
239            self.lookahead.push_back(tok);
240        }
241    }
242
243    fn peek_next_token(&mut self, cursor_size: usize) -> &Token {
244        self.ensure_lookahead(cursor_size);
245        &self.lookahead[cursor_size]
246    }
247
248    /// Consume and return the next token.
249    fn peek_token(&mut self) -> &Token {
250        self.peek_next_token(0)
251    }
252
253    fn consume_token(&mut self) -> Token {
254        if let Some(tok) = self.lookahead.pop_front() {
255            tok
256        } else {
257            self.tokenizer.next_token()
258        }
259    }
260
261    /// Parse the entire CSS source into a syntax tree.
262    ///
263    /// This method consumes tokens until `Token::EOF` is reached and constructs
264    /// a `CssNode` representing the stylesheet root.
265    ///
266    /// Parsing behavior:
267    /// - Whitespace tokens are ignored
268    /// - Qualified rules and at-rules are parsed into child nodes
269    /// - No semantic validation is performed
270    pub fn parse(&mut self) -> ParseResult<CssNode> {
271        let mut stylesheet = CssNode {
272            node: CssNodeType::Stylesheet,
273            children: vec![],
274        };
275
276        loop {
277            let token = self.peek_token().clone();
278
279            match token {
280                Token::EOF => break,
281                Token::Whitespace | Token::Comment(_) => {
282                    self.consume_token();
283                }
284                Token::AtKeyword(_) => {
285                    let node = self
286                        .parse_at_rule()
287                        .map_err(|e| e.with_context("parse: failed to parse at-rule"))?;
288                    log::debug!(target: "CssParser", "AtRule parsed: {:?}", &node);
289                    stylesheet.children.push(node);
290                }
291                _ => {
292                    let node = self
293                        .parse_rule()
294                        .map_err(|e| e.with_context("parse: failed to parse rule"))?;
295                    log::debug!(target: "CssParser", "Rule parsed: {:?}", &node);
296                    stylesheet.children.push(node);
297                }
298            }
299        }
300
301        Ok(stylesheet)
302    }
303
304    fn parse_at_rule(&mut self) -> ParseResult<CssNode> {
305        // 1. consume '@' token
306        let at_name = if let Token::AtKeyword(name) = self.consume_token() {
307            name
308        } else {
309            return Err(ParserError {
310                kind: ParserErrorKind::UnexpectedToken {
311                    expected: "@keyword",
312                    found: format!("{:?}", self.peek_token()),
313                },
314                context: vec![],
315            });
316        };
317
318        // 2. Collect prelude tokens (until '{' or ';'), handling nested parentheses
319        let mut prelude = vec![];
320        let mut paren_depth = 0;
321
322        loop {
323            match self.peek_token() {
324                Token::Delim('{') if paren_depth == 0 => break,
325                Token::Delim(';') if paren_depth == 0 => break,
326                Token::Delim('(') => {
327                    paren_depth += 1;
328                    prelude.push(self.consume_token());
329                }
330                Token::Delim(')') => {
331                    paren_depth -= 1;
332                    prelude.push(self.consume_token());
333                }
334                Token::EOF => break,
335                _ => prelude.push(self.consume_token()),
336            }
337        }
338
339        // 3. Convert prelude tokens to CssValue (handles functions and nested parentheses)
340        let params = Self::parse_at_query(prelude).map_err(|e| {
341            e.with_context("parse_at_rule: failed to parse params via parse_at_query")
342        })?;
343
344        // 4. Block vs semicolon
345        let children = if self.peek_token() == &Token::Delim('{') {
346            self.consume_token();
347            self.brace_depth += 1;
348
349            let mut children = vec![];
350            while self.peek_token() != &Token::Delim('}') {
351                match self.peek_token() {
352                    Token::EOF => {
353                        return Err(ParserError {
354                            kind: ParserErrorKind::UnexpectedEOF,
355                            context: vec![],
356                        });
357                    }
358                    Token::Whitespace => {
359                        self.consume_token();
360                    }
361                    Token::AtKeyword(_) => {
362                        let node = self.parse_at_rule().map_err(|e| {
363                            e.with_context("parse_at_rule: failed to parse nested at-rule")
364                        })?;
365                        children.push(node);
366                    }
367                    _ => {
368                        let mut cursor = 0;
369                        let mut is_declaration = false;
370
371                        loop {
372                            match self.peek_next_token(cursor) {
373                                Token::Delim('{') => {
374                                    break;
375                                }
376                                Token::Delim('}') => {
377                                    is_declaration = true;
378                                    break;
379                                }
380                                Token::EOF => {
381                                    return Err(ParserError {
382                                        kind: ParserErrorKind::UnexpectedEOF,
383                                        context: vec![],
384                                    });
385                                }
386                                _ => {}
387                            }
388                            cursor += 1;
389                        }
390
391                        let nodes = if is_declaration {
392                            self.parse_declaration_list().map_err(|e| {
393                                e.with_context(
394                                    "parse_at_rule: failed to parse declaration in block",
395                                )
396                            })?
397                        } else {
398                            vec![self.parse_rule().map_err(|e| {
399                                e.with_context("parse_at_rule: failed to parse rule in block")
400                            })?]
401                        };
402
403                        children.extend(nodes);
404                    }
405                }
406            }
407
408            self.consume_token(); // consume '}'
409            self.brace_depth -= 1;
410            children
411        } else {
412            if self.consume_token() != Token::Delim(';') {
413                return Err(ParserError {
414                    kind: ParserErrorKind::UnexpectedToken {
415                        expected: ";",
416                        found: format!("{:?}", self.peek_token()),
417                    },
418                    context: vec![],
419                });
420            }
421            vec![]
422        };
423
424        Ok(CssNode {
425            node: CssNodeType::AtRule {
426                name: at_name,
427                params,
428            },
429            children,
430        })
431    }
432
433    fn parse_at_query(tokens: Vec<Token>) -> ParseResult<AtQuery> {
434        let mut cursor = 0;
435        let items = Self::parse_at_query_list(&tokens, &mut cursor)?;
436        Ok(AtQuery::Group(items))
437    }
438
439    fn parse_at_query_list(tokens: &[Token], cursor: &mut usize) -> ParseResult<Vec<AtQuery>> {
440        let mut items = Vec::new();
441
442        while *cursor < tokens.len() {
443            match &tokens[*cursor] {
444                Token::Whitespace => {
445                    *cursor += 1;
446                }
447
448                Token::Delim('(') => {
449                    *cursor += 1;
450                    let group = Self::parse_at_query_list(tokens, cursor)?;
451                    items.push(AtQuery::Group(group));
452                }
453
454                Token::Delim(')') => {
455                    *cursor += 1;
456                    break;
457                }
458
459                Token::Ident(_) => {
460                    items.push(Self::parse_at_query_item(tokens, cursor)?);
461                }
462
463                _ => {
464                    *cursor += 1;
465                }
466            }
467        }
468
469        Ok(items)
470    }
471
472    fn parse_at_query_item(tokens: &[Token], cursor: &mut usize) -> ParseResult<AtQuery> {
473        let name = match &tokens[*cursor] {
474            Token::Ident(s) => s.clone(),
475            _ => unreachable!(),
476        };
477        *cursor += 1;
478
479        if matches!(tokens.get(*cursor), Some(Token::Delim(':'))) {
480            *cursor += 1;
481            let value = Self::parse_at_query_value(tokens, cursor)?;
482            Ok(AtQuery::Condition { name, value })
483        } else {
484            Ok(AtQuery::Keyword(name))
485        }
486    }
487
488    fn parse_at_query_value(tokens: &[Token], cursor: &mut usize) -> ParseResult<CssValue> {
489        let mut buf = Vec::new();
490        let mut paren_depth = 0;
491
492        while *cursor < tokens.len() {
493            match &tokens[*cursor] {
494                Token::Delim('(') => {
495                    paren_depth += 1;
496                    buf.push(tokens[*cursor].clone());
497                    *cursor += 1;
498                }
499                Token::Delim(')') if paren_depth == 0 => break,
500                Token::Delim(')') => {
501                    paren_depth -= 1;
502                    buf.push(tokens[*cursor].clone());
503                    *cursor += 1;
504                }
505                _ => {
506                    buf.push(tokens[*cursor].clone());
507                    *cursor += 1;
508                }
509            }
510        }
511
512        Self::parse_tokens_to_css_value(buf)
513    }
514
515    /// Parse a qualified rule (e.g., `div { color: red; }`).
516    ///
517    /// Parses the selector list first, then the block of declarations.
518    fn parse_rule(&mut self) -> ParseResult<CssNode> {
519        // 1. Parse selectors
520        let selectors = self.parse_selector_list();
521
522        // 2. Expect `{`
523        match self.consume_token() {
524            Token::Delim('{') => self.brace_depth += 1,
525            token => {
526                return Err(ParserError {
527                    kind: ParserErrorKind::UnexpectedToken {
528                        expected: "{",
529                        found: format!("{:?}", token),
530                    },
531                    context: vec![format!(
532                        "While parsing rule with selectors: {}",
533                        selectors
534                            .iter()
535                            .map(|s| format!("{:?}", s))
536                            .collect::<Vec<_>>()
537                            .join(", ")
538                    )],
539                });
540            }
541        }
542
543        // 3. Parse declarations inside the block
544        let mut children = vec![];
545        loop {
546            let token = self.peek_token().clone();
547            match token {
548                Token::Delim('}') => {
549                    self.consume_token();
550                    self.brace_depth -= 1;
551                    break;
552                }
553                Token::EOF => {
554                    return Err(ParserError {
555                        kind: ParserErrorKind::UnexpectedEOF,
556                        context: vec![],
557                    });
558                }
559                _ => {
560                    let mut decls = self.parse_declaration_list().map_err(|e| {
561                        e.with_context("parse_rule: failed to parse declaration list")
562                    })?;
563                    children.append(&mut decls);
564                }
565            }
566        }
567
568        Ok(CssNode {
569            node: CssNodeType::Rule { selectors },
570            children,
571        })
572    }
573
574    /// Parse a comma-separated list of selectors for a rule.
575    ///
576    /// Each selector is represented as a `ComplexSelector`.
577    fn parse_selector_list(&mut self) -> Vec<ComplexSelector> {
578        let mut selectors = vec![];
579        let mut parts = vec![];
580
581        let mut current_selector: Option<Selector> = None;
582        let mut current_combinator: Option<Combinator> = None;
583
584        loop {
585            let token = self.peek_token().clone();
586            match token {
587                Token::Ident(name) => {
588                    let sel = current_selector.get_or_insert_with(|| Selector {
589                        tag: None,
590                        id: None,
591                        classes: vec![],
592                        pseudo_class: None,
593                        pseudo_element: None,
594                    });
595
596                    if sel.tag.is_none() {
597                        sel.tag = Some(name);
598                    }
599
600                    self.consume_token();
601                }
602
603                Token::Hash(id) => {
604                    let sel = current_selector.get_or_insert_with(|| Selector {
605                        tag: None,
606                        id: None,
607                        classes: vec![],
608                        pseudo_class: None,
609                        pseudo_element: None,
610                    });
611                    sel.id = Some(id);
612                    self.consume_token();
613                }
614
615                Token::Delim('.') => {
616                    self.consume_token();
617                    if let Token::Ident(class) = self.consume_token() {
618                        let sel = current_selector.get_or_insert_with(|| Selector {
619                            tag: None,
620                            id: None,
621                            classes: vec![],
622                            pseudo_class: None,
623                            pseudo_element: None,
624                        });
625                        sel.classes.push(class);
626                    }
627                }
628
629                Token::Delim(':') => {
630                    self.consume_token();
631                    if self.peek_token() == &Token::Delim(':') {
632                        // pseudo-element
633                        self.consume_token();
634                        if let Token::Ident(name) = self.consume_token() {
635                            let sel = current_selector.get_or_insert_with(|| Selector {
636                                tag: None,
637                                id: None,
638                                classes: vec![],
639                                pseudo_class: None,
640                                pseudo_element: None,
641                            });
642                            sel.pseudo_element = Some(name);
643                        }
644                    } else if let Token::Ident(name) = self.consume_token() {
645                        let sel = current_selector.get_or_insert_with(|| Selector {
646                            tag: None,
647                            id: None,
648                            classes: vec![],
649                            pseudo_class: None,
650                            pseudo_element: None,
651                        });
652                        sel.pseudo_class = Some(name);
653                    }
654                }
655
656                Token::Whitespace | Token::Comment(_) => {
657                    // descendant combinator
658                    if let Some(sel) = current_selector.take() {
659                        parts.push(SelectorPart {
660                            selector: sel,
661                            combinator: current_combinator.take(),
662                        });
663                    }
664                    current_combinator = Some(Combinator::Descendant);
665                    self.consume_token();
666                }
667
668                Token::Delim(',') => {
669                    if let Some(sel) = current_selector.take() {
670                        parts.push(SelectorPart {
671                            selector: sel,
672                            combinator: current_combinator.take(),
673                        });
674                    }
675                    parts.reverse();
676                    selectors.push(ComplexSelector {
677                        parts: parts.clone(),
678                    });
679                    parts.clear();
680                    current_combinator = None;
681                    self.consume_token();
682
683                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
684                        self.consume_token();
685                    }
686                }
687
688                Token::Delim('{') | Token::EOF => {
689                    if let Some(sel) = current_selector.take() {
690                        parts.push(SelectorPart {
691                            selector: sel,
692                            combinator: current_combinator.take(),
693                        });
694                    }
695                    if !parts.is_empty() {
696                        parts.reverse();
697                        selectors.push(ComplexSelector { parts });
698                    }
699                    break;
700                }
701
702                _ => {
703                    self.consume_token();
704                }
705            }
706        }
707
708        selectors
709    }
710
711    /// Parse declaration until `Token::Delim('}')`.
712    fn parse_declaration_list(&mut self) -> ParseResult<Vec<CssNode>> {
713        let mut declarations = vec![];
714        let mut parsing_name = true;
715        let mut name = String::new();
716        let mut value_tokens = vec![];
717
718        loop {
719            let token = self.peek_token().clone();
720            match token {
721                Token::Delim(':') if parsing_name => {
722                    parsing_name = false;
723                    self.consume_token();
724                }
725                Token::Delim(';') if !parsing_name => {
726                    self.consume_token(); // consume ;
727                    declarations.push(CssNode {
728                        node: CssNodeType::Declaration {
729                            name: std::mem::take(&mut name),
730                            value: Self::parse_tokens_to_css_value(std::mem::take(
731                                &mut value_tokens,
732                            ))
733                            .map_err(|e| {
734                                e.with_context(
735                                    "parse_declaration: failed to parse declaration value list",
736                                )
737                            })?,
738                        },
739                        children: vec![],
740                    });
741                    parsing_name = true;
742                }
743                Token::Delim('}') | Token::EOF => {
744                    if !parsing_name && !name.is_empty() {
745                        declarations.push(CssNode {
746                            node: CssNodeType::Declaration {
747                                name: std::mem::take(&mut name),
748                                value: Self::parse_tokens_to_css_value(std::mem::take(
749                                    &mut value_tokens,
750                                ))?,
751                            },
752                            children: vec![],
753                        });
754                    }
755                    break;
756                }
757
758                Token::Ident(s) if parsing_name => {
759                    name.push_str(&s);
760                    self.consume_token();
761                }
762                _ => {
763                    if !parsing_name {
764                        value_tokens.push(self.consume_token());
765                    } else {
766                        self.consume_token(); // skip unsupported token in name
767                    }
768                }
769            }
770        }
771
772        Ok(declarations)
773    }
774
775    fn parse_tokens_to_css_value(tokens: Vec<Token>) -> ParseResult<CssValue> {
776        let mut values = vec![];
777        let mut iter = tokens.into_iter().peekable();
778
779        while let Some(token) = iter.next() {
780            log::debug!(target: "CssParser", "parse_tokens_to_css_value: token={:?}", token);
781
782            match token {
783                Token::Ident(s) => values.push(CssValue::Keyword(s.into())),
784
785                Token::Delim(',') => {
786                    // List separator
787                    continue;
788                }
789
790                Token::Delim('(') | Token::Delim(')') => {
791                    // Function の構文用なので無視
792                    continue;
793                }
794
795                Token::Delim(c) => {
796                    let mut s = [0_u8; 4];
797                    let s = c.encode_utf8(&mut s);
798
799                    values.push(CssValue::Keyword(s.into()));
800                }
801
802                Token::Number(n) => values.push(CssValue::Number(n)),
803
804                Token::String(s) => values.push(CssValue::String(s)),
805
806                Token::Dimension(value, unit) => {
807                    let unit = match unit.as_str() {
808                        "px" => Unit::Px,
809                        "em" => Unit::Em,
810                        "rem" => Unit::Rem,
811                        "%" => Unit::Percent,
812                        "vw" => Unit::Vw,
813                        "vh" => Unit::Vh,
814                        "deg" => Unit::Deg,
815                        _ => Unit::Px,
816                    };
817                    values.push(CssValue::Length(value, unit));
818                }
819
820                Token::Hash(s) => values.push(CssValue::Color(s)),
821
822                Token::Function(name) => {
823                    // () の中をそのまま集める
824                    let mut depth = 0;
825                    let mut func_tokens = vec![];
826
827                    for tok in iter.by_ref() {
828                        match &tok {
829                            Token::Delim('(') => {
830                                depth += 1;
831                                func_tokens.push(tok);
832                            }
833                            Token::Delim(')') => {
834                                func_tokens.push(tok);
835                                depth -= 1;
836                                if depth == 0 {
837                                    break;
838                                }
839                            }
840                            _ => func_tokens.push(tok),
841                        }
842                    }
843
844                    let arg_value = Self::parse_tokens_to_css_value(func_tokens)
845                        .map_err(|e| e.with_context("parse function args"))?;
846
847                    let args = match arg_value {
848                        CssValue::List(list) => list,
849                        other => vec![other],
850                    };
851
852                    values.push(CssValue::Function(name, args));
853                }
854
855                _ => continue,
856            }
857        }
858
859        // 複数値なら List、単数ならそのまま
860        Ok(match values.len() {
861            0 => CssValue::Keyword(CssIdent::new_static("")),
862            1 => values.remove(0),
863            _ => CssValue::List(values),
864        })
865    }
866}
867
868// ====================
869impl fmt::Display for CssNode {
870    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
871        fmt_tree_node(self, f, &[])
872    }
873}
874
875/// 再帰的にツリーを表示するヘルパー関数
876fn fmt_tree_node(
877    node: &CssNode,
878    f: &mut fmt::Formatter<'_>,
879    ancestors_last: &[bool],
880) -> fmt::Result {
881    let is_last = *ancestors_last.last().unwrap_or(&true);
882    let connector = if ancestors_last.is_empty() {
883        ""
884    } else if is_last {
885        "└── "
886    } else {
887        "├── "
888    };
889
890    let mut prefix = String::new();
891    for &ancestor_last in &ancestors_last[..ancestors_last.len().saturating_sub(1)] {
892        prefix.push_str(if ancestor_last { "    " } else { "│   " });
893    }
894
895    writeln!(f, "{}{}{:?}", prefix, connector, node.node())?;
896
897    let child_count = node.children().len();
898    for (i, child) in node.children().iter().enumerate() {
899        let child_is_last = i == child_count - 1;
900        let mut new_ancestors = ancestors_last.to_vec();
901        new_ancestors.push(child_is_last);
902        fmt_tree_node(child, f, &new_ancestors)?;
903    }
904
905    Ok(())
906}