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>, Vec>)>), Array(WithSpan>>), Struct(WithSpan<(Vec>, Vec>)>), NumLit(WithSpan<&'a str>, Num<'a>), StrLit(WithSpan>), CharLit(WithSpan>), BoolLit(WithSpan<&'a str>), Path(WithSpan>>), OrChain(WithSpan>>), Placeholder(WithSpan<()>), /// The `Option` is the variable name (if any) in `var_name @ ..`. Rest(WithSpan>>), } #[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(), } } /// Parses multiple targets with `or` separating them pub(super) fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> { enum OneOrMany<'a> { One(Target<'a>), Many(Vec>), } 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))), } } /// Parses a single target without an `or`, unless it is wrapped in parentheses. 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(); // match tuples 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))); } // match array 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))); } // match structs 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 the path only contains one item, we need to check the name. 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 { // Otherwise we need to check every element but the first. 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))); } // neither literal nor struct nor path 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, 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>>> { 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, ErrMode> { 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, T, ErrorContext>, ) -> ParseResult<'a, (bool, Vec)> { 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>, allow_named_rest: bool, type_kind: &str, ) -> Result>, 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) }