From 18baff174fb08c0cdb47675978113f7eb1d9a3f8 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 19 Dec 2024 20:07:59 +0100 Subject: [PATCH] feat(css-definition-syntax): support boolean-expr (#58) See https://drafts.csswg.org/css-values-5/#boolean --- crates/css-definition-syntax/src/generate.rs | 9 ++++ crates/css-definition-syntax/src/parser.rs | 51 +++++++++++++++++--- crates/css-definition-syntax/src/walk.rs | 3 ++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/crates/css-definition-syntax/src/generate.rs b/crates/css-definition-syntax/src/generate.rs index 7b99661..3287ce9 100644 --- a/crates/css-definition-syntax/src/generate.rs +++ b/crates/css-definition-syntax/src/generate.rs @@ -72,6 +72,10 @@ fn internal_generate<'a>( let decorated = decorate(multiplier, node); format!("{}{}", terms, decorated) } + Node::BooleanExpr(boolean_expr) => { + let terms = internal_generate(&boolean_expr.term, decorate, force_braces, compact)?; + format!("", terms) + } Node::Token(token) => token.value.to_string(), Node::Property(property) => format!("<'{}'>", property.name), Node::Type(typ) => { @@ -198,6 +202,11 @@ mod tests { let node = parse(input)?; let result = generate(&node, Default::default()).unwrap(); assert_eq!(result, " [ [ '+' | '-' ] ]*"); + + let input = ""; + let node = parse(input)?; + let result = generate(&node, Default::default()).unwrap(); + assert_eq!(result, ""); Ok(()) } } diff --git a/crates/css-definition-syntax/src/parser.rs b/crates/css-definition-syntax/src/parser.rs index 5b137f7..78cf728 100644 --- a/crates/css-definition-syntax/src/parser.rs +++ b/crates/css-definition-syntax/src/parser.rs @@ -126,6 +126,10 @@ pub struct Group { pub disallow_empty: bool, pub explicit: bool, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BooleanExpr { + pub term: Box, +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Node { @@ -142,6 +146,7 @@ pub enum Node { Spaces(Spaces), AtKeyword(AtKeyword), Group(Group), + BooleanExpr(BooleanExpr), } impl Node { @@ -160,6 +165,7 @@ impl Node { Node::Spaces(_) => "Spaces", Node::AtKeyword(_) => "AtKeyword", Node::Group(_) => "Group", + Node::BooleanExpr(_) => "BooleanExpr", } } } @@ -474,6 +480,23 @@ fn read_type_range(tokenizer: &mut Tokenizer) -> Result Result { tokenizer.eat(LESS_THAN_SIGN)?; let mut name = scan_word(tokenizer, false)?; + if name == "boolean-expr" { + tokenizer.eat(LEFT_SQUARE_BRACKET)?; + let mut implicit_group = read_implicit_group(tokenizer, Some(RIGHT_SQUARE_BRACKET))?; + tokenizer.eat(RIGHT_SQUARE_BRACKET)?; + tokenizer.eat(GREATER_THAN_SIGN)?; + + return maybe_multiplied( + tokenizer, + Node::BooleanExpr(BooleanExpr { + term: Box::new(if implicit_group.terms.len() == 1 { + implicit_group.terms.pop().unwrap() + } else { + Node::Group(implicit_group) + }), + }), + ); + } if tokenizer.char_code() == LEFT_PARENTHESIS && tokenizer.next_char_code() == RIGHT_PARENTHESIS { @@ -575,12 +598,15 @@ fn regroup_terms( (terms, combinator) } -fn read_implicit_group(tokenizer: &mut Tokenizer) -> Result { +fn read_implicit_group( + tokenizer: &mut Tokenizer, + stop_char: Option, +) -> Result { let mut prev_token_pos = tokenizer.pos; let mut combinators = HashSet::new(); let mut terms = vec![]; - while let Some(token) = peek(tokenizer)? { + while let Some(token) = peek(tokenizer, stop_char)? { match (&token, terms.last()) { (Node::Spaces(Spaces { value: _ }), _) => continue, ( @@ -618,9 +644,12 @@ fn read_implicit_group(tokenizer: &mut Tokenizer) -> Result Result { +fn read_group( + tokenizer: &mut Tokenizer, + stop_char: Option, +) -> Result { tokenizer.eat(LEFT_SQUARE_BRACKET)?; - let mut group = read_implicit_group(tokenizer)?; + let mut group = read_implicit_group(tokenizer, stop_char)?; tokenizer.eat(RIGHT_SQUARE_BRACKET)?; group.explicit = true; @@ -633,8 +662,16 @@ fn read_group(tokenizer: &mut Tokenizer) -> Result Ok(Node::Group(group)) } -fn peek(tokenizer: &mut Tokenizer) -> Result, SyntaxDefinitionError> { +fn peek( + tokenizer: &mut Tokenizer, + stop_char: Option, +) -> Result, SyntaxDefinitionError> { let code = tokenizer.char_code(); + if let Some(stop_char) = stop_char { + if code == stop_char { + return Ok(None); + } + } if is_name_char(code) { return Ok(Some(read_keyword_or_function(tokenizer)?)); } @@ -642,7 +679,7 @@ fn peek(tokenizer: &mut Tokenizer) -> Result, SyntaxDefinitionError Ok(match code { RIGHT_SQUARE_BRACKET => None, LEFT_SQUARE_BRACKET => { - let group = read_group(tokenizer)?; + let group = read_group(tokenizer, stop_char)?; Some(maybe_multiplied(tokenizer, group)?) } LESS_THAN_SIGN => Some(if tokenizer.next_char_code() == APOSTROPHE { @@ -710,7 +747,7 @@ fn peek(tokenizer: &mut Tokenizer) -> Result, SyntaxDefinitionError pub fn parse(source: &str) -> Result { let mut tokenizer = Tokenizer::new(source); - let mut result = read_implicit_group(&mut tokenizer)?; + let mut result = read_implicit_group(&mut tokenizer, None)?; if tokenizer.pos != tokenizer.str.len() { return Err(SyntaxDefinitionError::ParseErrorUnexpectedInput); diff --git a/crates/css-definition-syntax/src/walk.rs b/crates/css-definition-syntax/src/walk.rs index 75ab05c..1f6387d 100644 --- a/crates/css-definition-syntax/src/walk.rs +++ b/crates/css-definition-syntax/src/walk.rs @@ -34,6 +34,9 @@ pub fn walk( Node::Multiplier(multiplier) => { walk(&multiplier.term, options, context)?; } + Node::BooleanExpr(booleann_exp) => { + walk(&booleann_exp.term, options, context)?; + } Node::Token(_) | Node::Property(_) | Node::Type(_)