diff --git a/src/parser.rs b/src/parser.rs index 9c91f523..834ccf9a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -39,6 +39,28 @@ impl ParserState { } } +/// When parsing until a given token, sometimes the caller knows that parsing is going to restart +/// at some earlier point, and consuming until we find a top level delimiter is just wasted work. +/// +/// In that case, callers can pass ParseUntilErrorBehavior::Stop to avoid doing all that wasted +/// work. +/// +/// This is important for things like CSS nesting, where something like: +/// +/// foo:is(..) { +/// ... +/// } +/// +/// Would need to scan the whole {} block to find a semicolon, only for parsing getting restarted +/// as a qualified rule later. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ParseUntilErrorBehavior { + /// Consume until we see the relevant delimiter or the end of the stream. + Consume, + /// Eagerly error. + Stop, +} + /// Details about a `BasicParseError` #[derive(Clone, Debug, PartialEq)] pub enum BasicParseErrorKind<'i> { @@ -766,7 +788,7 @@ impl<'i: 't, 't> Parser<'i, 't> { where F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { - parse_until_before(self, delimiters, parse) + parse_until_before(self, delimiters, ParseUntilErrorBehavior::Consume, parse) } /// Like `parse_until_before`, but also consume the delimiter token. @@ -783,7 +805,7 @@ impl<'i: 't, 't> Parser<'i, 't> { where F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { - parse_until_after(self, delimiters, parse) + parse_until_after(self, delimiters, ParseUntilErrorBehavior::Consume, parse) } /// Parse a and return its value. @@ -1013,6 +1035,7 @@ impl<'i: 't, 't> Parser<'i, 't> { pub fn parse_until_before<'i: 't, 't, F, T, E>( parser: &mut Parser<'i, 't>, delimiters: Delimiters, + error_behavior: ParseUntilErrorBehavior, parse: F, ) -> Result> where @@ -1028,6 +1051,9 @@ where stop_before: delimiters, }; result = delimited_parser.parse_entirely(parse); + if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { + return result; + } if let Some(block_type) = delimited_parser.at_start_of { consume_until_end_of_block(block_type, &mut delimited_parser.input.tokenizer); } @@ -1051,12 +1077,16 @@ where pub fn parse_until_after<'i: 't, 't, F, T, E>( parser: &mut Parser<'i, 't>, delimiters: Delimiters, + error_behavior: ParseUntilErrorBehavior, parse: F, ) -> Result> where F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { - let result = parser.parse_until_before(delimiters, parse); + let result = parse_until_before(parser, delimiters, error_behavior, parse); + if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { + return result; + } let next_byte = parser.input.tokenizer.next_byte(); if next_byte.is_some() && !parser diff --git a/src/rules_and_declarations.rs b/src/rules_and_declarations.rs index 4698561d..fb33a7d0 100644 --- a/src/rules_and_declarations.rs +++ b/src/rules_and_declarations.rs @@ -8,7 +8,7 @@ use super::{ BasicParseError, BasicParseErrorKind, Delimiter, Delimiters, ParseError, Parser, Token, }; use crate::cow_rc_str::CowRcStr; -use crate::parser::{parse_nested_block, parse_until_after, parse_until_before, ParserState}; +use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState}; /// Parse `!important`. /// @@ -266,14 +266,25 @@ where // that in a slightly more straight-forward way Token::Ident(ref name) if self.parser.parse_declarations() => { let name = name.clone(); + let parse_qualified = self.parser.parse_qualified(); let result = { + let error_behavior = if parse_qualified { + ParseUntilErrorBehavior::Stop + } else { + ParseUntilErrorBehavior::Consume + }; let parser = &mut self.parser; - parse_until_after(self.input, Delimiter::Semicolon, |input| { - input.expect_colon()?; - parser.parse_value(name, input) - }) + parse_until_after( + self.input, + Delimiter::Semicolon, + error_behavior, + |input| { + input.expect_colon()?; + parser.parse_value(name, input) + }, + ) }; - if result.is_err() && self.parser.parse_qualified() { + if result.is_err() && parse_qualified { self.input.reset(&start); // We ignore the resulting error here. The property declaration parse error // is likely to be more relevant.