diff options
Diffstat (limited to 'third_party/rust/cssparser/src/parser.rs')
-rw-r--r-- | third_party/rust/cssparser/src/parser.rs | 1073 |
1 files changed, 1073 insertions, 0 deletions
diff --git a/third_party/rust/cssparser/src/parser.rs b/third_party/rust/cssparser/src/parser.rs new file mode 100644 index 0000000000..f04628dbfe --- /dev/null +++ b/third_party/rust/cssparser/src/parser.rs @@ -0,0 +1,1073 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 crate::cow_rc_str::CowRcStr; +use crate::tokenizer::{SourceLocation, SourcePosition, Token, Tokenizer}; +use smallvec::SmallVec; +use std::ops::BitOr; +use std::ops::Range; + +/// A capture of the internal state of a `Parser` (including the position within the input), +/// obtained from the `Parser::position` method. +/// +/// Can be used with the `Parser::reset` method to restore that state. +/// Should only be used with the `Parser` instance it came from. +#[derive(Debug, Clone)] +pub struct ParserState { + pub(crate) position: usize, + pub(crate) current_line_start_position: usize, + pub(crate) current_line_number: u32, + pub(crate) at_start_of: Option<BlockType>, +} + +impl ParserState { + /// The position from the start of the input, counted in UTF-8 bytes. + #[inline] + pub fn position(&self) -> SourcePosition { + SourcePosition(self.position) + } + + /// The line number and column number + #[inline] + pub fn source_location(&self) -> SourceLocation { + SourceLocation { + line: self.current_line_number, + column: (self.position - self.current_line_start_position + 1) as u32, + } + } +} + +/// Details about a `BasicParseError` +#[derive(Clone, Debug, PartialEq)] +pub enum BasicParseErrorKind<'i> { + /// An unexpected token was encountered. + UnexpectedToken(Token<'i>), + /// The end of the input was encountered unexpectedly. + EndOfInput, + /// An `@` rule was encountered that was invalid. + AtRuleInvalid(CowRcStr<'i>), + /// The body of an '@' rule was invalid. + AtRuleBodyInvalid, + /// A qualified rule was encountered that was invalid. + QualifiedRuleInvalid, +} + +/// The fundamental parsing errors that can be triggered by built-in parsing routines. +#[derive(Clone, Debug, PartialEq)] +pub struct BasicParseError<'i> { + /// Details of this error + pub kind: BasicParseErrorKind<'i>, + /// Location where this error occurred + pub location: SourceLocation, +} + +impl<'i, T> From<BasicParseError<'i>> for ParseError<'i, T> { + #[inline] + fn from(this: BasicParseError<'i>) -> ParseError<'i, T> { + ParseError { + kind: ParseErrorKind::Basic(this.kind), + location: this.location, + } + } +} + +impl SourceLocation { + /// Create a new BasicParseError at this location for an unexpected token + #[inline] + pub fn new_basic_unexpected_token_error<'i>(self, token: Token<'i>) -> BasicParseError<'i> { + BasicParseError { + kind: BasicParseErrorKind::UnexpectedToken(token), + location: self, + } + } + + /// Create a new ParseError at this location for an unexpected token + #[inline] + pub fn new_unexpected_token_error<'i, E>(self, token: Token<'i>) -> ParseError<'i, E> { + ParseError { + kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token)), + location: self, + } + } + + /// Create a new custom ParseError at this location + #[inline] + pub fn new_custom_error<'i, E1: Into<E2>, E2>(self, error: E1) -> ParseError<'i, E2> { + ParseError { + kind: ParseErrorKind::Custom(error.into()), + location: self, + } + } +} + +/// Details of a `ParseError` +#[derive(Clone, Debug, PartialEq)] +pub enum ParseErrorKind<'i, T: 'i> { + /// A fundamental parse error from a built-in parsing routine. + Basic(BasicParseErrorKind<'i>), + /// A parse error reported by downstream consumer code. + Custom(T), +} + +impl<'i, T> ParseErrorKind<'i, T> { + /// Like `std::convert::Into::into` + pub fn into<U>(self) -> ParseErrorKind<'i, U> + where + T: Into<U>, + { + match self { + ParseErrorKind::Basic(basic) => ParseErrorKind::Basic(basic), + ParseErrorKind::Custom(custom) => ParseErrorKind::Custom(custom.into()), + } + } +} + +/// Extensible parse errors that can be encountered by client parsing implementations. +#[derive(Clone, Debug, PartialEq)] +pub struct ParseError<'i, E> { + /// Details of this error + pub kind: ParseErrorKind<'i, E>, + /// Location where this error occurred + pub location: SourceLocation, +} + +impl<'i, T> ParseError<'i, T> { + /// Extract the fundamental parse error from an extensible error. + pub fn basic(self) -> BasicParseError<'i> { + match self.kind { + ParseErrorKind::Basic(kind) => BasicParseError { + kind: kind, + location: self.location, + }, + ParseErrorKind::Custom(_) => panic!("Not a basic parse error"), + } + } + + /// Like `std::convert::Into::into` + pub fn into<U>(self) -> ParseError<'i, U> + where + T: Into<U>, + { + ParseError { + kind: self.kind.into(), + location: self.location, + } + } +} + +/// The owned input for a parser. +pub struct ParserInput<'i> { + tokenizer: Tokenizer<'i>, + cached_token: Option<CachedToken<'i>>, +} + +struct CachedToken<'i> { + token: Token<'i>, + start_position: SourcePosition, + end_state: ParserState, +} + +impl<'i> ParserInput<'i> { + /// Create a new input for a parser. + pub fn new(input: &'i str) -> ParserInput<'i> { + ParserInput { + tokenizer: Tokenizer::new(input), + cached_token: None, + } + } + + /// Create a new input for a parser. Line numbers in locations + /// are offset by the given value. + pub fn new_with_line_number_offset(input: &'i str, first_line_number: u32) -> ParserInput<'i> { + ParserInput { + tokenizer: Tokenizer::with_first_line_number(input, first_line_number), + cached_token: None, + } + } + + #[inline] + fn cached_token_ref(&self) -> &Token<'i> { + &self.cached_token.as_ref().unwrap().token + } +} + +/// 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<BlockType>, + /// For parsers from `parse_until` or `parse_nested_block` + stop_before: Delimiters, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub(crate) enum BlockType { + Parenthesis, + SquareBracket, + CurlyBracket, +} + +impl BlockType { + fn opening(token: &Token) -> Option<BlockType> { + match *token { + Token::Function(_) | Token::ParenthesisBlock => Some(BlockType::Parenthesis), + Token::SquareBracketBlock => Some(BlockType::SquareBracket), + Token::CurlyBracketBlock => Some(BlockType::CurlyBracket), + _ => None, + } + } + + fn closing(token: &Token) -> Option<BlockType> { + match *token { + Token::CloseParenthesis => Some(BlockType::Parenthesis), + Token::CloseSquareBracket => Some(BlockType::SquareBracket), + Token::CloseCurlyBracket => Some(BlockType::CurlyBracket), + _ => None, + } + } +} + +/// A set of characters, to be used with the `Parser::parse_until*` methods. +/// +/// The union of two sets can be obtained with the `|` operator. Example: +/// +/// ```{rust,ignore} +/// input.parse_until_before(Delimiter::CurlyBracketBlock | Delimiter::Semicolon) +/// ``` +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Delimiters { + bits: u8, +} + +/// `Delimiters` constants. +#[allow(non_upper_case_globals, non_snake_case)] +pub mod Delimiter { + use super::Delimiters; + + /// The empty delimiter set + pub const None: Delimiters = Delimiters { bits: 0 }; + /// The delimiter set with only the `{` opening curly bracket + pub const CurlyBracketBlock: Delimiters = Delimiters { bits: 1 << 1 }; + /// The delimiter set with only the `;` semicolon + pub const Semicolon: Delimiters = Delimiters { bits: 1 << 2 }; + /// The delimiter set with only the `!` exclamation point + pub const Bang: Delimiters = Delimiters { bits: 1 << 3 }; + /// The delimiter set with only the `,` comma + pub const Comma: Delimiters = Delimiters { bits: 1 << 4 }; +} + +#[allow(non_upper_case_globals, non_snake_case)] +mod ClosingDelimiter { + use super::Delimiters; + + pub const CloseCurlyBracket: Delimiters = Delimiters { bits: 1 << 5 }; + pub const CloseSquareBracket: Delimiters = Delimiters { bits: 1 << 6 }; + pub const CloseParenthesis: Delimiters = Delimiters { bits: 1 << 7 }; +} + +impl BitOr<Delimiters> for Delimiters { + type Output = Delimiters; + + #[inline] + fn bitor(self, other: Delimiters) -> Delimiters { + Delimiters { + bits: self.bits | other.bits, + } + } +} + +impl Delimiters { + #[inline] + fn contains(self, other: Delimiters) -> bool { + (self.bits & other.bits) != 0 + } + + #[inline] + fn from_byte(byte: Option<u8>) -> Delimiters { + match byte { + Some(b';') => Delimiter::Semicolon, + Some(b'!') => Delimiter::Bang, + Some(b',') => Delimiter::Comma, + Some(b'{') => Delimiter::CurlyBracketBlock, + Some(b'}') => ClosingDelimiter::CloseCurlyBracket, + Some(b']') => ClosingDelimiter::CloseSquareBracket, + Some(b')') => ClosingDelimiter::CloseParenthesis, + _ => Delimiter::None, + } + } +} + +/// Used in some `fn expect_*` methods +macro_rules! expect { + ($parser: ident, $($branches: tt)+) => { + { + let start_location = $parser.current_source_location(); + match *$parser.next()? { + $($branches)+ + ref token => { + return Err(start_location.new_basic_unexpected_token_error(token.clone())) + } + } + } + } +} + +impl<'i: 't, 't> Parser<'i, 't> { + /// Create a new parser + #[inline] + pub fn new(input: &'t mut ParserInput<'i>) -> Parser<'i, 't> { + Parser { + input: input, + at_start_of: None, + stop_before: Delimiter::None, + } + } + + /// Return the current line that is being parsed. + pub fn current_line(&self) -> &'i str { + self.input.tokenizer.current_source_line() + } + + /// Check whether the input is exhausted. That is, if `.next()` would return a token. + /// + /// This ignores whitespace and comments. + #[inline] + pub fn is_exhausted(&mut self) -> bool { + self.expect_exhausted().is_ok() + } + + /// Check whether the input is exhausted. That is, if `.next()` would return a token. + /// Return a `Result` so that the `?` operator can be used: `input.expect_exhausted()?` + /// + /// This ignores whitespace and comments. + #[inline] + pub fn expect_exhausted(&mut self) -> Result<(), BasicParseError<'i>> { + let start = self.state(); + let result = match self.next() { + Err(BasicParseError { + kind: BasicParseErrorKind::EndOfInput, + .. + }) => Ok(()), + Err(e) => unreachable!("Unexpected error encountered: {:?}", e), + Ok(t) => Err(start + .source_location() + .new_basic_unexpected_token_error(t.clone())), + }; + self.reset(&start); + result + } + + /// Return the current position within the input. + /// + /// This can be used with the `Parser::slice` and `slice_from` methods. + #[inline] + pub fn position(&self) -> SourcePosition { + self.input.tokenizer.position() + } + + /// The current line number and column number. + #[inline] + pub fn current_source_location(&self) -> SourceLocation { + self.input.tokenizer.current_source_location() + } + + /// The source map URL, if known. + /// + /// The source map URL is extracted from a specially formatted + /// 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.input.tokenizer.current_source_map_url() + } + + /// The source URL, if known. + /// + /// The source URL is extracted from a specially formatted + /// comment. The last such comment is used, so this value may + /// change as parsing proceeds. + pub fn current_source_url(&self) -> Option<&str> { + self.input.tokenizer.current_source_url() + } + + /// Create a new BasicParseError at the current location + #[inline] + pub fn new_basic_error(&self, kind: BasicParseErrorKind<'i>) -> BasicParseError<'i> { + BasicParseError { + kind: kind, + location: self.current_source_location(), + } + } + + /// Create a new basic ParseError at the current location + #[inline] + pub fn new_error<E>(&self, kind: BasicParseErrorKind<'i>) -> ParseError<'i, E> { + ParseError { + kind: ParseErrorKind::Basic(kind), + location: self.current_source_location(), + } + } + + /// Create a new custom BasicParseError at the current location + #[inline] + pub fn new_custom_error<E1: Into<E2>, E2>(&self, error: E1) -> ParseError<'i, E2> { + self.current_source_location().new_custom_error(error) + } + + /// Create a new unexpected token BasicParseError at the current location + #[inline] + pub fn new_basic_unexpected_token_error(&self, token: Token<'i>) -> BasicParseError<'i> { + self.new_basic_error(BasicParseErrorKind::UnexpectedToken(token)) + } + + /// Create a new unexpected token ParseError at the current location + #[inline] + pub fn new_unexpected_token_error<E>(&self, token: Token<'i>) -> ParseError<'i, E> { + self.new_error(BasicParseErrorKind::UnexpectedToken(token)) + } + + /// Create a new unexpected token or EOF ParseError at the current location + #[inline] + pub fn new_error_for_next_token<E>(&mut self) -> ParseError<'i, E> { + let token = match self.next() { + Ok(token) => token.clone(), + Err(e) => return e.into(), + }; + self.new_error(BasicParseErrorKind::UnexpectedToken(token)) + } + + /// Return the current internal state of the parser (including position within the input). + /// + /// This state can later be restored with the `Parser::reset` method. + #[inline] + pub fn state(&self) -> ParserState { + ParserState { + at_start_of: self.at_start_of, + ..self.input.tokenizer.state() + } + } + + /// Advance the input until the next token that’s not whitespace or a comment. + #[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.input.tokenizer); + } + + 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.input.tokenizer); + } + + self.input.tokenizer.skip_cdc_and_cdo() + } + + #[inline] + pub(crate) fn next_byte(&self) -> Option<u8> { + let byte = self.input.tokenizer.next_byte(); + if self.stop_before.contains(Delimiters::from_byte(byte)) { + return None; + } + byte + } + + /// Restore the internal state of the parser (including position within the input) + /// to what was previously saved by the `Parser::position` method. + /// + /// Should only be used with `SourcePosition` values from the same `Parser` instance. + #[inline] + pub fn reset(&mut self, state: &ParserState) { + self.input.tokenizer.reset(state); + self.at_start_of = state.at_start_of; + } + + /// Start looking for `var()` / `env()` functions. (See the + /// `.seen_var_or_env_functions()` method.) + #[inline] + pub fn look_for_var_or_env_functions(&mut self) { + self.input.tokenizer.look_for_var_or_env_functions() + } + + /// Return whether a `var()` or `env()` function has been seen by the + /// tokenizer since either `look_for_var_or_env_functions` was called, and + /// stop looking. + #[inline] + pub fn seen_var_or_env_functions(&mut self) -> bool { + self.input.tokenizer.seen_var_or_env_functions() + } + + /// The old name of `try_parse`, which requires raw identifiers in the Rust 2018 edition. + #[inline] + pub fn r#try<F, T, E>(&mut self, thing: F) -> Result<T, E> + where + F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>, + { + self.try_parse(thing) + } + + /// Execute the given closure, passing it the parser. + /// If the result (returned unchanged) is `Err`, + /// the internal state of the parser (including position within the input) + /// is restored to what it was before the call. + #[inline] + pub fn try_parse<F, T, E>(&mut self, thing: F) -> Result<T, E> + where + F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>, + { + let start = self.state(); + let result = thing(self); + if result.is_err() { + self.reset(&start) + } + result + } + + /// Return a slice of the CSS input + #[inline] + pub fn slice(&self, range: Range<SourcePosition>) -> &'i str { + 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.input.tokenizer.slice_from(start_position) + } + + /// Return the next token in the input that is neither whitespace or a comment, + /// and advance the position accordingly. + /// + /// After returning a `Function`, `ParenthesisBlock`, + /// `CurlyBracketBlock`, or `SquareBracketBlock` token, + /// the next call will skip until after the matching `CloseParenthesis`, + /// `CloseCurlyBracket`, or `CloseSquareBracket` token. + /// + /// See the `Parser::parse_nested_block` method to parse the content of functions or blocks. + /// + /// This only returns a closing token when it is unmatched (and therefore an error). + pub fn next(&mut self) -> Result<&Token<'i>, BasicParseError<'i>> { + self.skip_whitespace(); + self.next_including_whitespace_and_comments() + } + + /// Same as `Parser::next`, but does not skip whitespace tokens. + pub fn next_including_whitespace(&mut self) -> Result<&Token<'i>, BasicParseError<'i>> { + loop { + match self.next_including_whitespace_and_comments() { + Err(e) => return Err(e), + Ok(&Token::Comment(_)) => {} + _ => break, + } + } + Ok(self.input.cached_token_ref()) + } + + /// Same as `Parser::next`, but does not skip whitespace or comment tokens. + /// + /// **Note**: This should only be used in contexts like a CSS pre-processor + /// where comments are preserved. + /// When parsing higher-level values, per the CSS Syntax specification, + /// comments should always be ignored between tokens. + pub fn next_including_whitespace_and_comments( + &mut self, + ) -> Result<&Token<'i>, BasicParseError<'i>> { + if let Some(block_type) = self.at_start_of.take() { + consume_until_end_of_block(block_type, &mut self.input.tokenizer); + } + + let byte = self.input.tokenizer.next_byte(); + if self.stop_before.contains(Delimiters::from_byte(byte)) { + return Err(self.new_basic_error(BasicParseErrorKind::EndOfInput)); + } + + let token_start_position = self.input.tokenizer.position(); + let using_cached_token = self + .input + .cached_token + .as_ref() + .map_or(false, |cached_token| { + cached_token.start_position == token_start_position + }); + let token = if using_cached_token { + let cached_token = self.input.cached_token.as_ref().unwrap(); + self.input.tokenizer.reset(&cached_token.end_state); + match cached_token.token { + Token::Function(ref name) => self.input.tokenizer.see_function(name), + _ => {} + } + &cached_token.token + } else { + let new_token = self + .input + .tokenizer + .next() + .map_err(|()| self.new_basic_error(BasicParseErrorKind::EndOfInput))?; + self.input.cached_token = Some(CachedToken { + token: new_token, + start_position: token_start_position, + end_state: self.input.tokenizer.state(), + }); + self.input.cached_token_ref() + }; + + if let Some(block_type) = BlockType::opening(token) { + self.at_start_of = Some(block_type); + } + Ok(token) + } + + /// Have the given closure parse something, then check the the input is exhausted. + /// The result is overridden to `Err(())` if some input remains. + /// + /// This can help tell e.g. `color: green;` from `color: green 4px;` + #[inline] + pub fn parse_entirely<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>> + where + F: FnOnce(&mut Parser<'i, 't>) -> Result<T, ParseError<'i, E>>, + { + let result = parse(self)?; + self.expect_exhausted()?; + Ok(result) + } + + /// Parse a list of comma-separated values, all with the same syntax. + /// + /// The given closure is called repeatedly with a "delimited" parser + /// (see the `Parser::parse_until_before` method) + /// so that it can over consume the input past a comma at this block/function nesting level. + /// + /// Successful results are accumulated in a vector. + /// + /// This method returns `Err(())` the first time that a closure call does, + /// or if a closure call leaves some input before the next comma or the end of the input. + #[inline] + pub fn parse_comma_separated<F, T, E>( + &mut self, + mut parse_one: F, + ) -> Result<Vec<T>, ParseError<'i, E>> + where + F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, + { + // 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 + // way overallocate. Note that we always push at least one item if + // parsing succeeds. + let mut values = Vec::with_capacity(1); + loop { + self.skip_whitespace(); // Unnecessary for correctness, but may help try() in parse_one rewind less. + values.push(self.parse_until_before(Delimiter::Comma, &mut parse_one)?); + match self.next() { + Err(_) => return Ok(values), + Ok(&Token::Comma) => continue, + Ok(_) => unreachable!(), + } + } + } + + /// Parse the content of a block or function. + /// + /// This method panics if the last token yielded by this parser + /// (from one of the `next*` methods) + /// is not a on that marks the start of a block or function: + /// a `Function`, `ParenthesisBlock`, `CurlyBracketBlock`, or `SquareBracketBlock`. + /// + /// The given closure is called with a "delimited" parser + /// that stops at the end of the block or function (at the matching closing token). + /// + /// The result is overridden to `Err(())` if the closure leaves some input before that point. + #[inline] + pub fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>> + where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, + { + parse_nested_block(self, parse) + } + + /// Limit parsing to until a given delimiter or the end of the input. (E.g. + /// a semicolon for a property value.) + /// + /// The given closure is called with a "delimited" parser + /// that stops before the first character at this block/function nesting level + /// that matches the given set of delimiters, or at the end of the input. + /// + /// The result is overridden to `Err(())` if the closure leaves some input before that point. + #[inline] + pub fn parse_until_before<F, T, E>( + &mut self, + delimiters: Delimiters, + parse: F, + ) -> Result<T, ParseError<'i, E>> + where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, + { + parse_until_before(self, delimiters, parse) + } + + /// Like `parse_until_before`, but also consume the delimiter token. + /// + /// This can be useful when you don’t need to know which delimiter it was + /// (e.g. if these is only one in the given set) + /// or if it was there at all (as opposed to reaching the end of the input). + #[inline] + pub fn parse_until_after<F, T, E>( + &mut self, + delimiters: Delimiters, + parse: F, + ) -> Result<T, ParseError<'i, E>> + where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, + { + parse_until_after(self, delimiters, parse) + } + + /// Parse a <whitespace-token> and return its value. + #[inline] + pub fn expect_whitespace(&mut self) -> Result<&'i str, BasicParseError<'i>> { + let start_location = self.current_source_location(); + match *self.next_including_whitespace()? { + Token::WhiteSpace(value) => Ok(value), + ref t => Err(start_location.new_basic_unexpected_token_error(t.clone())), + } + } + + /// Parse a <ident-token> and return the unescaped value. + #[inline] + pub fn expect_ident(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::Ident(ref value) => Ok(value), + } + } + + /// expect_ident, but clone the CowRcStr + #[inline] + pub fn expect_ident_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { + self.expect_ident().map(|s| s.clone()) + } + + /// Parse a <ident-token> whose unescaped value is an ASCII-insensitive match for the given value. + #[inline] + pub fn expect_ident_matching( + &mut self, + expected_value: &str, + ) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Ident(ref value) if value.eq_ignore_ascii_case(expected_value) => Ok(()), + } + } + + /// Parse a <string-token> and return the unescaped value. + #[inline] + pub fn expect_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::QuotedString(ref value) => Ok(value), + } + } + + /// expect_string, but clone the CowRcStr + #[inline] + pub fn expect_string_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { + self.expect_string().map(|s| s.clone()) + } + + /// Parse either a <ident-token> or a <string-token>, and return the unescaped value. + #[inline] + pub fn expect_ident_or_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::Ident(ref value) => Ok(value), + Token::QuotedString(ref value) => Ok(value), + } + } + + /// Parse a <url-token> and return the unescaped value. + #[inline] + pub fn expect_url(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::UnquotedUrl(ref value) => Ok(value.clone()), + Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { + self.parse_nested_block(|input| { + input.expect_string().map_err(Into::into).map(|s| s.clone()) + }) + .map_err(ParseError::<()>::basic) + } + } + } + + /// Parse either a <url-token> or a <string-token>, and return the unescaped value. + #[inline] + pub fn expect_url_or_string(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::UnquotedUrl(ref value) => Ok(value.clone()), + Token::QuotedString(ref value) => Ok(value.clone()), + Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { + self.parse_nested_block(|input| { + input.expect_string().map_err(Into::into).map(|s| s.clone()) + }) + .map_err(ParseError::<()>::basic) + } + } + } + + /// Parse a <number-token> and return the integer value. + #[inline] + pub fn expect_number(&mut self) -> Result<f32, BasicParseError<'i>> { + expect! {self, + Token::Number { value, .. } => Ok(value), + } + } + + /// Parse a <number-token> that does not have a fractional part, and return the integer value. + #[inline] + pub fn expect_integer(&mut self) -> Result<i32, BasicParseError<'i>> { + expect! {self, + Token::Number { int_value: Some(int_value), .. } => Ok(int_value), + } + } + + /// Parse a <percentage-token> and return the value. + /// `0%` and `100%` map to `0.0` and `1.0` (not `100.0`), respectively. + #[inline] + pub fn expect_percentage(&mut self) -> Result<f32, BasicParseError<'i>> { + expect! {self, + Token::Percentage { unit_value, .. } => Ok(unit_value), + } + } + + /// Parse a `:` <colon-token>. + #[inline] + pub fn expect_colon(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Colon => Ok(()), + } + } + + /// Parse a `;` <semicolon-token>. + #[inline] + pub fn expect_semicolon(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Semicolon => Ok(()), + } + } + + /// Parse a `,` <comma-token>. + #[inline] + pub fn expect_comma(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Comma => Ok(()), + } + } + + /// Parse a <delim-token> with the given value. + #[inline] + pub fn expect_delim(&mut self, expected_value: char) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Delim(value) if value == expected_value => Ok(()), + } + } + + /// Parse a `{ /* ... */ }` curly brackets block. + /// + /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. + #[inline] + pub fn expect_curly_bracket_block(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::CurlyBracketBlock => Ok(()), + } + } + + /// Parse a `[ /* ... */ ]` square brackets block. + /// + /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. + #[inline] + pub fn expect_square_bracket_block(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::SquareBracketBlock => Ok(()), + } + } + + /// Parse a `( /* ... */ )` parenthesis block. + /// + /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. + #[inline] + pub fn expect_parenthesis_block(&mut self) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::ParenthesisBlock => Ok(()), + } + } + + /// Parse a <function> token and return its name. + /// + /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. + #[inline] + pub fn expect_function(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { + expect! {self, + Token::Function(ref name) => Ok(name), + } + } + + /// Parse a <function> token whose name is an ASCII-insensitive match for the given value. + /// + /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. + #[inline] + pub fn expect_function_matching( + &mut self, + expected_name: &str, + ) -> Result<(), BasicParseError<'i>> { + expect! {self, + Token::Function(ref name) if name.eq_ignore_ascii_case(expected_name) => Ok(()), + } + } + + /// Parse the input until exhaustion and check that it contains no “error” token. + /// + /// See `Token::is_parse_error`. This also checks nested blocks and functions recursively. + #[inline] + pub fn expect_no_error_token(&mut self) -> Result<(), BasicParseError<'i>> { + loop { + match self.next_including_whitespace_and_comments() { + Ok(&Token::Function(_)) + | Ok(&Token::ParenthesisBlock) + | Ok(&Token::SquareBracketBlock) + | Ok(&Token::CurlyBracketBlock) => self + .parse_nested_block(|input| input.expect_no_error_token().map_err(Into::into)) + .map_err(ParseError::<()>::basic)?, + Ok(t) => { + // FIXME: maybe these should be separate variants of + // BasicParseError instead? + if t.is_parse_error() { + let token = t.clone(); + return Err(self.new_basic_unexpected_token_error(token)); + } + } + Err(_) => return Ok(()), + } + } + } +} + +pub fn parse_until_before<'i: 't, 't, F, T, E>( + parser: &mut Parser<'i, 't>, + delimiters: Delimiters, + parse: F, +) -> Result<T, ParseError<'i, E>> +where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, +{ + let delimiters = parser.stop_before | delimiters; + 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 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. + loop { + if delimiters.contains(Delimiters::from_byte(parser.input.tokenizer.next_byte())) { + break; + } + if let Ok(token) = parser.input.tokenizer.next() { + if let Some(block_type) = BlockType::opening(&token) { + consume_until_end_of_block(block_type, &mut parser.input.tokenizer); + } + } else { + break; + } + } + result +} + +pub fn parse_until_after<'i: 't, 't, F, T, E>( + parser: &mut Parser<'i, 't>, + delimiters: Delimiters, + parse: F, +) -> Result<T, ParseError<'i, E>> +where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, +{ + let result = parser.parse_until_before(delimiters, parse); + let next_byte = parser.input.tokenizer.next_byte(); + if next_byte.is_some() + && !parser + .stop_before + .contains(Delimiters::from_byte(next_byte)) + { + debug_assert!(delimiters.contains(Delimiters::from_byte(next_byte))); + // We know this byte is ASCII. + parser.input.tokenizer.advance(1); + if next_byte == Some(b'{') { + consume_until_end_of_block(BlockType::CurlyBracket, &mut parser.input.tokenizer); + } + } + result +} + +pub fn parse_nested_block<'i: 't, 't, F, T, E>( + parser: &mut Parser<'i, 't>, + parse: F, +) -> Result<T, ParseError<'i, E>> +where + F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, +{ + let block_type = parser.at_start_of.take().expect( + "\ + A nested parser can only be created when a Function, \ + ParenthesisBlock, SquareBracketBlock, or CurlyBracketBlock \ + token was just consumed.\ + ", + ); + let closing_delimiter = match block_type { + BlockType::CurlyBracket => ClosingDelimiter::CloseCurlyBracket, + BlockType::SquareBracket => ClosingDelimiter::CloseSquareBracket, + BlockType::Parenthesis => ClosingDelimiter::CloseParenthesis, + }; + 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.input.tokenizer); + result +} + +#[inline(never)] +#[cold] +fn consume_until_end_of_block(block_type: BlockType, tokenizer: &mut Tokenizer) { + let mut stack = SmallVec::<[BlockType; 16]>::new(); + stack.push(block_type); + + // FIXME: have a special-purpose tokenizer method for this that does less work. + while let Ok(ref token) = tokenizer.next() { + if let Some(b) = BlockType::closing(token) { + if *stack.last().unwrap() == b { + stack.pop(); + if stack.is_empty() { + return; + } + } + } + + if let Some(block_type) = BlockType::opening(token) { + stack.push(block_type); + } + } +} |