Skip to main content

askama_parser/
target.rs

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    /// The `Option` is the variable name (if any) in `var_name @ ..`.
27    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    /// Parses multiple targets with `or` separating them
62    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    /// Parses a single target without an `or`, unless it is wrapped in parentheses.
91    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        // match tuples
104        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        // match array
117        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        // match structs
126        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 the path only contains one item, we need to check the name.
150                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                // Otherwise we need to check every element but the first.
161                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        // neither literal nor struct nor path
177        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}