use super::CssParser;
use super::selectors::{
NthTerminator, nth_arg_terminator, parse_complex_selector_list, parse_forgiving_selector_list,
parse_relative_selector_list,
};
use crate::ast::*;
use crate::lexer::TokenKind;
use crate::whitespace::is_boundary_whitespace;
use tsv_lang::{Error, Span};
pub(crate) fn parse_pseudo_selector<'arena>(
parser: &mut CssParser<'_, 'arena>,
start: usize,
) -> Result<SimpleSelector<'arena>, Error> {
parser.advance()?; parser.register_and_skip_comments()?;
let is_pseudo_element = parser.check(TokenKind::Colon);
if is_pseudo_element {
parser.advance()?; parser.register_and_skip_comments()?;
}
if !parser.check(TokenKind::Identifier) {
return Err(parser.error_expected("pseudo-class or pseudo-element name"));
}
let arg_kind = if parser.peek_kind_outlined()? == TokenKind::LeftParen {
Some(classify_pseudo_args(
parser.current_identifier(),
is_pseudo_element,
))
} else {
None
};
let name_end = parser.span_pos(parser.current_end());
let mut end = name_end;
parser.advance()?;
let args = if let Some(arg_kind) = arg_kind {
let (args_opt, args_end) = parse_pseudo_args(parser, arg_kind)?;
end = args_end; args_opt
} else {
None
};
if is_pseudo_element {
Ok(SimpleSelector::PseudoElement {
args,
name_end,
span: Span {
start: start as u32,
end,
},
})
} else {
Ok(SimpleSelector::PseudoClass {
args,
name_end,
span: Span {
start: start as u32,
end,
},
})
}
}
#[derive(Clone, Copy)]
enum SelectorListGrammar {
Relative,
Forgiving,
Complex,
}
#[derive(Clone, Copy)]
enum PseudoArgKind {
SelectorList(SelectorListGrammar),
Part,
Nth,
Unknown,
}
fn classify_pseudo_args(pseudo_name: &str, is_pseudo_element: bool) -> PseudoArgKind {
let lower_name;
let pseudo_name: &str = if pseudo_name.bytes().any(|b| b.is_ascii_uppercase()) {
lower_name = pseudo_name.to_ascii_lowercase();
&lower_name
} else {
pseudo_name
};
match pseudo_name {
"slotted" if is_pseudo_element => PseudoArgKind::SelectorList(SelectorListGrammar::Complex),
"part" if is_pseudo_element => PseudoArgKind::Part,
"has" => PseudoArgKind::SelectorList(SelectorListGrammar::Relative),
"is" | "where" => PseudoArgKind::SelectorList(SelectorListGrammar::Forgiving),
"not" | "global" | "dir" | "lang" | "highlight" => {
PseudoArgKind::SelectorList(SelectorListGrammar::Complex)
}
"nth-child" | "nth-of-type" | "nth-last-child" | "nth-last-of-type" | "nth-col"
| "nth-last-col" => PseudoArgKind::Nth,
_ => PseudoArgKind::Unknown,
}
}
fn parse_pseudo_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
arg_kind: PseudoArgKind,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.expect(TokenKind::LeftParen)?;
let args_start = parser.current_start();
match arg_kind {
PseudoArgKind::SelectorList(grammar) => selector_list_args(parser, args_start, grammar),
PseudoArgKind::Part => parse_part_args(parser, args_start),
PseudoArgKind::Nth => parse_nth_args(parser, args_start),
PseudoArgKind::Unknown => with_pseudo_args(parser, |p| parse_unknown_args(p, args_start)),
}
}
fn with_pseudo_args<'arena, R>(
parser: &mut CssParser<'_, 'arena>,
f: impl FnOnce(&mut CssParser<'_, 'arena>) -> Result<R, Error>,
) -> Result<R, Error> {
let saved = parser.in_pseudo_args;
parser.in_pseudo_args = true;
let result = parser.nested(4, f);
parser.in_pseudo_args = saved;
result
}
fn finish_selector_list_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
selectors: SelectorList<'arena>,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.skip_boundary_whitespace_registering_comments()?;
let end = parser.expect_and_capture(TokenKind::RightParen)?;
Ok((
Some(PseudoClassArgs::SelectorList {
selectors,
span: Span {
start: parser.span_pos(args_start),
end,
},
}),
end,
))
}
fn parse_part_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.skip_boundary_whitespace_registering_comments()?;
let mut ident_spans: bumpalo::collections::Vec<'_, Span> = parser.bvec();
while !parser.check(TokenKind::RightParen) && !parser.check(TokenKind::Eof) {
if !parser.check(TokenKind::Identifier) {
return Err(parser.error_msg("::part() requires identifier arguments"));
}
ident_spans.push(Span {
start: parser.span_pos(parser.current_start()),
end: parser.span_pos(parser.current_end()),
});
parser.advance()?;
parser.skip_boundary_whitespace_registering_comments()?;
}
if ident_spans.is_empty() {
return Err(parser.error_msg_at(
"::part() requires at least one identifier",
parser.base_offset() + args_start,
));
}
let end = parser.expect_and_capture(TokenKind::RightParen)?;
Ok((
Some(PseudoClassArgs::Part {
ident_spans: ident_spans.into_bump_slice(),
span: Span {
start: parser.span_pos(args_start),
end,
},
}),
end,
))
}
fn selector_list_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
grammar: SelectorListGrammar,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
with_pseudo_args(parser, |p| parse_selector_list_args(p, args_start, grammar))
}
fn parse_selector_list_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
grammar: SelectorListGrammar,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.skip_boundary_whitespace_registering_comments()?;
let selector_list = match grammar {
SelectorListGrammar::Relative => parse_relative_selector_list(parser)?,
SelectorListGrammar::Forgiving => parse_forgiving_selector_list(parser)?,
SelectorListGrammar::Complex => parse_complex_selector_list(parser)?,
};
finish_selector_list_args(parser, args_start, selector_list)
}
fn parse_nth_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.skip_boundary_whitespace_registering_comments()?;
let anb_start = parser.current_start();
let Some(terminator) = nth_arg_terminator(parser.source(), anb_start) else {
let selectors = with_pseudo_args(parser, parse_complex_selector_list)?;
return finish_selector_list_args(parser, args_start, selectors);
};
let (anb_end, found_of) = match terminator {
NthTerminator::Paren(at) => (at, false),
NthTerminator::Of(at) => (at, true),
};
let anb_raw = &parser.source()[anb_start..anb_end];
let anb_trimmed_start =
anb_start + (anb_raw.len() - anb_raw.trim_start_matches(is_boundary_whitespace).len());
let anb_value = parser.alloc_str_in(anb_raw.trim_matches(is_boundary_whitespace));
let value_span = Span {
start: parser.span_pos(anb_trimmed_start),
end: parser.span_pos(anb_trimmed_start + anb_value.len()),
};
parser.resume_at(if found_of { anb_end + 2 } else { anb_end })?;
let of_selector = if found_of {
parser.skip_boundary_whitespace_registering_comments()?;
Some(parser.nested(4, |p| with_pseudo_args(p, parse_complex_selector_list))?)
} else {
None
};
parser.skip_boundary_whitespace_registering_comments()?;
let span_end = parser.span_pos(parser.current_start()); let paren_end = parser.expect_and_capture(TokenKind::RightParen)?;
Ok((
Some(PseudoClassArgs::Nth {
value: anb_value,
of_selector,
span: Span {
start: parser.span_pos(args_start),
end: span_end,
},
value_span,
}),
paren_end,
))
}
fn parse_unknown_args<'arena>(
parser: &mut CssParser<'_, 'arena>,
args_start: usize,
) -> Result<(Option<PseudoClassArgs<'arena>>, u32), Error> {
parser.skip_boundary_whitespace_registering_comments()?;
match parse_complex_selector_list(parser) {
Ok(selector_list) => finish_selector_list_args(parser, args_start, selector_list),
Err(error) if error.kind() == tsv_lang::ErrorKind::TooDeep => Err(error),
Err(_) => {
let mut depth = 1;
while depth > 0 && !parser.check(TokenKind::Eof) {
if parser.check(TokenKind::LeftParen) {
depth += 1;
} else if parser.check(TokenKind::RightParen) {
depth -= 1;
if depth == 0 {
break;
}
}
if depth > 0 {
parser.advance()?;
}
}
let end = parser.expect_and_capture(TokenKind::RightParen)?;
Ok((None, end))
}
}
}