From d75485e2c0efbe8976c91e1b310c00e40008a522 Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Sun, 10 Nov 2024 21:19:28 +0000 Subject: [PATCH] (ast/syntax) fix baddeclaration parsing --- crates/hdx_ast/src/css/properties/mod.rs | 2 +- crates/hdx_ast/src/css/stylerule.rs | 7 ++- crates/hdx_ast/src/syntax.rs | 39 ++++++++++++++++ crates/hdx_lexer/src/lib.rs | 6 +++ crates/hdx_parser/src/cursor.rs | 8 +++- crates/hdx_parser/src/traits/mod.rs | 4 +- crates/hdx_parser/src/traits/rules.rs | 57 ++++++++++++++++-------- 7 files changed, 100 insertions(+), 23 deletions(-) diff --git a/crates/hdx_ast/src/css/properties/mod.rs b/crates/hdx_ast/src/css/properties/mod.rs index 5a66b6e..9adb8c9 100644 --- a/crates/hdx_ast/src/css/properties/mod.rs +++ b/crates/hdx_ast/src/css/properties/mod.rs @@ -22,7 +22,7 @@ impl<'a> WriteCss<'a> for Custom<'a> { impl<'a> Parse<'a> for Custom<'a> { fn parse(parser: &mut Parser<'a>) -> ParserResult { - let old_state = parser.set_state(State::StopOnSemicolon); + let old_state = parser.set_state(State::StopOnSemicolon | State::Nested); parser .parse::() .inspect_err(|_| { diff --git a/crates/hdx_ast/src/css/stylerule.rs b/crates/hdx_ast/src/css/stylerule.rs index 77d35b5..8542575 100644 --- a/crates/hdx_ast/src/css/stylerule.rs +++ b/crates/hdx_ast/src/css/stylerule.rs @@ -1,4 +1,7 @@ -use crate::css::{properties::Property, selector::SelectorList}; +use crate::{ + css::{properties::Property, selector::SelectorList}, + syntax::BadDeclaration, +}; use hdx_derive::Visitable; use hdx_parser::{Block, Parse, Parser, QualifiedRule, Result as ParserResult, Spanned, Vec}; use hdx_writer::{CssWriter, OutputOption, Result as WriterResult, WriteCss}; @@ -24,6 +27,7 @@ impl<'a> Parse<'a> for StyleRule<'a> { impl<'a> QualifiedRule<'a> for StyleRule<'a> { type Block = StyleDeclaration<'a>; type Prelude = SelectorList<'a>; + type BadDeclaration = BadDeclaration; } impl<'a> WriteCss<'a> for StyleRule<'a> { @@ -114,6 +118,7 @@ mod tests { assert_parse!(StyleRule, ":nth-child(1) {\n\topacity: 0;\n}"); assert_parse!(StyleRule, ".foo {\n\t--bar: (baz);\n}"); assert_parse!(StyleRule, ".foo {\n\twidth: calc(1px + (var(--foo)) + 1px);\n}"); + assert_parse!(StyleRule, ".foo {--bar:1}", ".foo {\n\t--bar: 1;\n}"); } #[test] diff --git a/crates/hdx_ast/src/syntax.rs b/crates/hdx_ast/src/syntax.rs index c3aa59d..5962f1a 100644 --- a/crates/hdx_ast/src/syntax.rs +++ b/crates/hdx_ast/src/syntax.rs @@ -384,9 +384,48 @@ impl<'a> Parse<'a> for QualifiedRule<'a> { } } +pub struct BadDeclaration; +// https://drafts.csswg.org/css-syntax-3/#consume-the-remnants-of-a-bad-declaration +impl<'a> Parse<'a> for BadDeclaration { + fn parse(parser: &mut Parser<'a>) -> ParserResult { + // To consume the remnants of a bad declaration from a token stream input, given a bool nested: + // + // Process input: + loop { + let token = parser.peek::().unwrap(); + dbg!(parser.at_end(), token); + // + // + // + // + // Discard a token from input, and return nothing. + if parser.at_end() || token.kind() == Kind::Semicolon { + parser.hop(token); + return Ok(Self); + } + // <}-token> + // + // If nested is true, return nothing. Otherwise, discard a token. + if token.kind() == Kind::RightCurly { + if parser.is(State::Nested) { + return Ok(Self); + } else { + parser.hop(token); + } + } + // anything else + // + // Consume a component value from input, and do nothing. + // + parser.parse::()?; + } + } +} + impl<'a> QualifiedRuleTrait<'a> for QualifiedRule<'a> { type Block = Block<'a>; type Prelude = ComponentValues<'a>; + type BadDeclaration = BadDeclaration; } impl<'a> WriteCss<'a> for QualifiedRule<'a> { diff --git a/crates/hdx_lexer/src/lib.rs b/crates/hdx_lexer/src/lib.rs index ad4eecd..f483add 100644 --- a/crates/hdx_lexer/src/lib.rs +++ b/crates/hdx_lexer/src/lib.rs @@ -32,6 +32,12 @@ pub enum Feature { #[derive(Copy, Clone, PartialEq, Default, Hash)] pub struct LexerCheckpoint(pub(crate) Token); +impl LexerCheckpoint { + pub fn span(&self) -> Span { + self.0.span() + } +} + pub struct Lexer<'a> { source: &'a str, current_token: Token, diff --git a/crates/hdx_parser/src/cursor.rs b/crates/hdx_parser/src/cursor.rs index 2c344fb..f1823d6 100644 --- a/crates/hdx_parser/src/cursor.rs +++ b/crates/hdx_parser/src/cursor.rs @@ -1,4 +1,4 @@ -use hdx_lexer::{Include, Kind, LexerCheckpoint, Token}; +use hdx_lexer::{Include, Kind, LexerCheckpoint, Span, Token}; use crate::Parser; @@ -8,6 +8,12 @@ pub struct ParserCheckpoint { errors_pos: u8, } +impl ParserCheckpoint { + pub fn span(&self) -> Span { + self.checkpoint.span() + } +} + impl<'a> Parser<'a> { #[inline] pub fn offset(&self) -> u32 { diff --git a/crates/hdx_parser/src/traits/mod.rs b/crates/hdx_parser/src/traits/mod.rs index a4b44b0..81fa782 100644 --- a/crates/hdx_parser/src/traits/mod.rs +++ b/crates/hdx_parser/src/traits/mod.rs @@ -105,7 +105,9 @@ pub trait StyleSheet<'a>: Sized + Parse<'a> { return Ok(rules); } discard!(parser, CdcOrCdo); - rules.push(Self::Rule::parse_spanned(parser)?); + if let Ok(rule) = Self::Rule::parse_spanned(parser) { + rules.push(rule) + } } } } diff --git a/crates/hdx_parser/src/traits/rules.rs b/crates/hdx_parser/src/traits/rules.rs index d04517a..3a96ea3 100644 --- a/crates/hdx_parser/src/traits/rules.rs +++ b/crates/hdx_parser/src/traits/rules.rs @@ -1,5 +1,5 @@ use hdx_atom::Atom; -use hdx_lexer::{Kind, Spanned}; +use hdx_lexer::{Kind, Span, Spanned}; use smallvec::{smallvec, SmallVec}; use crate::{diagnostics, discard, parser::Parser, Delim, Result, State, Token, Vec}; @@ -62,18 +62,31 @@ pub trait QualifiedRule<'a>: Sized + Parse<'a> { type Prelude: Parse<'a>; type Block: Parse<'a>; + // QualifiedRules must be able to consume a bad declaration, for when + // a custom property like declaration is encountered. + type BadDeclaration: Parse<'a>; + // QualifiedRules must have a prelude, consequently parse_prelude must be // implemented. // parse_prelude is called right away, so could start with any token. fn parse_prelude(parser: &mut Parser<'a>) -> Result> { - Self::Prelude::parse_spanned(parser) + parser.parse_spanned::() } // QualifiedRules must have a block, consequently parse_prelude must be // implemented. // parse_block will always start with a {-token. fn parse_block(parser: &mut Parser<'a>) -> Result> { - Self::Block::parse_spanned(parser) + parser.parse_spanned::() + } + + // QualifiedRules must be able to consume a block from their input when encountering + // a custom property like declaration that doesn't end but opens a `{` block. This + // is implemented as parsing the existing block as that' simplifies downstream logic + // but consumers of this trait can instead opt to implement an optimised version of + // this which doesn't build up an AST and just throws away tokens. + fn consume_block(parser: &mut Parser<'a>) { + parser.parse::().ok(); } // https://drafts.csswg.org/css-syntax-3/#consume-a-qualified-rule @@ -96,32 +109,38 @@ pub trait QualifiedRule<'a>: Sized + Parse<'a> { } } - let mut potential_custom = false; - // <{-token> - // If the first two non- values of rule’s prelude are an whose value starts with "--" followed by a .... + // If the first two non- values of rule’s prelude are an whose value starts with "--" followed by a , then: let checkpoint = parser.checkpoint(); if let Some(token) = parser.peek::() { if token.is_dashed_ident() { parser.hop(token); - potential_custom = parser.peek::().is_some(); + if parser.peek::().is_some() { + // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing. + if parser.is(State::Nested) { + parser.rewind(checkpoint); + parser.parse::()?; + let token = parser.peek::().unwrap(); + Err(diagnostics::BadDeclaration(Span { + start: checkpoint.span().start, + end: token.span().end, + }))?; + // If nested is false, consume a block from input, and return nothing. + } else { + Self::consume_block(parser); + let token = parser.peek::().unwrap(); + Err(diagnostics::BadDeclaration(Span { + start: checkpoint.span().start, + end: token.span().end, + }))?; + } + } parser.rewind(checkpoint); } } - let prelude = Self::parse_prelude(parser); + let mut prelude = Self::parse_prelude(parser); - // <{-token> - // If the first two non- values of rule’s prelude are an whose value starts with "--" followed by a , then: - if potential_custom && parser.is(State::Nested) { - // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing. - parser.rewind(checkpoint); - todo!(); - } else if potential_custom { - // If nested is false, consume a block from input, and return nothing. - parser.rewind(checkpoint); - todo!(); - } // Otherwise, consume a block from input, and let child rules be the result. // If the first item of child rules is a list of declarations, // remove it from child rules and assign it to rule’s declarations.