Source code
Revision control
Copy as Markdown
Other Tools
/* 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
use super::{BasicParseError, BasicParseErrorKind, Delimiter, ParseError, Parser, Token};
use crate::cow_rc_str::CowRcStr;
use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState};
/// Parse `!important`.
///
/// Typical usage is `input.try_parse(parse_important).is_ok()`
/// at the end of a `DeclarationParser::parse_value` implementation.
pub fn parse_important<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> {
input.expect_delim('!')?;
input.expect_ident_matching("important")
}
/// A trait to provide various parsing of declaration values.
///
/// For example, there could be different implementations for property declarations in style rules
/// and for descriptors in `@font-face` rules.
pub trait DeclarationParser<'i> {
/// The finished representation of a declaration.
type Declaration;
/// The error type that is included in the ParseError value that can be returned.
type Error: 'i;
/// Parse the value of a declaration with the given `name`.
///
/// Return the finished representation for the declaration
/// as returned by `DeclarationListParser::next`,
/// or an `Err(..)` to ignore the entire declaration as invalid.
///
/// Declaration name matching should be case-insensitive in the ASCII range.
/// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
/// or with the `match_ignore_ascii_case!` macro.
///
/// The given `input` is a "delimited" parser
/// that ends wherever the declaration value should end.
/// (In declaration lists, before the next semicolon or end of the current block.)
///
/// If `!important` can be used in a given context,
/// `input.try_parse(parse_important).is_ok()` should be used at the end
/// of the implementation of this method and the result should be part of the return value.
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
Err(input.new_error(BasicParseErrorKind::UnexpectedToken(Token::Ident(name))))
}
}
/// A trait to provide various parsing of at-rules.
///
/// For example, there could be different implementations for top-level at-rules
/// (`@media`, `@font-face`, …)
/// and for page-margin rules inside `@page`.
///
/// Default implementations that reject all at-rules are provided,
/// so that `impl AtRuleParser<(), ()> for ... {}` can be used
/// for using `DeclarationListParser` to parse a declarations list with only qualified rules.
pub trait AtRuleParser<'i> {
/// The intermediate representation of prelude of an at-rule.
type Prelude;
/// The finished representation of an at-rule.
type AtRule;
/// The error type that is included in the ParseError value that can be returned.
type Error: 'i;
/// Parse the prelude of an at-rule with the given `name`.
///
/// Return the representation of the prelude and the type of at-rule,
/// or an `Err(..)` to ignore the entire at-rule as invalid.
///
/// The prelude is the part after the at-keyword
/// and before the `;` semicolon or `{ /* ... */ }` block.
///
/// At-rule name matching should be case-insensitive in the ASCII range.
/// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
/// or with the `match_ignore_ascii_case!` macro.
///
/// The given `input` is a "delimited" parser
/// that ends wherever the prelude should end.
/// (Before the next semicolon, the next `{`, or the end of the current block.)
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
}
/// End an at-rule which doesn't have block. Return the finished
/// representation of the at-rule.
///
/// The location passed in is source location of the start of the prelude.
///
/// This is only called when `parse_prelude` returned `WithoutBlock`, and
/// either the `;` semicolon indeed follows the prelude, or parser is at
/// the end of the input.
#[allow(clippy::result_unit_err)]
fn rule_without_block(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
) -> Result<Self::AtRule, ()> {
let _ = prelude;
let _ = start;
Err(())
}
/// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.
///
/// The location passed in is source location of the start of the prelude.
///
/// Return the finished representation of the at-rule
/// as returned by `RuleListParser::next` or `DeclarationListParser::next`,
/// or an `Err(..)` to ignore the entire at-rule as invalid.
///
/// This is only called when `parse_prelude` returned `WithBlock`, and a block
/// was indeed found following the prelude.
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
let _ = prelude;
let _ = start;
Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))
}
}
/// A trait to provide various parsing of qualified rules.
///
/// For example, there could be different implementations for top-level qualified rules (i.e. style
/// rules with Selectors as prelude) and for qualified rules inside `@keyframes` (keyframe rules
/// with keyframe selectors as prelude).
///
/// Default implementations that reject all qualified rules are provided, so that
/// `impl QualifiedRuleParser<(), ()> for ... {}` can be used for example for using
/// `RuleListParser` to parse a rule list with only at-rules (such as inside
/// `@font-feature-values`).
pub trait QualifiedRuleParser<'i> {
/// The intermediate representation of a qualified rule prelude.
type Prelude;
/// The finished representation of a qualified rule.
type QualifiedRule;
/// The error type that is included in the ParseError value that can be returned.
type Error: 'i;
/// Parse the prelude of a qualified rule. For style rules, this is as Selector list.
///
/// Return the representation of the prelude,
/// or an `Err(..)` to ignore the entire at-rule as invalid.
///
/// The prelude is the part before the `{ /* ... */ }` block.
///
/// The given `input` is a "delimited" parser
/// that ends where the prelude should end (before the next `{`).
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
}
/// Parse the content of a `{ /* ... */ }` block for the body of the qualified rule.
///
/// The location passed in is source location of the start of the prelude.
///
/// Return the finished representation of the qualified rule
/// as returned by `RuleListParser::next`,
/// or an `Err(..)` to ignore the entire at-rule as invalid.
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
let _ = prelude;
let _ = start;
Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
}
}
/// Provides an iterator for rule bodies and declaration lists.
pub struct RuleBodyParser<'i, 't, 'a, P, I, E> {
/// The input given to the parser.
pub input: &'a mut Parser<'i, 't>,
/// The parser given to `DeclarationListParser::new`
pub parser: &'a mut P,
_phantom: std::marker::PhantomData<(I, E)>,
}
/// A parser for a rule body item.
pub trait RuleBodyItemParser<'i, DeclOrRule, Error: 'i>:
DeclarationParser<'i, Declaration = DeclOrRule, Error = Error>
+ QualifiedRuleParser<'i, QualifiedRule = DeclOrRule, Error = Error>
+ AtRuleParser<'i, AtRule = DeclOrRule, Error = Error>
{
/// Whether we should attempt to parse declarations. If you know you won't, returning false
/// here is slightly faster.
fn parse_declarations(&self) -> bool;
/// Whether we should attempt to parse qualified rules. If you know you won't, returning false
/// would be slightly faster.
fn parse_qualified(&self) -> bool;
}
impl<'i, 't, 'a, P, I, E> RuleBodyParser<'i, 't, 'a, P, I, E> {
/// Create a new `DeclarationListParser` for the given `input` and `parser`.
///
/// Note that all CSS declaration lists can on principle contain at-rules.
/// Even if no such valid at-rule exists (yet),
/// this affects error handling: at-rules end at `{}` blocks, not just semicolons.
///
/// The given `parser` therefore needs to implement
/// both `DeclarationParser` and `AtRuleParser` traits.
/// However, the latter can be an empty `impl`
/// since `AtRuleParser` provides default implementations of its methods.
///
/// The return type for finished declarations and at-rules also needs to be the same,
/// since `<DeclarationListParser as Iterator>::next` can return either.
/// It could be a custom enum.
pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
Self {
input,
parser,
_phantom: std::marker::PhantomData,
}
}
}
impl<'i, 't, 'a, I, P, E: 'i> Iterator for RuleBodyParser<'i, 't, 'a, P, I, E>
where
P: RuleBodyItemParser<'i, I, E>,
{
type Item = Result<I, (ParseError<'i, E>, &'i str)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.input.skip_whitespace();
let start = self.input.state();
match self.input.next_including_whitespace_and_comments().ok()? {
Token::CloseCurlyBracket
| Token::WhiteSpace(..)
| Token::Semicolon
| Token::Comment(..) => continue,
Token::AtKeyword(ref name) => {
let name = name.clone();
return Some(parse_at_rule(&start, name, self.input, &mut *self.parser));
}
// keep parsing as a qualified rule if the token is not an ident, so we implement
// 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,
error_behavior,
|input| {
input.expect_colon()?;
parser.parse_value(name, input)
},
)
};
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.
if let Ok(qual) = parse_qualified_rule(
&start,
self.input,
&mut *self.parser,
/* nested = */ true,
) {
return Some(Ok(qual));
}
}
return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
}
token => {
let result = if self.parser.parse_qualified() {
self.input.reset(&start);
let nested = self.parser.parse_declarations();
parse_qualified_rule(&start, self.input, &mut *self.parser, nested)
} else {
let token = token.clone();
self.input.parse_until_after(Delimiter::Semicolon, |_| {
Err(start.source_location().new_unexpected_token_error(token))
})
};
return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
}
}
}
}
}
/// Provides an iterator for rule list parsing at the top-level of a stylesheet.
pub struct StyleSheetParser<'i, 't, 'a, P> {
/// The input given.
pub input: &'a mut Parser<'i, 't>,
/// The parser given.
pub parser: &'a mut P,
any_rule_so_far: bool,
}
impl<'i, 't, 'a, R, P, E: 'i> StyleSheetParser<'i, 't, 'a, P>
where
P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
+ AtRuleParser<'i, AtRule = R, Error = E>,
{
/// The given `parser` needs to implement both `QualifiedRuleParser` and `AtRuleParser` traits.
/// However, either of them can be an empty `impl` since the traits provide default
/// implementations of their methods.
///
/// The return type for finished qualified rules and at-rules also needs to be the same,
/// since `<RuleListParser as Iterator>::next` can return either. It could be a custom enum.
pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
Self {
input,
parser,
any_rule_so_far: false,
}
}
}
/// `RuleListParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one.
impl<'i, 't, 'a, R, P, E: 'i> Iterator for StyleSheetParser<'i, 't, 'a, P>
where
P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
+ AtRuleParser<'i, AtRule = R, Error = E>,
{
type Item = Result<R, (ParseError<'i, E>, &'i str)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.input.skip_cdc_and_cdo();
let start = self.input.state();
let at_keyword = match self.input.next_byte()? {
b'@' => match self.input.next_including_whitespace_and_comments() {
Ok(Token::AtKeyword(name)) => Some(name.clone()),
_ => {
self.input.reset(&start);
None
}
},
_ => None,
};
if let Some(name) = at_keyword {
let first_stylesheet_rule = !self.any_rule_so_far;
self.any_rule_so_far = true;
if first_stylesheet_rule && name.eq_ignore_ascii_case("charset") {
let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
let _: Result<(), ParseError<()>> =
self.input.parse_until_after(delimiters, |_| Ok(()));
} else {
return Some(parse_at_rule(
&start,
name.clone(),
self.input,
&mut *self.parser,
));
}
} else {
self.any_rule_so_far = true;
let result = parse_qualified_rule(
&start,
self.input,
&mut *self.parser,
/* nested = */ false,
);
return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
}
}
}
}
/// Parse a single declaration, such as an `( /* ... */ )` parenthesis in an `@supports` prelude.
pub fn parse_one_declaration<'i, 't, P, E>(
input: &mut Parser<'i, 't>,
parser: &mut P,
) -> Result<<P as DeclarationParser<'i>>::Declaration, (ParseError<'i, E>, &'i str)>
where
P: DeclarationParser<'i, Error = E>,
{
let start_position = input.position();
input
.parse_entirely(|input| {
let name = input.expect_ident()?.clone();
input.expect_colon()?;
parser.parse_value(name, input)
})
.map_err(|e| (e, input.slice_from(start_position)))
}
/// Parse a single rule, such as for CSSOM’s `CSSStyleSheet.insertRule`.
pub fn parse_one_rule<'i, 't, R, P, E>(
input: &mut Parser<'i, 't>,
parser: &mut P,
) -> Result<R, ParseError<'i, E>>
where
P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
+ AtRuleParser<'i, AtRule = R, Error = E>,
{
input.parse_entirely(|input| {
input.skip_whitespace();
let start = input.state();
let at_keyword = if input.next_byte() == Some(b'@') {
match *input.next_including_whitespace_and_comments()? {
Token::AtKeyword(ref name) => Some(name.clone()),
_ => {
input.reset(&start);
None
}
}
} else {
None
};
if let Some(name) = at_keyword {
parse_at_rule(&start, name, input, parser).map_err(|e| e.0)
} else {
parse_qualified_rule(&start, input, parser, /* nested = */ false)
}
})
}
fn parse_at_rule<'i, 't, P, E>(
start: &ParserState,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
parser: &mut P,
) -> Result<<P as AtRuleParser<'i>>::AtRule, (ParseError<'i, E>, &'i str)>
where
P: AtRuleParser<'i, Error = E>,
{
let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
let result = input.parse_until_before(delimiters, |input| parser.parse_prelude(name, input));
match result {
Ok(prelude) => {
let result = match input.next() {
Ok(&Token::Semicolon) | Err(_) => parser
.rule_without_block(prelude, start)
.map_err(|()| input.new_unexpected_token_error(Token::Semicolon)),
Ok(&Token::CurlyBracketBlock) => {
parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
}
Ok(_) => unreachable!(),
};
result.map_err(|e| (e, input.slice_from(start.position())))
}
Err(error) => {
let end_position = input.position();
match input.next() {
Ok(&Token::CurlyBracketBlock) | Ok(&Token::Semicolon) | Err(_) => {}
_ => unreachable!(),
};
Err((error, input.slice(start.position()..end_position)))
}
}
}
// 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...
fn looks_like_a_custom_property(input: &mut Parser) -> bool {
let ident = match input.expect_ident() {
Ok(i) => i,
Err(..) => return false,
};
ident.starts_with("--") && input.expect_colon().is_ok()
}
fn parse_qualified_rule<'i, 't, P, E>(
start: &ParserState,
input: &mut Parser<'i, 't>,
parser: &mut P,
nested: bool,
) -> Result<<P as QualifiedRuleParser<'i>>::QualifiedRule, ParseError<'i, E>>
where
P: QualifiedRuleParser<'i, Error = E>,
{
input.skip_whitespace();
let prelude = {
let state = input.state();
if looks_like_a_custom_property(input) {
// If nested is true, consume the remnants of a bad declaration from input, with
// nested set to true, and return nothing.
// If nested is false, consume a block from input, and return nothing.
let delimiters = if nested {
Delimiter::Semicolon
} else {
Delimiter::CurlyBracketBlock
};
let _: Result<(), ParseError<()>> = input.parse_until_after(delimiters, |_| Ok(()));
return Err(state
.source_location()
.new_error(BasicParseErrorKind::QualifiedRuleInvalid));
}
let delimiters = if nested {
Delimiter::Semicolon | Delimiter::CurlyBracketBlock
} else {
Delimiter::CurlyBracketBlock
};
input.reset(&state);
input.parse_until_before(delimiters, |input| parser.parse_prelude(input))
};
input.expect_curly_bracket_block()?;
// Do this here so that we consume the `{` even if the prelude is `Err`.
let prelude = prelude?;
parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
}