Skip to content

Commit

Permalink
(ast/syntax) fix baddeclaration parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
keithamus committed Nov 10, 2024
1 parent e95f977 commit d75485e
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 23 deletions.
2 changes: 1 addition & 1 deletion crates/hdx_ast/src/css/properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
let old_state = parser.set_state(State::StopOnSemicolon);
let old_state = parser.set_state(State::StopOnSemicolon | State::Nested);
parser
.parse::<ComponentValues>()
.inspect_err(|_| {
Expand Down
7 changes: 6 additions & 1 deletion crates/hdx_ast/src/css/stylerule.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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> {
Expand Down Expand Up @@ -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]
Expand Down
39 changes: 39 additions & 0 deletions crates/hdx_ast/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
// To consume the remnants of a bad declaration from a token stream input, given a bool nested:
//
// Process input:
loop {
let token = parser.peek::<Token![Any]>().unwrap();
dbg!(parser.at_end(), token);
//
// <eof-token>
// <semicolon-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::<ComponentValue>()?;
}
}
}

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> {
Expand Down
6 changes: 6 additions & 0 deletions crates/hdx_lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion crates/hdx_parser/src/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use hdx_lexer::{Include, Kind, LexerCheckpoint, Token};
use hdx_lexer::{Include, Kind, LexerCheckpoint, Span, Token};

use crate::Parser;

Expand All @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion crates/hdx_parser/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down
57 changes: 38 additions & 19 deletions crates/hdx_parser/src/traits/rules.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<Spanned<Self::Prelude>> {
Self::Prelude::parse_spanned(parser)
parser.parse_spanned::<Self::Prelude>()
}

// 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<Spanned<Self::Block>> {
Self::Block::parse_spanned(parser)
parser.parse_spanned::<Self::Block>()
}

// 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::<Self::Block>().ok();
}

// https://drafts.csswg.org/css-syntax-3/#consume-a-qualified-rule
Expand All @@ -96,32 +109,38 @@ pub trait QualifiedRule<'a>: Sized + Parse<'a> {
}
}

let mut potential_custom = false;

// <{-token>
// If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--" followed by a <colon-token>....
// If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--" followed by a <colon-token>, then:
let checkpoint = parser.checkpoint();
if let Some(token) = parser.peek::<Token![Ident]>() {
if token.is_dashed_ident() {
parser.hop(token);
potential_custom = parser.peek::<Delim![:]>().is_some();
if parser.peek::<Delim![:]>().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::<Self::BadDeclaration>()?;
let token = parser.peek::<Token![Any]>().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::<Token![Any]>().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-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--" followed by a <colon-token>, 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.
Expand Down

0 comments on commit d75485e

Please sign in to comment.