From fc4c03fb5537ecc06ea6467547fdaf3833d1c3bb Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Mon, 7 Sep 2026 13:19:02 +0200 Subject: [PATCH] Revert "parser: Remove ParserInput." This reverts commit ddccc070a0261cb94195ed0f74095c67b86a8550. This is a temporary revert in order to release a new version of `rust-cssparser`. Signed-off-by: Martin Robinson --- color/lib.rs | 22 ++-- color/tests.rs | 11 +- fuzz/fuzz_targets/cssparser.rs | 5 +- src/lib.rs | 2 +- src/nth.rs | 5 +- src/parser.rs | 232 +++++++++++++++++++-------------- src/rules_and_declarations.rs | 38 +++--- src/size_of_tests.rs | 3 +- src/tests.rs | 153 +++++++++++++--------- 9 files changed, 274 insertions(+), 197 deletions(-) diff --git a/color/lib.rs b/color/lib.rs index 4d5890e0..93c8f198 100644 --- a/color/lib.rs +++ b/color/lib.rs @@ -44,7 +44,7 @@ where /// value on success. pub fn parse_color_with<'i, P>( color_parser: &P, - input: &mut Parser<'i>, + input: &mut Parser<'i, '_>, ) -> Result> where P: ColorParser<'i>, @@ -112,7 +112,7 @@ where /// Parse the alpha component by itself from either number or percentage, /// clipping the result to [0.0..1.0]. #[inline] -fn parse_alpha_component<'i, P>( +fn parse_alpha_component<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -125,7 +125,7 @@ where .clamp(0.0, OPAQUE)) } -fn parse_legacy_alpha<'i, P>( +fn parse_legacy_alpha<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -140,7 +140,7 @@ where }) } -fn parse_modern_alpha<'i, P>( +fn parse_modern_alpha<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result, ParseError> @@ -156,7 +156,7 @@ where } #[inline] -fn parse_rgb<'i, P>( +fn parse_rgb<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -222,7 +222,7 @@ where /// /// #[inline] -fn parse_hsl<'i, P>( +fn parse_hsl<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -261,7 +261,7 @@ where /// /// #[inline] -fn parse_hwb<'i, P>( +fn parse_hwb<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -340,7 +340,7 @@ type IntoColorFn = fn(l: Option, a: Option, b: Option, alpha: Option) -> Output; #[inline] -fn parse_lab_like<'i, P>( +fn parse_lab_like<'i, 't, P>( color_parser: &P, arguments: &mut Parser, lightness_range: f32, @@ -366,7 +366,7 @@ where } #[inline] -fn parse_lch_like<'i, P>( +fn parse_lch_like<'i, 't, P>( color_parser: &P, arguments: &mut Parser, lightness_range: f32, @@ -393,7 +393,7 @@ where /// Parse the color() function. #[inline] -fn parse_color_with_color_space<'i, P>( +fn parse_color_with_color_space<'i, 't, P>( color_parser: &P, arguments: &mut Parser, ) -> Result> @@ -427,7 +427,7 @@ type ComponentParseResult = Result<(Option, Option, Option, Option), ParseError>; /// Parse the color components and alpha with the modern [color-4] syntax. -pub fn parse_components<'i, P, F1, F2, F3, R1, R2, R3>( +pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>( color_parser: &P, input: &mut Parser, f1: F1, diff --git a/color/tests.rs b/color/tests.rs index fbbb553d..7cfac15d 100644 --- a/color/tests.rs +++ b/color/tests.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::*; +use cssparser::ParserInput; use serde_json::{Value, json}; fn almost_equals(a: &Value, b: &Value) -> bool { @@ -59,7 +60,8 @@ fn run_raw_json_tests(json_data: &str, run: F) { fn run_json_tests Value>(json_data: &str, parse: F) { run_raw_json_tests(json_data, |input, expected| match input { Value::String(input) => { - let result = parse(&mut Parser::new(&input)); + let mut parse_input = ParserInput::new(&input); + let result = parse(&mut Parser::new(&mut parse_input)); assert_json_eq(result, expected, &input); } _ => panic!("Unexpected JSON"), @@ -147,7 +149,9 @@ fn color4_color_function() { macro_rules! parse_single_color { ($i:expr) => {{ - let mut input = Parser::new($i); + let input = $i; + let mut input = ParserInput::new(input); + let mut input = Parser::new(&mut input); Color::parse(&mut input).map_err(Into::>::into) }}; } @@ -351,7 +355,8 @@ fn generic_parser() { ]; for (input, expected) in TESTS { - let mut input = Parser::new(input); + let mut input = ParserInput::new(input); + let mut input = Parser::new(&mut input); let actual: OutputType = parse_color_with(&TestColorParser, &mut input).unwrap(); assert_eq!(actual, *expected); diff --git a/fuzz/fuzz_targets/cssparser.rs b/fuzz/fuzz_targets/cssparser.rs index 6740cbdd..c95516d9 100644 --- a/fuzz/fuzz_targets/cssparser.rs +++ b/fuzz/fuzz_targets/cssparser.rs @@ -5,7 +5,8 @@ use cssparser::*; const DEBUG: bool = false; fn parse_and_serialize(input: &str, preserving_comments: bool) -> String { - let mut parser = Parser::new(input); + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); let mut serialization = String::new(); let result = do_parse_and_serialize( &mut parser, @@ -20,7 +21,7 @@ fn parse_and_serialize(input: &str, preserving_comments: bool) -> String { serialization } -fn do_parse_and_serialize( +fn do_parse_and_serialize<'i>( input: &mut Parser, preserving_comments: bool, mut previous_token_type: TokenSerializationType, diff --git a/src/lib.rs b/src/lib.rs index 2730e14a..61451dea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ pub use crate::from_bytes::{EncodingSupport, stylesheet_encoding}; pub use crate::macros::_cssparser_internal_to_lowercase; pub use crate::nth::parse_nth; pub use crate::parser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; -pub use crate::parser::{Delimiter, Delimiters, Parser, ParserState}; +pub use crate::parser::{Delimiter, Delimiters, Parser, ParserInput, ParserState}; pub use crate::rules_and_declarations::{AtRuleParser, QualifiedRuleParser}; pub use crate::rules_and_declarations::{DeclarationParser, RuleBodyItemParser, RuleBodyParser}; pub use crate::rules_and_declarations::{StyleSheetParser, parse_one_rule}; diff --git a/src/nth.rs b/src/nth.rs index 40affc40..69f06f77 100644 --- a/src/nth.rs +++ b/src/nth.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::{BasicParseError, Parser, Token}; +use super::{BasicParseError, Parser, ParserInput, Token}; /// Parse the *An+B* notation, as found in the `:nth-child()` selector. /// The input is typically the arguments of a function, @@ -117,7 +117,8 @@ fn parse_n_dash_digits(string: &str) -> Result { } fn parse_number_saturate(string: &str) -> Result { - let mut parser = Parser::new(string); + let mut input = ParserInput::new(string); + let mut parser = Parser::new(&mut input); let int = if let Ok(&Token::Number { int_value: Some(int), .. diff --git a/src/parser.rs b/src/parser.rs index 1ee17817..2efff17a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -220,17 +220,12 @@ impl fmt::Display for ParseError { impl std::error::Error for ParseError {} -/// A CSS parser that borrows its `&str` input, yields `Token`s, and keeps track of nested blocks -/// and functions. -pub struct Parser<'i> { +/// The owned input for a parser. +pub struct ParserInput<'i> { tokenizer: Tokenizer<'i>, cached_token: CachedToken<'i>, current_block_depth: u8, nested_block_limit: u8, - /// If `Some(_)`, .parse_nested_block() can be called. - at_start_of: Option, - /// For parsers from `parse_until` or `parse_nested_block` - stop_before: Delimiters, } struct CachedToken<'i> { @@ -239,6 +234,43 @@ struct CachedToken<'i> { end_state: ParserState, } +impl<'i> ParserInput<'i> { + /// 75 nested blocks seems reasonable enough. + const REASONABLE_NESTED_BLOCK_LIMIT: u8 = 75; + + /// Create a new input for a parser. + pub fn new(input: &'i str) -> ParserInput<'i> { + ParserInput { + tokenizer: Tokenizer::new(input), + nested_block_limit: Self::REASONABLE_NESTED_BLOCK_LIMIT, + current_block_depth: 0, + cached_token: CachedToken { + token: Token::Semicolon, // Anything would do. + start_position: SourcePosition(usize::MAX), // No token would match this cache. + end_state: ParserState::default(), + }, + } + } + + /// Sets a limit for how many nested blocks we're allowed to parse. This is useful to avoid + /// running out of stack space. By default, it's set to `REASONABLE_NESTED_BLOCK_LIMIT`, but it + /// can be overridden or cleared. A limit of 0 will be equivalent to no limit at all. + pub fn set_nested_block_limit(&mut self, limit: u8) { + self.nested_block_limit = limit; + } +} + +/// A CSS parser that borrows its `&str` input, +/// yields `Token`s, +/// and keeps track of nested blocks and functions. +pub struct Parser<'i, 't> { + input: &'t mut ParserInput<'i>, + /// If `Some(_)`, .parse_nested_block() can be called. + at_start_of: Option, + /// For parsers from `parse_until` or `parse_nested_block` + stop_before: Delimiters, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum BlockType { Parenthesis, @@ -357,37 +389,20 @@ macro_rules! expect { /// See https://drafts.csswg.org/css-values-5/#arbitrary-substitution pub type ArbitrarySubstitutionFunctions<'a> = &'a [&'static str]; -impl<'i> Parser<'i> { - /// 75 nested blocks seems reasonable enough. - const REASONABLE_NESTED_BLOCK_LIMIT: u8 = 75; - - /// Create a new parser for the given input. +impl<'i: 't, 't> Parser<'i, 't> { + /// Create a new parser #[inline] - pub fn new(input: &'i str) -> Self { - Self { - tokenizer: Tokenizer::new(input), + pub fn new(input: &'t mut ParserInput<'i>) -> Parser<'i, 't> { + Parser { + input, at_start_of: None, stop_before: Delimiter::None, - nested_block_limit: Self::REASONABLE_NESTED_BLOCK_LIMIT, - current_block_depth: 0, - cached_token: CachedToken { - token: Token::Semicolon, // Anything would do. - start_position: SourcePosition(usize::MAX), // No token would match this cache. - end_state: ParserState::default(), - }, } } - /// Sets a limit for how many nested blocks we're allowed to parse. This is useful to avoid - /// running out of stack space. By default, it's set to `REASONABLE_NESTED_BLOCK_LIMIT`, but it - /// can be overridden or cleared. A limit of 0 will be equivalent to no limit at all. - pub fn set_nested_block_limit(&mut self, limit: u8) { - self.nested_block_limit = limit; - } - /// Return the current line that is being parsed. pub fn current_line(&self) -> &'i str { - self.tokenizer.current_source_line() + self.input.tokenizer.current_source_line() } /// Check whether the input is exhausted. That is, if `.next()` would return a token. @@ -422,13 +437,13 @@ impl<'i> Parser<'i> { /// This can be used with the `Parser::slice` and `slice_from` methods. #[inline] pub fn position(&self) -> SourcePosition { - self.tokenizer.position() + self.input.tokenizer.position() } /// The current line number and column number. #[inline] pub fn current_source_location(&self) -> SourceLocation { - self.tokenizer.current_source_location() + self.input.tokenizer.current_source_location() } /// The source map URL, if known. @@ -437,7 +452,7 @@ impl<'i> Parser<'i> { /// comment. The last such comment is used, so this value may /// change as parsing proceeds. pub fn current_source_map_url(&self) -> Option<&str> { - self.tokenizer.current_source_map_url() + self.input.tokenizer.current_source_map_url() } /// The source URL, if known. @@ -446,7 +461,7 @@ impl<'i> Parser<'i> { /// comment. The last such comment is used, so this value may /// change as parsing proceeds. pub fn current_source_url(&self) -> Option<&str> { - self.tokenizer.current_source_url() + self.input.tokenizer.current_source_url() } /// Create a new unexpected token or EOF ParseError at the current location @@ -465,7 +480,7 @@ impl<'i> Parser<'i> { pub fn state(&self) -> ParserState { ParserState { at_start_of: self.at_start_of, - ..self.tokenizer.state() + ..self.input.tokenizer.state() } } @@ -473,24 +488,24 @@ impl<'i> Parser<'i> { #[inline] pub fn skip_whitespace(&mut self) { if let Some(block_type) = self.at_start_of.take() { - consume_until_end_of_block(block_type, &mut self.tokenizer); + consume_until_end_of_block(block_type, &mut self.input.tokenizer); } - self.tokenizer.skip_whitespace() + self.input.tokenizer.skip_whitespace() } #[inline] pub(crate) fn skip_cdc_and_cdo(&mut self) { if let Some(block_type) = self.at_start_of.take() { - consume_until_end_of_block(block_type, &mut self.tokenizer); + consume_until_end_of_block(block_type, &mut self.input.tokenizer); } - self.tokenizer.skip_cdc_and_cdo() + self.input.tokenizer.skip_cdc_and_cdo() } #[inline] pub(crate) fn next_byte(&self) -> Option { - let byte = self.tokenizer.next_byte()?; + let byte = self.input.tokenizer.next_byte()?; if self.stop_before.contains(Delimiters::from_byte(byte)) { return None; } @@ -503,7 +518,7 @@ impl<'i> Parser<'i> { /// Should only be used with `SourcePosition` values from the same `Parser` instance. #[inline] pub fn reset(&mut self, state: &ParserState) { - self.tokenizer.reset(state); + self.input.tokenizer.reset(state); self.at_start_of = state.at_start_of; } @@ -514,7 +529,8 @@ impl<'i> Parser<'i> { &mut self, fns: ArbitrarySubstitutionFunctions<'i>, ) { - self.tokenizer + self.input + .tokenizer .look_for_arbitrary_substitution_functions(fns) } @@ -522,14 +538,14 @@ impl<'i> Parser<'i> { /// `look_for_arbitrary_substitution_functions` was called, and stop looking. #[inline] pub fn seen_arbitrary_substitution_functions(&mut self) -> bool { - self.tokenizer.seen_arbitrary_substitution_functions() + self.input.tokenizer.seen_arbitrary_substitution_functions() } /// The old name of `try_parse`, which requires raw identifiers in the Rust 2018 edition. #[inline] pub fn r#try(&mut self, thing: F) -> Result where - F: FnOnce(&mut Parser<'i>) -> Result, + F: FnOnce(&mut Parser<'i, 't>) -> Result, { self.try_parse(thing) } @@ -541,7 +557,7 @@ impl<'i> Parser<'i> { #[inline] pub fn try_parse(&mut self, thing: F) -> Result where - F: FnOnce(&mut Parser<'i>) -> Result, + F: FnOnce(&mut Parser<'i, 't>) -> Result, { let start = self.state(); let result = thing(self); @@ -554,13 +570,13 @@ impl<'i> Parser<'i> { /// Return a slice of the CSS input #[inline] pub fn slice(&self, range: Range) -> &'i str { - self.tokenizer.slice(range) + self.input.tokenizer.slice(range) } /// Return a slice of the CSS input, from the given position to the current one. #[inline] pub fn slice_from(&self, start_position: SourcePosition) -> &'i str { - self.tokenizer.slice_from(start_position) + self.input.tokenizer.slice_from(start_position) } /// Return the next token in the input that is neither whitespace or a comment, @@ -585,7 +601,7 @@ impl<'i> Parser<'i> { while let Token::Comment(..) = self.next_including_whitespace_and_comments()? { // Keep going } - Ok(&self.cached_token.token) + Ok(&self.input.cached_token.token) } /// Same as `Parser::next`, but does not skip whitespace or comment tokens. @@ -598,10 +614,10 @@ impl<'i> Parser<'i> { &mut self, ) -> Result<&Token<'i>, BasicParseError> { if let Some(block_type) = self.at_start_of.take() { - consume_until_end_of_block(block_type, &mut self.tokenizer); + consume_until_end_of_block(block_type, &mut self.input.tokenizer); } - let Some(byte) = self.tokenizer.next_byte() else { + let Some(byte) = self.input.tokenizer.next_byte() else { return Err(BasicParseError::new(BasicParseErrorKind::EndOfInput)); }; @@ -609,23 +625,23 @@ impl<'i> Parser<'i> { return Err(BasicParseError::new(BasicParseErrorKind::EndOfInput)); } - let token_start_position = self.tokenizer.position(); - let using_cached_token = self.cached_token.start_position == token_start_position; + let token_start_position = self.input.tokenizer.position(); + let using_cached_token = self.input.cached_token.start_position == token_start_position; let token = if using_cached_token { - let cached_token = &self.cached_token; - self.tokenizer.reset(&cached_token.end_state); + let cached_token = &self.input.cached_token; + self.input.tokenizer.reset(&cached_token.end_state); if let Token::Function(ref name) = cached_token.token { - self.tokenizer.see_function(name) + self.input.tokenizer.see_function(name) } &cached_token.token } else { - let new_token = self.tokenizer.next_unchecked(); - self.cached_token = CachedToken { + let new_token = self.input.tokenizer.next_unchecked(); + self.input.cached_token = CachedToken { token: new_token, start_position: token_start_position, - end_state: self.tokenizer.state(), + end_state: self.input.tokenizer.state(), }; - &self.cached_token.token + &self.input.cached_token.token }; if let Some(block_type) = BlockType::opening(token) { @@ -641,7 +657,7 @@ impl<'i> Parser<'i> { #[inline] pub fn parse_entirely(&mut self, parse: F) -> Result> where - F: FnOnce(&mut Parser<'i>) -> Result>, + F: FnOnce(&mut Parser<'i, 't>) -> Result>, { let result = parse(self)?; self.expect_exhausted()?; @@ -662,7 +678,7 @@ impl<'i> Parser<'i> { #[inline] pub fn parse_comma_separated(&mut self, parse_one: F) -> Result, ParseError> where - F: FnMut(&mut Parser<'i>) -> Result>, + F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result>, { self.parse_comma_separated_internal(parse_one, /* ignore_errors = */ false) } @@ -675,7 +691,7 @@ impl<'i> Parser<'i> { #[inline] pub fn parse_comma_separated_ignoring_errors(&mut self, parse_one: F) -> Vec where - F: FnMut(&mut Parser<'i>) -> Result>, + F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result>, { match self.parse_comma_separated_internal(parse_one, /* ignore_errors = */ true) { Ok(values) => values, @@ -690,7 +706,7 @@ impl<'i> Parser<'i> { ignore_errors: bool, ) -> Result, ParseError> where - F: FnMut(&mut Parser<'i>) -> Result>, + F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result>, { // Vec grows from 0 to 4 by default on first push(). So allocate with // capacity 1, so in the somewhat common case of only one item we don't @@ -726,7 +742,7 @@ impl<'i> Parser<'i> { #[inline] pub fn parse_nested_block(&mut self, parse: F) -> Result> where - F: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { parse_nested_block(self, parse) } @@ -746,7 +762,7 @@ impl<'i> Parser<'i> { parse: F, ) -> Result> where - F: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { parse_until_before(self, delimiters, ParseUntilErrorBehavior::Consume, parse) } @@ -763,7 +779,7 @@ impl<'i> Parser<'i> { parse: F, ) -> Result> where - F: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { parse_until_after(self, delimiters, ParseUntilErrorBehavior::Consume, parse) } @@ -984,72 +1000,78 @@ impl<'i> Parser<'i> { } } -pub fn parse_until_before<'i, F, T, E>( - parser: &mut Parser<'i>, +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 - F: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { - let old_stop_before = parser.stop_before; let delimiters = parser.stop_before | delimiters; - parser.stop_before = delimiters; - let result = parser.parse_entirely(parse); - parser.stop_before = old_stop_before; - if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { - return result; - } - if let Some(block_type) = parser.at_start_of.take() { - consume_until_end_of_block(block_type, &mut parser.tokenizer); + let result; + // Introduce a new scope to limit duration of nested_parser’s borrow + { + let mut delimited_parser = Parser { + input: parser.input, + at_start_of: parser.at_start_of.take(), + 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); + } } // FIXME: have a special-purpose tokenizer method for this that does less work. - while let Some(next_byte) = parser.tokenizer.next_byte() { + while let Some(next_byte) = parser.input.tokenizer.next_byte() { if delimiters.contains(Delimiters::from_byte(next_byte)) { break; } - let token = parser.tokenizer.next_unchecked(); + let token = parser.input.tokenizer.next_unchecked(); if let Some(block_type) = BlockType::opening(&token) { - consume_until_end_of_block(block_type, &mut parser.tokenizer); + consume_until_end_of_block(block_type, &mut parser.input.tokenizer); } } result } -pub fn parse_until_after<'i, F, T, E>( - parser: &mut Parser<'i>, +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: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { let result = parse_until_before(parser, delimiters, error_behavior, parse); if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { return result; } - if let Some(next_byte) = parser.tokenizer.next_byte() { + if let Some(next_byte) = parser.input.tokenizer.next_byte() { let delimiter = Delimiters::from_byte(next_byte); if !parser.stop_before.contains(delimiter) { debug_assert!(delimiters.contains(delimiter)); // We know this byte is ASCII. - parser.tokenizer.advance(1); + parser.input.tokenizer.advance(1); if next_byte == b'{' { - consume_until_end_of_block(BlockType::CurlyBracket, &mut parser.tokenizer); + consume_until_end_of_block(BlockType::CurlyBracket, &mut parser.input.tokenizer); } } } result } -pub fn parse_nested_block<'i, F, T, E>( - parser: &mut Parser<'i>, +pub fn parse_nested_block<'i: 't, 't, F, T, E>( + parser: &mut Parser<'i, 't>, parse: F, ) -> Result> where - F: FnOnce(&mut Parser<'i>) -> Result>, + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result>, { let block_type = parser.at_start_of.take().expect( "\ @@ -1058,27 +1080,37 @@ where token was just consumed.\ ", ); - if parser.current_block_depth >= parser.nested_block_limit && parser.nested_block_limit != 0 { + if parser.input.current_block_depth >= parser.input.nested_block_limit + && parser.input.nested_block_limit != 0 + { return Err(ParseError::from_basic_kind( BasicParseErrorKind::TooManyNestedBlocks, )); } // Fine to use wrapping addition, overflow can only occur without a limit. - parser.current_block_depth = parser.current_block_depth.wrapping_add(1); + parser.input.current_block_depth = parser.input.current_block_depth.wrapping_add(1); - let old_stop_before = parser.stop_before; - parser.stop_before = match block_type { + let closing_delimiter = match block_type { BlockType::CurlyBracket => ClosingDelimiter::CloseCurlyBracket, BlockType::SquareBracket => ClosingDelimiter::CloseSquareBracket, BlockType::Parenthesis => ClosingDelimiter::CloseParenthesis, }; - let result = parser.parse_entirely(parse); - if let Some(nested_block_type) = parser.at_start_of.take() { - consume_until_end_of_block(nested_block_type, &mut parser.tokenizer); + let result; + // Introduce a new scope to limit duration of nested_parser’s borrow + { + let mut nested_parser = Parser { + input: parser.input, + at_start_of: None, + stop_before: closing_delimiter, + }; + result = nested_parser.parse_entirely(parse); + if let Some(block_type) = nested_parser.at_start_of { + consume_until_end_of_block(block_type, &mut nested_parser.input.tokenizer); + } } - consume_until_end_of_block(block_type, &mut parser.tokenizer); - parser.stop_before = old_stop_before; - parser.current_block_depth = parser.current_block_depth.wrapping_sub(1); + consume_until_end_of_block(block_type, &mut parser.input.tokenizer); + // See above. + parser.input.current_block_depth = parser.input.current_block_depth.wrapping_sub(1); result } diff --git a/src/rules_and_declarations.rs b/src/rules_and_declarations.rs index f33eadb8..813224b8 100644 --- a/src/rules_and_declarations.rs +++ b/src/rules_and_declarations.rs @@ -49,7 +49,7 @@ pub trait DeclarationParser<'i> { fn parse_value( &mut self, _name: CowRcStr<'i>, - _input: &mut Parser<'i>, + _input: &mut Parser<'i, '_>, _declaration_start: &ParserState, ) -> Result> { Err(ParseError::unexpected_token()) @@ -93,7 +93,7 @@ pub trait AtRuleParser<'i> { fn parse_prelude( &mut self, _name: CowRcStr<'i>, - _input: &mut Parser<'i>, + _input: &mut Parser<'i, '_>, ) -> Result> { Err(ParseError::from_basic_kind( BasicParseErrorKind::AtRuleInvalid, @@ -133,7 +133,7 @@ pub trait AtRuleParser<'i> { &mut self, prelude: Self::Prelude, start: &ParserState, - _input: &mut Parser<'i>, + _input: &mut Parser<'i, '_>, ) -> Result> { let _ = prelude; let _ = start; @@ -174,7 +174,7 @@ pub trait QualifiedRuleParser<'i> { /// that ends where the prelude should end (before the next `{`). fn parse_prelude( &mut self, - _input: &mut Parser<'i>, + _input: &mut Parser<'i, '_>, ) -> Result> { Err(ParseError::from_basic_kind( BasicParseErrorKind::QualifiedRuleInvalid, @@ -192,7 +192,7 @@ pub trait QualifiedRuleParser<'i> { &mut self, prelude: Self::Prelude, start: &ParserState, - _input: &mut Parser<'i>, + _input: &mut Parser<'i, '_>, ) -> Result> { let _ = prelude; let _ = start; @@ -203,9 +203,9 @@ pub trait QualifiedRuleParser<'i> { } /// Provides an iterator for rule bodies and declaration lists. -pub struct RuleBodyParser<'i, 'a, P, I, E> { +pub struct RuleBodyParser<'i, 't, 'a, P, I, E> { /// The input given to the parser. - pub input: &'a mut Parser<'i>, + pub input: &'a mut Parser<'i, 't>, /// The parser given to `RuleBodyParser::new` pub parser: &'a mut P, @@ -226,7 +226,7 @@ pub trait RuleBodyItemParser<'i, DeclOrRule, Error>: fn parse_qualified(&self) -> bool; } -impl<'i, 'a, P, I, E> RuleBodyParser<'i, 'a, P, I, E> { +impl<'i, 't, 'a, P, I, E> RuleBodyParser<'i, 't, 'a, P, I, E> { /// Create a new `RuleBodyParser` for the given `input` and `parser`. /// /// Note that all CSS declaration lists can on principle contain at-rules. @@ -241,7 +241,7 @@ impl<'i, 'a, P, I, E> RuleBodyParser<'i, 'a, P, I, E> { /// The return type for finished declarations and at-rules also needs to be the same, /// since `::next` can return either. /// It could be a custom enum. - pub fn new(input: &'a mut Parser<'i>, parser: &'a mut P) -> Self { + pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self { Self { input, parser, @@ -251,7 +251,7 @@ impl<'i, 'a, P, I, E> RuleBodyParser<'i, 'a, P, I, E> { } /// https://drafts.csswg.org/css-syntax/#consume-a-blocks-contents -impl<'i, I, P, E> Iterator for RuleBodyParser<'i, '_, P, I, E> +impl<'i, I, P, E> Iterator for RuleBodyParser<'i, '_, '_, P, I, E> where P: RuleBodyItemParser<'i, I, E>, { @@ -339,9 +339,9 @@ where } /// Provides an iterator for rule list parsing at the top-level of a stylesheet. -pub struct StyleSheetParser<'i, 'a, P> { +pub struct StyleSheetParser<'i, 't, 'a, P> { /// The input given. - pub input: &'a mut Parser<'i>, + pub input: &'a mut Parser<'i, 't>, /// The parser given. pub parser: &'a mut P, @@ -349,7 +349,7 @@ pub struct StyleSheetParser<'i, 'a, P> { any_rule_so_far: bool, } -impl<'i, 'a, R, P, E> StyleSheetParser<'i, 'a, P> +impl<'i, 't, 'a, R, P, E> StyleSheetParser<'i, 't, 'a, P> where P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E> + AtRuleParser<'i, AtRule = R, Error = E>, @@ -360,7 +360,7 @@ where /// /// The return type for finished qualified rules and at-rules also needs to be the same, /// since `::next` can return either. It could be a custom enum. - pub fn new(input: &'a mut Parser<'i>, parser: &'a mut P) -> Self { + pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self { Self { input, parser, @@ -370,7 +370,7 @@ where } /// `StyleSheetParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one. -impl<'i, R, P, E> Iterator for StyleSheetParser<'i, '_, P> +impl<'i, R, P, E> Iterator for StyleSheetParser<'i, '_, '_, P> where P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E> + AtRuleParser<'i, AtRule = R, Error = E>, @@ -429,7 +429,7 @@ where /// Parse a single declaration, such as an `( /* ... */ )` parenthesis in an `@supports` prelude. pub fn parse_one_declaration<'i, P, E>( - input: &mut Parser<'i>, + input: &mut Parser<'i, '_>, parser: &mut P, ) -> Result<

