use crate::cow_rc_str::CowRcStr;
use crate::tokenizer::{SourceLocation, SourcePosition, Token, Tokenizer};
use smallvec::SmallVec;
use std::fmt;
use std::ops::BitOr;
use std::ops::Range;
#[derive(Debug, Clone, Default)]
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 {
#[inline]
pub fn position(&self) -> SourcePosition {
SourcePosition(self.position)
}
#[inline]
pub fn source_location(&self) -> SourceLocation {
SourceLocation {
line: self.current_line_number,
column: (self.position - self.current_line_start_position + 1) as u32,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParseUntilErrorBehavior {
Consume,
Stop,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BasicParseErrorKind {
UnexpectedToken,
EndOfInput,
AtRuleInvalid,
AtRuleBodyInvalid,
QualifiedRuleInvalid,
TooManyNestedBlocks,
}
impl fmt::Display for BasicParseErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BasicParseErrorKind::TooManyNestedBlocks => {
write!(f, "nesting block limit reached")
}
BasicParseErrorKind::UnexpectedToken => write!(f, "unexpected token"),
BasicParseErrorKind::EndOfInput => write!(f, "unexpected end of input"),
BasicParseErrorKind::AtRuleInvalid => write!(f, "invalid @ rule encountered"),
BasicParseErrorKind::AtRuleBodyInvalid => write!(f, "invalid @ rule body encountered"),
BasicParseErrorKind::QualifiedRuleInvalid => {
write!(f, "invalid qualified rule encountered")
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BasicParseError {
pub kind: BasicParseErrorKind,
}
impl BasicParseError {
#[inline]
pub fn new(kind: BasicParseErrorKind) -> Self {
Self { kind }
}
#[inline]
pub fn unexpected_token() -> Self {
Self::new(BasicParseErrorKind::UnexpectedToken)
}
}
impl<T> From<BasicParseError> for ParseError<T> {
#[inline]
fn from(this: BasicParseError) -> ParseError<T> {
ParseError {
kind: ParseErrorKind::Basic(this.kind),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParseErrorKind<T> {
Basic(BasicParseErrorKind),
Custom(T),
}
impl<T> ParseErrorKind<T> {
pub fn into<U>(self) -> ParseErrorKind<U>
where
T: Into<U>,
{
match self {
ParseErrorKind::Basic(basic) => ParseErrorKind::Basic(basic),
ParseErrorKind::Custom(custom) => ParseErrorKind::Custom(custom.into()),
}
}
}
impl<E: fmt::Display> fmt::Display for ParseErrorKind<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseErrorKind::Basic(basic) => basic.fmt(f),
ParseErrorKind::Custom(custom) => custom.fmt(f),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParseError<E> {
pub kind: ParseErrorKind<E>,
}
impl<T> ParseError<T> {
#[inline]
pub fn from_basic_kind(kind: BasicParseErrorKind) -> Self {
Self {
kind: ParseErrorKind::Basic(kind),
}
}
#[inline]
pub fn unexpected_token() -> Self {
Self::from_basic_kind(BasicParseErrorKind::UnexpectedToken)
}
#[inline]
pub fn custom<E: Into<T>>(error: E) -> Self {
Self {
kind: ParseErrorKind::Custom(error.into()),
}
}
pub fn basic(self) -> BasicParseError {
match self.kind {
ParseErrorKind::Basic(kind) => BasicParseError { kind },
ParseErrorKind::Custom(_) => panic!("Not a basic parse error"),
}
}
pub fn into<U>(self) -> ParseError<U>
where
T: Into<U>,
{
ParseError {
kind: self.kind.into(),
}
}
}
impl<E: fmt::Display> fmt::Display for ParseError<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.kind.fmt(f)
}
}
impl<E: fmt::Display + fmt::Debug> std::error::Error for ParseError<E> {}
pub struct Parser<'i> {
tokenizer: Tokenizer<'i>,
cached_token: CachedToken<'i>,
current_block_depth: u8,
nested_block_limit: u8,
at_start_of: Option<BlockType>,
stop_before: Delimiters,
}
struct CachedToken<'i> {
token: Token<'i>,
start_position: SourcePosition,
end_state: ParserState,
}
#[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,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Delimiters {
bits: u8,
}
#[allow(non_upper_case_globals, non_snake_case)]
pub mod Delimiter {
use super::Delimiters;
pub const None: Delimiters = Delimiters { bits: 0 };
pub const CurlyBracketBlock: Delimiters = Delimiters { bits: 1 << 1 };
pub const Semicolon: Delimiters = Delimiters { bits: 1 << 2 };
pub const Bang: Delimiters = Delimiters { bits: 1 << 3 };
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]
pub(crate) fn from_byte(byte: u8) -> Delimiters {
const TABLE: [Delimiters; 256] = {
let mut table = [Delimiter::None; 256];
table[b';' as usize] = Delimiter::Semicolon;
table[b'!' as usize] = Delimiter::Bang;
table[b',' as usize] = Delimiter::Comma;
table[b'{' as usize] = Delimiter::CurlyBracketBlock;
table[b'}' as usize] = ClosingDelimiter::CloseCurlyBracket;
table[b']' as usize] = ClosingDelimiter::CloseSquareBracket;
table[b')' as usize] = ClosingDelimiter::CloseParenthesis;
table
};
TABLE[byte as usize]
}
}
macro_rules! expect {
($parser: ident, $($branches: tt)+) => {
{
match *$parser.next()? {
$($branches)+
_ => {
return Err(BasicParseError::unexpected_token())
}
}
}
}
}
pub type ArbitrarySubstitutionFunctions<'a> = &'a [&'static str];
impl<'i> Parser<'i> {
const REASONABLE_NESTED_BLOCK_LIMIT: u8 = 75;
#[inline]
pub fn new(input: &'i str) -> Self {
Self {
tokenizer: Tokenizer::new(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, start_position: SourcePosition(usize::MAX), end_state: ParserState::default(),
},
}
}
pub fn set_nested_block_limit(&mut self, limit: u8) {
self.nested_block_limit = limit;
}
pub fn current_line(&self) -> &'i str {
self.tokenizer.current_source_line()
}
#[inline]
pub fn is_exhausted(&mut self) -> bool {
self.expect_exhausted().is_ok()
}
#[inline]
pub fn expect_exhausted(&mut self) -> Result<(), BasicParseError> {
let start = self.state();
let result = match self.next() {
Err(BasicParseError {
kind: BasicParseErrorKind::EndOfInput,
..
}) => Ok(()),
Err(e) => unreachable!("Unexpected error encountered: {:?}", e),
Ok(_) => Err(BasicParseError::unexpected_token()),
};
self.reset(&start);
result
}
#[inline]
pub fn position(&self) -> SourcePosition {
self.tokenizer.position()
}
#[inline]
pub fn current_source_location(&self) -> SourceLocation {
self.tokenizer.current_source_location()
}
pub fn current_source_map_url(&self) -> Option<&str> {
self.tokenizer.current_source_map_url()
}
pub fn current_source_url(&self) -> Option<&str> {
self.tokenizer.current_source_url()
}
#[inline]
pub fn new_error_for_next_token<E>(&mut self) -> ParseError<E> {
match self.next() {
Ok(_) => ParseError::unexpected_token(),
Err(e) => e.into(),
}
}
#[inline]
pub fn state(&self) -> ParserState {
ParserState {
at_start_of: self.at_start_of,
..self.tokenizer.state()
}
}
#[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);
}
self.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);
}
self.tokenizer.skip_cdc_and_cdo()
}
#[inline]
pub(crate) fn next_byte(&self) -> Option<u8> {
let byte = self.tokenizer.next_byte()?;
if self.stop_before.contains(Delimiters::from_byte(byte)) {
return None;
}
Some(byte)
}
#[inline]
pub fn reset(&mut self, state: &ParserState) {
self.tokenizer.reset(state);
self.at_start_of = state.at_start_of;
}
#[inline]
pub fn look_for_arbitrary_substitution_functions(
&mut self,
fns: ArbitrarySubstitutionFunctions<'i>,
) {
self.tokenizer
.look_for_arbitrary_substitution_functions(fns)
}
#[inline]
pub fn seen_arbitrary_substitution_functions(&mut self) -> bool {
self.tokenizer.seen_arbitrary_substitution_functions()
}
#[inline]
pub fn r#try<F, T, E>(&mut self, thing: F) -> Result<T, E>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, E>,
{
self.try_parse(thing)
}
#[inline]
pub fn try_parse<F, T, E>(&mut self, thing: F) -> Result<T, E>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, E>,
{
let start = self.state();
let result = thing(self);
if result.is_err() {
self.reset(&start)
}
result
}
#[inline]
pub fn slice(&self, range: Range<SourcePosition>) -> &'i str {
self.tokenizer.slice(range)
}
#[inline]
pub fn slice_from(&self, start_position: SourcePosition) -> &'i str {
self.tokenizer.slice_from(start_position)
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<&Token<'i>, BasicParseError> {
self.skip_whitespace();
self.next_including_whitespace_and_comments()
}
pub fn next_including_whitespace(&mut self) -> Result<&Token<'i>, BasicParseError> {
while let Token::Comment(..) = self.next_including_whitespace_and_comments()? {
}
Ok(&self.cached_token.token)
}
pub fn next_including_whitespace_and_comments(
&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);
}
let Some(byte) = self.tokenizer.next_byte() else {
return Err(BasicParseError::new(BasicParseErrorKind::EndOfInput));
};
if self.stop_before.contains(Delimiters::from_byte(byte)) {
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 = if using_cached_token {
let cached_token = &self.cached_token;
self.tokenizer.reset(&cached_token.end_state);
if let Token::Function(ref name) = cached_token.token {
self.tokenizer.see_function(name)
}
&cached_token.token
} else {
let new_token = self.tokenizer.next_unchecked();
self.cached_token = CachedToken {
token: new_token,
start_position: token_start_position,
end_state: self.tokenizer.state(),
};
&self.cached_token.token
};
if let Some(block_type) = BlockType::opening(token) {
self.at_start_of = Some(block_type);
}
Ok(token)
}
#[inline]
pub fn parse_entirely<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
let result = parse(self)?;
self.expect_exhausted()?;
Ok(result)
}
#[inline]
pub fn parse_comma_separated<F, T, E>(&mut self, parse_one: F) -> Result<Vec<T>, ParseError<E>>
where
F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
self.parse_comma_separated_internal(parse_one, false)
}
#[inline]
pub fn parse_comma_separated_ignoring_errors<F, T, E>(&mut self, parse_one: F) -> Vec<T>
where
F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
match self.parse_comma_separated_internal(parse_one, true) {
Ok(values) => values,
Err(..) => unreachable!(),
}
}
#[inline]
fn parse_comma_separated_internal<F, T, E>(
&mut self,
mut parse_one: F,
ignore_errors: bool,
) -> Result<Vec<T>, ParseError<E>>
where
F: FnMut(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
let mut values = Vec::with_capacity(1);
loop {
self.skip_whitespace(); match self.parse_until_before(Delimiter::Comma, &mut parse_one) {
Ok(v) => values.push(v),
Err(e) if !ignore_errors => return Err(e),
Err(_) => {}
}
match self.next() {
Err(_) => return Ok(values),
Ok(&Token::Comma) => continue,
Ok(_) => unreachable!(),
}
}
}
#[inline]
pub fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
parse_nested_block(self, parse)
}
#[inline]
pub fn parse_until_before<F, T, E>(
&mut self,
delimiters: Delimiters,
parse: F,
) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
parse_until_before(self, delimiters, ParseUntilErrorBehavior::Consume, parse)
}
#[inline]
pub fn parse_until_after<F, T, E>(
&mut self,
delimiters: Delimiters,
parse: F,
) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
parse_until_after(self, delimiters, ParseUntilErrorBehavior::Consume, parse)
}
#[inline]
pub fn expect_whitespace(&mut self) -> Result<&'i str, BasicParseError> {
match *self.next_including_whitespace()? {
Token::WhiteSpace(value) => Ok(value),
_ => Err(BasicParseError::unexpected_token()),
}
}
#[inline]
pub fn expect_ident(&mut self) -> Result<&CowRcStr<'i>, BasicParseError> {
expect! {self,
Token::Ident(ref value) => Ok(value),
}
}
#[inline]
pub fn expect_ident_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError> {
self.expect_ident().cloned()
}
#[inline]
pub fn expect_ident_matching(&mut self, expected_value: &str) -> Result<(), BasicParseError> {
expect! {self,
Token::Ident(ref value) if value.eq_ignore_ascii_case(expected_value) => Ok(()),
}
}
#[inline]
pub fn expect_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError> {
expect! {self,
Token::QuotedString(ref value) => Ok(value),
}
}
#[inline]
pub fn expect_string_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError> {
self.expect_string().cloned()
}
#[inline]
pub fn expect_ident_or_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError> {
expect! {self,
Token::Ident(ref value) => Ok(value),
Token::QuotedString(ref value) => Ok(value),
}
}
#[inline]
pub fn expect_url(&mut self) -> Result<CowRcStr<'i>, BasicParseError> {
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).cloned()
})
.map_err(ParseError::<()>::basic)
}
}
}
#[inline]
pub fn expect_url_or_string(&mut self) -> Result<CowRcStr<'i>, BasicParseError> {
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).cloned()
})
.map_err(ParseError::<()>::basic)
}
}
}
#[inline]
pub fn expect_number(&mut self) -> Result<f32, BasicParseError> {
expect! {self,
Token::Number { value, .. } => Ok(value),
}
}
#[inline]
pub fn expect_integer(&mut self) -> Result<i32, BasicParseError> {
expect! {self,
Token::Number { int_value: Some(int_value), .. } => Ok(int_value),
}
}
#[inline]
pub fn expect_percentage(&mut self) -> Result<f32, BasicParseError> {
expect! {self,
Token::Percentage { unit_value, .. } => Ok(unit_value),
}
}
#[inline]
pub fn expect_colon(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::Colon => Ok(()),
}
}
#[inline]
pub fn expect_semicolon(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::Semicolon => Ok(()),
}
}
#[inline]
pub fn expect_comma(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::Comma => Ok(()),
}
}
#[inline]
pub fn expect_delim(&mut self, expected_value: char) -> Result<(), BasicParseError> {
expect! {self,
Token::Delim(value) if value == expected_value => Ok(()),
}
}
#[inline]
pub fn expect_curly_bracket_block(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::CurlyBracketBlock => Ok(()),
}
}
#[inline]
pub fn expect_square_bracket_block(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::SquareBracketBlock => Ok(()),
}
}
#[inline]
pub fn expect_parenthesis_block(&mut self) -> Result<(), BasicParseError> {
expect! {self,
Token::ParenthesisBlock => Ok(()),
}
}
#[inline]
pub fn expect_function(&mut self) -> Result<&CowRcStr<'i>, BasicParseError> {
expect! {self,
Token::Function(ref name) => Ok(name),
}
}
#[inline]
pub fn expect_function_matching(&mut self, expected_name: &str) -> Result<(), BasicParseError> {
expect! {self,
Token::Function(ref name) if name.eq_ignore_ascii_case(expected_name) => Ok(()),
}
}
#[inline]
pub fn expect_no_error_token(&mut self) -> Result<(), BasicParseError> {
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) => {
if t.is_parse_error() {
return Err(BasicParseError::unexpected_token());
}
}
Err(_) => return Ok(()),
}
}
}
}
pub fn parse_until_before<'i, F, T, E>(
parser: &mut Parser<'i>,
delimiters: Delimiters,
error_behavior: ParseUntilErrorBehavior,
parse: F,
) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
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);
}
while let Some(next_byte) = parser.tokenizer.next_byte() {
if delimiters.contains(Delimiters::from_byte(next_byte)) {
break;
}
let token = parser.tokenizer.next_unchecked();
if let Some(block_type) = BlockType::opening(&token) {
consume_until_end_of_block(block_type, &mut parser.tokenizer);
}
}
result
}
pub fn parse_until_after<'i, F, T, E>(
parser: &mut Parser<'i>,
delimiters: Delimiters,
error_behavior: ParseUntilErrorBehavior,
parse: F,
) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
{
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() {
let delimiter = Delimiters::from_byte(next_byte);
if !parser.stop_before.contains(delimiter) {
debug_assert!(delimiters.contains(delimiter));
parser.tokenizer.advance(1);
if next_byte == b'{' {
consume_until_end_of_block(BlockType::CurlyBracket, &mut parser.tokenizer);
}
}
}
result
}
pub fn parse_nested_block<'i, F, T, E>(
parser: &mut Parser<'i>,
parse: F,
) -> Result<T, ParseError<E>>
where
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<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.\
",
);
if parser.current_block_depth >= parser.nested_block_limit && parser.nested_block_limit != 0 {
return Err(ParseError::from_basic_kind(
BasicParseErrorKind::TooManyNestedBlocks,
));
}
parser.current_block_depth = parser.current_block_depth.wrapping_add(1);
let old_stop_before = parser.stop_before;
parser.stop_before = 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);
}
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);
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);
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);
}
}
}