use winnow::combinator::{alt, opt, peek, preceded, separated, terminated};
use winnow::error::ErrMode;
use winnow::stream::{Location, Stream};
use winnow::token::one_of;
use winnow::{ModalParser, Parser};
use crate::{
CharLit, ErrorContext, InputStream, Num, ParseErr, ParseResult, PathComponent,
PathOrIdentifier, Span, StrLit, WithSpan, bool_lit, can_be_variable_name, char_lit, cut_error,
identifier, is_rust_keyword, keyword, num_lit, path_or_identifier, str_lit, ws,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Target<'a> {
Name(WithSpan<&'a str>),
Tuple(WithSpan<(Vec<PathComponent<'a>>, Vec<Target<'a>>)>),
Array(WithSpan<Vec<Target<'a>>>),
Struct(WithSpan<(Vec<PathComponent<'a>>, Vec<NamedTarget<'a>>)>),
NumLit(WithSpan<&'a str>, Num<'a>),
StrLit(WithSpan<StrLit<'a>>),
CharLit(WithSpan<CharLit<'a>>),
BoolLit(WithSpan<&'a str>),
Path(WithSpan<Vec<PathComponent<'a>>>),
OrChain(WithSpan<Vec<Target<'a>>>),
Placeholder(WithSpan<()>),
Rest(WithSpan<Option<WithSpan<&'a str>>>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct NamedTarget<'a> {
pub src: WithSpan<&'a str>,
pub dest: Target<'a>,
}
impl<'a> From<(WithSpan<&'a str>, Target<'a>)> for NamedTarget<'a> {
#[inline]
fn from((src, dest): (WithSpan<&'a str>, Target<'a>)) -> Self {
Self { src, dest }
}
}
impl<'a: 'l, 'l> Target<'a> {
pub fn span(&self) -> Span {
match self {
Target::Name(v) => v.span(),
Target::Tuple(v) => v.span(),
Target::Array(v) => v.span(),
Target::Struct(v) => v.span(),
Target::NumLit(v, _) => v.span(),
Target::StrLit(v) => v.span(),
Target::CharLit(v) => v.span(),
Target::BoolLit(v) => v.span(),
Target::Path(v) => v.span(),
Target::OrChain(v) => v.span(),
Target::Placeholder(v) => v.span(),
Target::Rest(v) => v.span(),
}
}
pub(super) fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
enum OneOrMany<'a> {
One(Target<'a>),
Many(Vec<Target<'a>>),
}
let mut or_more = opt(preceded(ws(keyword("or")), Self::parse_one));
let one_or_many = |i: &mut _| {
let target = Self::parse_one(i)?;
let Some(snd_target) = or_more.parse_next(i)? else {
return Ok(OneOrMany::One(target));
};
let mut targets = vec![target, snd_target];
while let Some(target) = or_more.parse_next(i)? {
targets.push(target);
}
Ok(OneOrMany::Many(targets))
};
let _level_guard = i.state.level.nest(i)?;
let (inner, span) = one_or_many.with_span().parse_next(i)?;
match inner {
OneOrMany::One(target) => Ok(target),
OneOrMany::Many(targets) => Ok(Self::OrChain(WithSpan::new(targets, span))),
}
}
fn parse_one(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
let mut opt_opening_paren = opt(ws('(').span()).map(|o| o.is_some());
let mut opt_opening_brace = opt(ws('{').span()).map(|o| o.is_some());
let mut opt_opening_bracket = opt(ws('[').span()).map(|o| o.is_some());
let lit = ws(opt(Self::lit)).parse_next(i)?;
if let Some(lit) = lit {
return Ok(lit);
}
let start = i.current_token_start();
let target_is_tuple = opt_opening_paren.parse_next(i)?;
if target_is_tuple {
let (is_singleton, mut targets) = collect_targets(i, ')', Self::unnamed)?;
if is_singleton && let Some(target) = targets.pop() {
return Ok(target);
}
let range = start..i.current_token_start();
let targets = only_one_rest_pattern(targets, false, "tuple")?;
return Ok(Self::Tuple(WithSpan::new((Vec::new(), targets), range)));
}
let target_is_array = opt_opening_bracket.parse_next(i)?;
if target_is_array {
let targets = collect_targets(i, ']', Self::unnamed)?.1;
let inner = only_one_rest_pattern(targets, true, "array")?;
let range = start..i.current_token_start();
return Ok(Self::Array(WithSpan::new(inner, range)));
}
let path = path_or_identifier.verify_map(|r| match r {
PathOrIdentifier::Path(v) => Some(v),
PathOrIdentifier::Identifier(_) => None,
});
let path = opt(path.with_span()).parse_next(i)?;
if let Some((path, path_span)) = path {
let i_before_matching_with = i.checkpoint();
let _ = opt(ws(keyword("with"))).parse_next(i)?;
let is_unnamed_struct = opt_opening_paren.parse_next(i)?;
if is_unnamed_struct {
let targets = collect_targets(i, ')', Self::unnamed)?.1;
let inner = only_one_rest_pattern(targets, false, "struct")?;
return Ok(Self::Tuple(WithSpan::new((path, inner), path_span)));
}
let is_named_struct = opt_opening_brace.parse_next(i)?;
if is_named_struct {
let targets = collect_targets(i, '}', Self::named)?.1;
return Ok(Self::Struct(WithSpan::new((path, targets), path_span)));
}
if let [arg] = path.as_slice() {
if !can_be_variable_name(*arg.name) {
return cut_error!(
format!(
"`{}` cannot be used as an identifier",
arg.name.escape_debug()
),
arg.name.span
);
}
} else {
if let Some(arg) = path.iter().skip(1).find(|n| !can_be_variable_name(*n.name)) {
return cut_error!(
format!(
"`{}` cannot be used as an identifier",
arg.name.escape_debug()
),
arg.name.span
);
}
}
i.reset(&i_before_matching_with);
return Ok(Self::Path(WithSpan::new(path, path_span)));
}
let (name, span) = identifier.with_span().parse_next(i)?;
match name {
"_" => Ok(Self::Placeholder(WithSpan::new((), span))),
_ => verify_name(WithSpan::new(name, span)),
}
}
fn lit(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
enum Lit<'a> {
Str(StrLit<'a>),
Bool(&'a str),
Char(CharLit<'a>),
Num(&'a str, Num<'a>),
}
let p = alt((
str_lit.map(Lit::Str),
char_lit.map(Lit::Char),
bool_lit.map(Lit::Bool),
num_lit.with_taken().map(|(num, full)| Lit::Num(full, num)),
));
let (inner, span) = p.with_span().parse_next(i)?;
let span = Span::new(span);
match inner {
Lit::Str(v) => Ok(Target::StrLit(WithSpan::new(v, span))),
Lit::Bool(v) => Ok(Target::BoolLit(WithSpan::new(v, span))),
Lit::Char(v) => Ok(Target::CharLit(WithSpan::new(v, span))),
Lit::Num(v, num) => Ok(Target::NumLit(WithSpan::new(v, span), num)),
}
}
fn unnamed(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
alt((Self::rest, Self::parse)).parse_next(i)
}
fn named<O: From<(WithSpan<&'a str>, Self)>>(
i: &mut InputStream<'a, 'l>,
) -> ParseResult<'a, O> {
if let Some(rest) = opt(Self::rest_inner).parse_next(i)? {
let chr = peek(ws(opt(one_of([',', ':']).with_span()))).parse_next(i)?;
if let Some((chr, span)) = chr {
return cut_error!(
format!(
"unexpected `{}` character after `..`\n\
note that in a named struct, `..` must come last to ignore other members",
chr.escape_debug()
),
span,
);
}
if rest.inner.is_some() {
return cut_error!("`@ ..` cannot be used in struct", rest.span);
}
Ok((WithSpan::new("..", rest.span), Target::Rest(rest)).into())
} else {
let ((src, span), target) =
(identifier.with_span(), opt(preceded(ws(':'), Self::parse))).parse_next(i)?;
let src = WithSpan::new(src, span);
if *src == "_" {
return cut_error!(
"cannot use placeholder `_` as source in named struct",
src.span,
);
}
let target = match target {
Some(target) => target,
None => verify_name(src)?,
};
Ok((src, target).into())
}
}
fn rest(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
Self::rest_inner.map(Self::Rest).parse_next(i)
}
fn rest_inner(
i: &mut InputStream<'a, 'l>,
) -> ParseResult<'a, WithSpan<Option<WithSpan<&'a str>>>> {
let p = |i: &mut _| {
let id =
terminated(opt(terminated(identifier.with_span(), ws('@'))), "..").parse_next(i)?;
Ok(id.map(|(id, span)| WithSpan::new(id, span)))
};
let (id, span) = ws(p.with_span()).parse_next(i)?;
Ok(WithSpan::new(id, span))
}
}
fn verify_name<'a>(name: WithSpan<&'a str>) -> Result<Target<'a>, ErrMode<ErrorContext>> {
if !can_be_variable_name(*name) {
cut_error!(
format!("`{}` cannot be used as an identifier", name.escape_debug()),
name.span,
)
} else if is_rust_keyword(*name) {
cut_error!(
format!(
"cannot use `{}` as a name: it is a rust keyword",
name.escape_debug(),
),
name.span,
)
} else if name.starts_with("__askama") {
cut_error!(
format!(
"cannot use `{}` as a name: it is reserved for `askama`",
name.escape_debug()
),
name.span,
)
} else {
Ok(Target::Name(name))
}
}
fn collect_targets<'a: 'l, 'l, T>(
i: &mut InputStream<'a, 'l>,
delim: char,
one: impl ModalParser<InputStream<'a, 'l>, T, ErrorContext>,
) -> ParseResult<'a, (bool, Vec<T>)> {
let opt_comma = ws(opt(',')).map(|o| o.is_some());
let mut opt_end = ws(opt(one_of(delim))).map(|o| o.is_some());
let has_end = opt_end.parse_next(i)?;
if has_end {
return Ok((false, Vec::new()));
}
let (targets, span) = opt(separated(1.., one, ws(',')).map(|v: Vec<_>| v))
.with_span()
.parse_next(i)?;
let Some(targets) = targets else {
return cut_error!("expected comma separated list of members", span);
};
let (has_comma, has_end) = (opt_comma, opt_end).parse_next(i)?;
if !has_end {
let delim = delim.escape_debug();
return cut_error!(
match has_comma {
true => format!("expected member, or `{delim}` as terminator"),
false => format!("expected `,` for more members, or `{delim}` as terminator"),
},
*i
);
}
let singleton = !has_comma && targets.len() == 1;
Ok((singleton, targets))
}
fn only_one_rest_pattern<'a>(
targets: Vec<Target<'a>>,
allow_named_rest: bool,
type_kind: &str,
) -> Result<Vec<Target<'a>>, ParseErr<'a>> {
let mut found_rest = false;
for target in &targets {
if let Target::Rest(s) = target {
if !allow_named_rest && s.is_some() {
return cut_error!("`@ ..` is only allowed in slice patterns", s.span);
} else if found_rest {
return cut_error!(
format!("`..` can only be used once per {type_kind} pattern"),
s.span,
);
} else {
found_rest = true;
}
}
}
Ok(targets)
}