1use winnow::combinator::{alt, opt, peek, preceded, separated, terminated};
2use winnow::error::ErrMode;
3use winnow::stream::{Location, Stream};
4use winnow::token::one_of;
5use winnow::{ModalParser, Parser};
6
7use crate::{
8 CharLit, ErrorContext, InputStream, Num, ParseErr, ParseResult, PathComponent,
9 PathOrIdentifier, Span, StrLit, WithSpan, bool_lit, can_be_variable_name, char_lit, cut_error,
10 identifier, is_rust_keyword, keyword, num_lit, path_or_identifier, str_lit, ws,
11};
12
13#[derive(Clone, Debug, PartialEq)]
14pub enum Target<'a> {
15 Name(WithSpan<&'a str>),
16 Tuple(WithSpan<(Vec<PathComponent<'a>>, Vec<Target<'a>>)>),
17 Array(WithSpan<Vec<Target<'a>>>),
18 Struct(WithSpan<(Vec<PathComponent<'a>>, Vec<NamedTarget<'a>>)>),
19 NumLit(WithSpan<&'a str>, Num<'a>),
20 StrLit(WithSpan<StrLit<'a>>),
21 CharLit(WithSpan<CharLit<'a>>),
22 BoolLit(WithSpan<&'a str>),
23 Path(WithSpan<Vec<PathComponent<'a>>>),
24 OrChain(WithSpan<Vec<Target<'a>>>),
25 Placeholder(WithSpan<()>),
26 Rest(WithSpan<Option<WithSpan<&'a str>>>),
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct NamedTarget<'a> {
32 pub src: WithSpan<&'a str>,
33 pub dest: Target<'a>,
34}
35
36impl<'a> From<(WithSpan<&'a str>, Target<'a>)> for NamedTarget<'a> {
37 #[inline]
38 fn from((src, dest): (WithSpan<&'a str>, Target<'a>)) -> Self {
39 Self { src, dest }
40 }
41}
42
43impl<'a: 'l, 'l> Target<'a> {
44 pub fn span(&self) -> Span {
45 match self {
46 Target::Name(v) => v.span(),
47 Target::Tuple(v) => v.span(),
48 Target::Array(v) => v.span(),
49 Target::Struct(v) => v.span(),
50 Target::NumLit(v, _) => v.span(),
51 Target::StrLit(v) => v.span(),
52 Target::CharLit(v) => v.span(),
53 Target::BoolLit(v) => v.span(),
54 Target::Path(v) => v.span(),
55 Target::OrChain(v) => v.span(),
56 Target::Placeholder(v) => v.span(),
57 Target::Rest(v) => v.span(),
58 }
59 }
60
61 pub(super) fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
63 enum OneOrMany<'a> {
64 One(Target<'a>),
65 Many(Vec<Target<'a>>),
66 }
67
68 let mut or_more = opt(preceded(ws(keyword("or")), Self::parse_one));
69 let one_or_many = |i: &mut _| {
70 let target = Self::parse_one(i)?;
71 let Some(snd_target) = or_more.parse_next(i)? else {
72 return Ok(OneOrMany::One(target));
73 };
74
75 let mut targets = vec![target, snd_target];
76 while let Some(target) = or_more.parse_next(i)? {
77 targets.push(target);
78 }
79 Ok(OneOrMany::Many(targets))
80 };
81
82 let _level_guard = i.state.level.nest(i)?;
83 let (inner, span) = one_or_many.with_span().parse_next(i)?;
84 match inner {
85 OneOrMany::One(target) => Ok(target),
86 OneOrMany::Many(targets) => Ok(Self::OrChain(WithSpan::new(targets, span))),
87 }
88 }
89
90 fn parse_one(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
92 let mut opt_opening_paren = opt(ws('(').span()).map(|o| o.is_some());
93 let mut opt_opening_brace = opt(ws('{').span()).map(|o| o.is_some());
94 let mut opt_opening_bracket = opt(ws('[').span()).map(|o| o.is_some());
95
96 let lit = ws(opt(Self::lit)).parse_next(i)?;
97 if let Some(lit) = lit {
98 return Ok(lit);
99 }
100
101 let start = i.current_token_start();
102
103 let target_is_tuple = opt_opening_paren.parse_next(i)?;
105 if target_is_tuple {
106 let (is_singleton, mut targets) = collect_targets(i, ')', Self::unnamed)?;
107 if is_singleton && let Some(target) = targets.pop() {
108 return Ok(target);
109 }
110
111 let range = start..i.current_token_start();
112 let targets = only_one_rest_pattern(targets, false, "tuple")?;
113 return Ok(Self::Tuple(WithSpan::new((Vec::new(), targets), range)));
114 }
115
116 let target_is_array = opt_opening_bracket.parse_next(i)?;
118 if target_is_array {
119 let targets = collect_targets(i, ']', Self::unnamed)?.1;
120 let inner = only_one_rest_pattern(targets, true, "array")?;
121 let range = start..i.current_token_start();
122 return Ok(Self::Array(WithSpan::new(inner, range)));
123 }
124
125 let path = path_or_identifier.verify_map(|r| match r {
127 PathOrIdentifier::Path(v) => Some(v),
128 PathOrIdentifier::Identifier(_) => None,
129 });
130 let path = opt(path.with_span()).parse_next(i)?;
131 if let Some((path, path_span)) = path {
132 let i_before_matching_with = i.checkpoint();
133 let _ = opt(ws(keyword("with"))).parse_next(i)?;
134
135 let is_unnamed_struct = opt_opening_paren.parse_next(i)?;
136 if is_unnamed_struct {
137 let targets = collect_targets(i, ')', Self::unnamed)?.1;
138 let inner = only_one_rest_pattern(targets, false, "struct")?;
139 return Ok(Self::Tuple(WithSpan::new((path, inner), path_span)));
140 }
141
142 let is_named_struct = opt_opening_brace.parse_next(i)?;
143 if is_named_struct {
144 let targets = collect_targets(i, '}', Self::named)?.1;
145 return Ok(Self::Struct(WithSpan::new((path, targets), path_span)));
146 }
147
148 if let [arg] = path.as_slice() {
149 if !can_be_variable_name(*arg.name) {
151 return cut_error!(
152 format!(
153 "`{}` cannot be used as an identifier",
154 arg.name.escape_debug()
155 ),
156 arg.name.span
157 );
158 }
159 } else {
160 if let Some(arg) = path.iter().skip(1).find(|n| !can_be_variable_name(*n.name)) {
162 return cut_error!(
163 format!(
164 "`{}` cannot be used as an identifier",
165 arg.name.escape_debug()
166 ),
167 arg.name.span
168 );
169 }
170 }
171
172 i.reset(&i_before_matching_with);
173 return Ok(Self::Path(WithSpan::new(path, path_span)));
174 }
175
176 let (name, span) = identifier.with_span().parse_next(i)?;
178 match name {
179 "_" => Ok(Self::Placeholder(WithSpan::new((), span))),
180 _ => verify_name(WithSpan::new(name, span)),
181 }
182 }
183
184 fn lit(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
185 enum Lit<'a> {
186 Str(StrLit<'a>),
187 Bool(&'a str),
188 Char(CharLit<'a>),
189 Num(&'a str, Num<'a>),
190 }
191
192 let p = alt((
193 str_lit.map(Lit::Str),
194 char_lit.map(Lit::Char),
195 bool_lit.map(Lit::Bool),
196 num_lit.with_taken().map(|(num, full)| Lit::Num(full, num)),
197 ));
198 let (inner, span) = p.with_span().parse_next(i)?;
199 let span = Span::new(span);
200 match inner {
201 Lit::Str(v) => Ok(Target::StrLit(WithSpan::new(v, span))),
202 Lit::Bool(v) => Ok(Target::BoolLit(WithSpan::new(v, span))),
203 Lit::Char(v) => Ok(Target::CharLit(WithSpan::new(v, span))),
204 Lit::Num(v, num) => Ok(Target::NumLit(WithSpan::new(v, span), num)),
205 }
206 }
207
208 fn unnamed(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
209 alt((Self::rest, Self::parse)).parse_next(i)
210 }
211
212 fn named<O: From<(WithSpan<&'a str>, Self)>>(
213 i: &mut InputStream<'a, 'l>,
214 ) -> ParseResult<'a, O> {
215 if let Some(rest) = opt(Self::rest_inner).parse_next(i)? {
216 let chr = peek(ws(opt(one_of([',', ':']).with_span()))).parse_next(i)?;
217 if let Some((chr, span)) = chr {
218 return cut_error!(
219 format!(
220 "unexpected `{}` character after `..`\n\
221 note that in a named struct, `..` must come last to ignore other members",
222 chr.escape_debug()
223 ),
224 span,
225 );
226 }
227 if rest.inner.is_some() {
228 return cut_error!("`@ ..` cannot be used in struct", rest.span);
229 }
230 Ok((WithSpan::new("..", rest.span), Target::Rest(rest)).into())
231 } else {
232 let ((src, span), target) =
233 (identifier.with_span(), opt(preceded(ws(':'), Self::parse))).parse_next(i)?;
234
235 let src = WithSpan::new(src, span);
236 if *src == "_" {
237 return cut_error!(
238 "cannot use placeholder `_` as source in named struct",
239 src.span,
240 );
241 }
242
243 let target = match target {
244 Some(target) => target,
245 None => verify_name(src)?,
246 };
247 Ok((src, target).into())
248 }
249 }
250
251 fn rest(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
252 Self::rest_inner.map(Self::Rest).parse_next(i)
253 }
254
255 fn rest_inner(
256 i: &mut InputStream<'a, 'l>,
257 ) -> ParseResult<'a, WithSpan<Option<WithSpan<&'a str>>>> {
258 let p = |i: &mut _| {
259 let id =
260 terminated(opt(terminated(identifier.with_span(), ws('@'))), "..").parse_next(i)?;
261 Ok(id.map(|(id, span)| WithSpan::new(id, span)))
262 };
263 let (id, span) = ws(p.with_span()).parse_next(i)?;
264 Ok(WithSpan::new(id, span))
265 }
266}
267
268fn verify_name<'a>(name: WithSpan<&'a str>) -> Result<Target<'a>, ErrMode<ErrorContext>> {
269 if !can_be_variable_name(*name) {
270 cut_error!(
271 format!("`{}` cannot be used as an identifier", name.escape_debug()),
272 name.span,
273 )
274 } else if is_rust_keyword(*name) {
275 cut_error!(
276 format!(
277 "cannot use `{}` as a name: it is a rust keyword",
278 name.escape_debug(),
279 ),
280 name.span,
281 )
282 } else if name.starts_with("__askama") {
283 cut_error!(
284 format!(
285 "cannot use `{}` as a name: it is reserved for `askama`",
286 name.escape_debug()
287 ),
288 name.span,
289 )
290 } else {
291 Ok(Target::Name(name))
292 }
293}
294
295fn collect_targets<'a: 'l, 'l, T>(
296 i: &mut InputStream<'a, 'l>,
297 delim: char,
298 one: impl ModalParser<InputStream<'a, 'l>, T, ErrorContext>,
299) -> ParseResult<'a, (bool, Vec<T>)> {
300 let opt_comma = ws(opt(',')).map(|o| o.is_some());
301 let mut opt_end = ws(opt(one_of(delim))).map(|o| o.is_some());
302
303 let has_end = opt_end.parse_next(i)?;
304 if has_end {
305 return Ok((false, Vec::new()));
306 }
307
308 let (targets, span) = opt(separated(1.., one, ws(',')).map(|v: Vec<_>| v))
309 .with_span()
310 .parse_next(i)?;
311 let Some(targets) = targets else {
312 return cut_error!("expected comma separated list of members", span);
313 };
314
315 let (has_comma, has_end) = (opt_comma, opt_end).parse_next(i)?;
316 if !has_end {
317 let delim = delim.escape_debug();
318 return cut_error!(
319 match has_comma {
320 true => format!("expected member, or `{delim}` as terminator"),
321 false => format!("expected `,` for more members, or `{delim}` as terminator"),
322 },
323 *i
324 );
325 }
326
327 let singleton = !has_comma && targets.len() == 1;
328 Ok((singleton, targets))
329}
330
331fn only_one_rest_pattern<'a>(
332 targets: Vec<Target<'a>>,
333 allow_named_rest: bool,
334 type_kind: &str,
335) -> Result<Vec<Target<'a>>, ParseErr<'a>> {
336 let mut found_rest = false;
337 for target in &targets {
338 if let Target::Rest(s) = target {
339 if !allow_named_rest && s.is_some() {
340 return cut_error!("`@ ..` is only allowed in slice patterns", s.span);
341 } else if found_rest {
342 return cut_error!(
343 format!("`..` can only be used once per {type_kind} pattern"),
344 s.span,
345 );
346 } else {
347 found_rest = true;
348 }
349 }
350 }
351 Ok(targets)
352}