>::Declaration, (ParseError, &'i str, SourceLocation)> where @@ -448,7 +448,7 @@ where /// Parse a single rule, such as for CSSOM’s `CSSStyleSheet.insertRule`. pub fn parse_one_rule<'i, R, P, E>( - input: &mut Parser<'i>, + input: &mut Parser<'i, '_>, parser: &mut P, ) -> Result> where @@ -481,7 +481,7 @@ where fn parse_at_rule<'i, P, E>( start: &ParserState, name: CowRcStr<'i>, - input: &mut Parser<'i>, + input: &mut Parser<'i, '_>, parser: &mut P, ) -> Result<

>::AtRule, (ParseError, &'i str, SourceLocation)> where @@ -536,7 +536,7 @@ fn looks_like_a_custom_property(input: &mut Parser) -> bool { // https://drafts.csswg.org/css-syntax/#consume-a-qualified-rule fn parse_qualified_rule<'i, P, E>( start: &ParserState, - input: &mut Parser<'i>, + input: &mut Parser<'i, '_>, parser: &mut P, nested: bool, ) -> Result<

>::QualifiedRule, ParseError> diff --git a/src/size_of_tests.rs b/src/size_of_tests.rs index e407578b..1a53cc0f 100644 --- a/src/size_of_tests.rs +++ b/src/size_of_tests.rs @@ -43,7 +43,8 @@ size_of_test!(std_cow_str, std::borrow::Cow<'static, str>, 24, 32); size_of_test!(cow_rc_str, CowRcStr, 16); size_of_test!(tokenizer, crate::tokenizer::Tokenizer, 96); -size_of_test!(parser, crate::parser::Parser, 168); +size_of_test!(parser_input, crate::parser::ParserInput, 168); +size_of_test!(parser, crate::parser::Parser, 16); size_of_test!(source_position, crate::SourcePosition, 8); size_of_test!(parser_state, crate::ParserState, 24); diff --git a/src/tests.rs b/src/tests.rs index 0c538618..aa913c81 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -15,10 +15,10 @@ use self::test::Bencher; use super::{ AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, DeclarationParser, Delimiter, - EncodingSupport, ParseError, ParseErrorKind, Parser, ParserState, QualifiedRuleParser, - RuleBodyItemParser, RuleBodyParser, SourceLocation, StyleSheetParser, ToCss, Token, - TokenSerializationType, UnicodeRange, parse_important, parse_nth, parse_one_declaration, - parse_one_rule, stylesheet_encoding, + EncodingSupport, ParseError, ParseErrorKind, Parser, ParserInput, ParserState, + QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, StyleSheetParser, + ToCss, Token, TokenSerializationType, UnicodeRange, parse_important, parse_nth, + parse_one_declaration, parse_one_rule, stylesheet_encoding, }; macro_rules! JArray { @@ -97,7 +97,8 @@ fn run_raw_json_tests(json_data: &str, run: F) { fn run_json_tests Value>(json_data: &str, parse: F) { run_raw_json_tests(json_data, |input, expected| match input { Value::String(input) => { - let result = parse(&mut Parser::new(&input)); + let mut parse_input = ParserInput::new(&input); + let result = parse(&mut Parser::new(&mut parse_input)); assert_json_eq(result, expected, &input); } _ => panic!("Unexpected JSON"), @@ -227,8 +228,9 @@ fn stylesheet_from_bytes() { environment_encoding, ); let (css_unicode, used_encoding, _) = encoding.decode(&css); - let mut parser = Parser::new(&css_unicode); - let rules = StyleSheetParser::new(&mut parser, &mut JsonParser) + let mut input = ParserInput::new(&css_unicode); + let input = &mut Parser::new(&mut input); + let rules = StyleSheetParser::new(input, &mut JsonParser) .map(|result| result.unwrap_or(JArray!["error", "invalid"])) .collect::>(); JArray![rules, used_encoding.name().to_lowercase()] @@ -249,24 +251,29 @@ fn stylesheet_from_bytes() { #[test] fn expect_no_error_token() { - assert!( - Parser::new("foo 4px ( / { !bar }") - .expect_no_error_token() - .is_ok() - ); - assert!(Parser::new(")").expect_no_error_token().is_err()); - assert!(Parser::new("}").expect_no_error_token().is_err()); - assert!(Parser::new("(a){]").expect_no_error_token().is_err()); - assert!(Parser::new("'\n'").expect_no_error_token().is_err()); - assert!(Parser::new("url('\n'").expect_no_error_token().is_err()); - assert!(Parser::new("url(a b)").expect_no_error_token().is_err()); - assert!(Parser::new("url(\u{7F}))").expect_no_error_token().is_err()); + let mut input = ParserInput::new("foo 4px ( / { !bar }"); + assert!(Parser::new(&mut input).expect_no_error_token().is_ok()); + let mut input = ParserInput::new(")"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("}"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("(a){]"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("'\n'"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("url('\n'"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("url(a b)"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); + let mut input = ParserInput::new("url(\u{7F}))"); + assert!(Parser::new(&mut input).expect_no_error_token().is_err()); } /// https://github.com/servo/rust-cssparser/issues/71 #[test] fn outer_block_end_consumed() { - let mut input = Parser::new("(calc(true))"); + let mut input = ParserInput::new("(calc(true))"); + let mut input = Parser::new(&mut input); assert!(input.expect_parenthesis_block().is_ok()); assert!( input @@ -282,7 +289,8 @@ fn outer_block_end_consumed() { /// https://github.com/servo/rust-cssparser/issues/174 #[test] fn bad_url_slice_out_of_bounds() { - let mut parser = Parser::new("url(\u{1}\\"); + let mut input = ParserInput::new("url(\u{1}\\"); + let mut parser = Parser::new(&mut input); let result = parser.next_including_whitespace_and_comments(); // This used to panic assert_eq!(result, Ok(&Token::BadUrl("\u{1}\\".into()))); } @@ -290,7 +298,8 @@ fn bad_url_slice_out_of_bounds() { /// https://bugzilla.mozilla.org/show_bug.cgi?id=1383975 #[test] fn bad_url_slice_not_at_char_boundary() { - let mut parser = Parser::new("url(9\n۰"); + let mut input = ParserInput::new("url(9\n۰"); + let mut parser = Parser::new(&mut input); let result = parser.next_including_whitespace_and_comments(); // This used to panic assert_eq!(result, Ok(&Token::BadUrl("9\n۰".into()))); } @@ -318,23 +327,31 @@ fn unquoted_url_escaping() { )\ " ); - assert_eq!(Parser::new(&serialized).next(), Ok(&token)); + let mut input = ParserInput::new(&serialized); + assert_eq!(Parser::new(&mut input).next(), Ok(&token)); } #[test] fn test_expect_url() { - fn parse<'a>(s: &'a str) -> Result, BasicParseError> { + fn parse<'a>(s: &mut ParserInput<'a>) -> Result, BasicParseError> { Parser::new(s).expect_url() } - assert_eq!(parse("url()").unwrap(), ""); - assert_eq!(parse("url( ").unwrap(), ""); - assert_eq!(parse("url( abc").unwrap(), "abc"); - assert_eq!(parse("url( abc \t)").unwrap(), "abc"); - assert_eq!(parse("url( 'abc' \t)").unwrap(), "abc"); - assert!(parse("url(abc more stuff)").is_err()); + let mut input = ParserInput::new("url()"); + assert_eq!(parse(&mut input).unwrap(), ""); + let mut input = ParserInput::new("url( "); + assert_eq!(parse(&mut input).unwrap(), ""); + let mut input = ParserInput::new("url( abc"); + assert_eq!(parse(&mut input).unwrap(), "abc"); + let mut input = ParserInput::new("url( abc \t)"); + assert_eq!(parse(&mut input).unwrap(), "abc"); + let mut input = ParserInput::new("url( 'abc' \t)"); + assert_eq!(parse(&mut input).unwrap(), "abc"); + let mut input = ParserInput::new("url(abc more stuff)"); + assert!(parse(&mut input).is_err()); // The grammar at https://drafts.csswg.org/css-values/#urls plans for `*` // at the position of "more stuff", but no such modifier is defined yet. - assert!(parse("url('abc' more stuff)").is_err()); + let mut input = ParserInput::new("url('abc' more stuff)"); + assert!(parse(&mut input).is_err()); } #[test] @@ -354,7 +371,8 @@ fn nth() { #[test] fn parse_comma_separated_ignoring_errors() { let input = "red, green something, yellow, whatever, blue"; - let mut input = Parser::new(input); + let mut input = ParserInput::new(input); + let mut input = Parser::new(&mut input); let result = input.parse_comma_separated_ignoring_errors(|input| { let ident = input.expect_ident()?; crate::color::parse_named_color(ident).map_err(|()| ParseError::<()>::unexpected_token()) @@ -449,7 +467,8 @@ fn serializer(preserve_comments: bool) { &mut serialized, preserve_comments, ); - let parser = &mut Parser::new(&serialized); + let mut input = ParserInput::new(&serialized); + let parser = &mut Parser::new(&mut input); Value::Array(component_values_to_json(parser)) }, ); @@ -457,7 +476,8 @@ fn serializer(preserve_comments: bool) { #[test] fn serialize_bad_tokens() { - let mut parser = Parser::new("url(foo\\) b\\)ar)'ba\\'\"z\n4"); + let mut input = ParserInput::new("url(foo\\) b\\)ar)'ba\\'\"z\n4"); + let mut parser = Parser::new(&mut input); let token = parser.next().unwrap().clone(); assert!(matches!(token, Token::BadUrl(_))); @@ -476,7 +496,7 @@ fn serialize_bad_tokens() { #[test] fn line_numbers() { - let mut input = Parser::new(concat!( + let mut input = ParserInput::new(concat!( "fo\\30\r\n", "0o bar/*\n", "*/baz\r\n", @@ -486,6 +506,7 @@ fn line_numbers() { ")\"a\\\r\n", "b\"" )); + let mut input = Parser::new(&mut input); assert_eq!( input.current_source_location(), SourceLocation { line: 0, column: 1 } @@ -593,7 +614,8 @@ fn overflow() { " .replace("{309 zeros}", &"0".repeat(309)); - let mut input = Parser::new(&css); + let mut input = ParserInput::new(&css); + let mut input = Parser::new(&mut input); assert_eq!(input.expect_integer(), Ok(2147483646)); assert_eq!(input.expect_integer(), Ok(2147483647)); @@ -620,7 +642,8 @@ fn overflow() { #[test] fn line_delimited() { - let mut input = Parser::new(" { foo ; bar } baz;,"); + let mut input = ParserInput::new(" { foo ; bar } baz;,"); + let mut input = Parser::new(&mut input); assert_eq!(input.next(), Ok(&Token::CurlyBracketBlock)); assert!( { @@ -783,7 +806,8 @@ const ARBITRARY_SUBSTITUTION_FUNCTIONS: ArbitrarySubstitutionFunctions = &["var" #[bench] fn unquoted_url(b: &mut Bencher) { b.iter(|| { - let mut input = Parser::new(BACKGROUND_IMAGE); + let mut input = ParserInput::new(BACKGROUND_IMAGE); + let mut input = Parser::new(&mut input); input.look_for_arbitrary_substitution_functions(ARBITRARY_SUBSTITUTION_FUNCTIONS); let result = input.try_parse(|input| input.expect_url()); @@ -803,7 +827,8 @@ fn unquoted_url(b: &mut Bencher) { fn numeric(b: &mut Bencher) { b.iter(|| { for _ in 0..1000000 { - let mut input = Parser::new("10px"); + let mut input = ParserInput::new("10px"); + let mut input = Parser::new(&mut input); let _ = test::black_box(input.next()); } }) @@ -819,7 +844,8 @@ fn no_stack_overflow_multiple_nested_blocks() { let dup = input.clone(); input.push_str(&dup); } - let mut input = Parser::new(&input); + let mut input = ParserInput::new(&input); + let mut input = Parser::new(&mut input); while input.next().is_ok() {} } @@ -839,17 +865,19 @@ fn nested_block_limit() { // Returns `Err(())` if (and only if) parsing bailed out due to the nesting limit. fn parse(depth: usize, limit: Option) -> Result<(), ()> { let css = format!("{}1{}", "calc(".repeat(depth), ")".repeat(depth)); - let mut parser = Parser::new(&css); + let mut input = ParserInput::new(&css); if let Some(limit) = limit { - parser.set_nested_block_limit(limit); + input.set_nested_block_limit(limit); } - parser.parse_entirely(parse_calc).map_err(|e| match e.kind { - ParseErrorKind::Basic(BasicParseErrorKind::TooManyNestedBlocks) => (), - other => panic!( - "Unexpected error parsing {} nested blocks: {:?}", - depth, other - ), - }) + Parser::new(&mut input) + .parse_entirely(parse_calc) + .map_err(|e| match e.kind { + ParseErrorKind::Basic(BasicParseErrorKind::TooManyNestedBlocks) => (), + other => panic!( + "Unexpected error parsing {} nested blocks: {:?}", + depth, other + ), + }) } // The default limit is 75 nested blocks. @@ -1148,9 +1176,11 @@ fn parse_until_before_stops_at_delimiter_or_end_of_input() { for equivalent in inputs { for (j, x) in equivalent.1.iter().enumerate() { for y in equivalent.1[j + 1..].iter() { - let mut ix = Parser::new(x); + let mut ix = ParserInput::new(x); + let mut ix = Parser::new(&mut ix); - let mut iy = Parser::new(y); + let mut iy = ParserInput::new(y); + let mut iy = Parser::new(&mut iy); let _ = ix.parse_until_before::<_, _, ()>(equivalent.0, |ix| { iy.parse_until_before::<_, _, ()>(equivalent.0, |iy| { @@ -1172,7 +1202,8 @@ fn parse_until_before_stops_at_delimiter_or_end_of_input() { #[test] fn parser_maintains_current_line() { - let mut parser = Parser::new("ident ident;\nident ident ident;\nident"); + let mut input = ParserInput::new("ident ident;\nident ident ident;\nident"); + let mut parser = Parser::new(&mut input); assert_eq!(parser.current_line(), "ident ident;"); assert_eq!(parser.next(), Ok(&Token::Ident("ident".into()))); assert_eq!(parser.next(), Ok(&Token::Ident("ident".into()))); @@ -1190,7 +1221,8 @@ fn parser_maintains_current_line() { #[test] fn cdc_regression_test() { - let mut parser = Parser::new("-->x"); + let mut input = ParserInput::new("-->x"); + let mut parser = Parser::new(&mut input); parser.skip_cdc_and_cdo(); assert_eq!(parser.next(), Ok(&Token::Ident("x".into()))); assert_eq!( @@ -1207,7 +1239,8 @@ fn parse_entirely_reports_first_error() { enum E { Foo, } - let mut parser = Parser::new("ident"); + let mut input = ParserInput::new("ident"); + let mut parser = Parser::new(&mut input); let result: Result<(), _> = parser.parse_entirely(|_| Err(ParseError::custom(E::Foo))); assert_eq!( result, @@ -1237,7 +1270,8 @@ fn parse_sourcemapping_comments() { ]; for test in tests { - let mut parser = Parser::new(test.0); + let mut input = ParserInput::new(test.0); + let mut parser = Parser::new(&mut input); while parser.next_including_whitespace().is_ok() {} assert_eq!(parser.current_source_map_url(), test.1); } @@ -1260,7 +1294,8 @@ fn parse_sourceurl_comments() { ]; for test in tests { - let mut parser = Parser::new(test.0); + let mut input = ParserInput::new(test.0); + let mut parser = Parser::new(&mut input); while parser.next_including_whitespace().is_ok() {} assert_eq!(parser.current_source_url(), test.1); } @@ -1270,7 +1305,8 @@ fn parse_sourceurl_comments() { #[test] fn roundtrip_percentage_token() { fn test_roundtrip(value: &str) { - let mut parser = Parser::new(value); + let mut input = ParserInput::new(value); + let mut parser = Parser::new(&mut input); let token = parser.next().unwrap(); assert_eq!(token.to_css_string(), value); } @@ -1321,7 +1357,8 @@ fn utf16_columns() { ]; for test in tests { - let mut parser = Parser::new(test.0); + let mut input = ParserInput::new(test.0); + let mut parser = Parser::new(&mut input); // Read all tokens. loop {