Skip to main content

askama_parser/
node.rs

1use std::str::{self, FromStr};
2
3use winnow::combinator::{
4    alt, cut_err, delimited, eof, fail, not, opt, peek, preceded, repeat, separated,
5    separated_pair, terminated,
6};
7use winnow::error::ErrMode;
8use winnow::stream::{Location, Stream};
9use winnow::token::{any, literal, rest, take, take_until};
10use winnow::{ModalParser, Parser};
11
12use crate::expr::BinOp;
13use crate::{
14    ErrorContext, Expr, Filter, HashSet, InputStream, ParseErr, ParseResult, Span, Target,
15    TyGenerics, WithSpan, block_end, block_start, cut_error, deny_any_rust_token, expr_end,
16    expr_start, filter, identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws,
17};
18
19#[derive(Debug, PartialEq)]
20pub enum Node<'a> {
21    Lit(WithSpan<Lit<'a>>),
22    Comment(WithSpan<Comment<'a>>),
23    Expr(Ws, WithSpan<Box<Expr<'a>>>),
24    Call(WithSpan<Call<'a>>),
25    Let(WithSpan<Let<'a>>),
26    /// Mutable operations like `+=`.
27    Compound(WithSpan<Compound<'a>>),
28    Declare(WithSpan<Declare<'a>>),
29    If(WithSpan<If<'a>>),
30    Match(WithSpan<Match<'a>>),
31    Loop(WithSpan<Loop<'a>>),
32    Extends(WithSpan<Extends<'a>>),
33    BlockDef(WithSpan<BlockDef<'a>>),
34    Include(WithSpan<Include<'a>>),
35    Import(WithSpan<Import<'a>>),
36    Macro(WithSpan<Macro<'a>>),
37    Raw(WithSpan<Raw<'a>>),
38    Break(WithSpan<Ws>),
39    Continue(WithSpan<Ws>),
40    FilterBlock(WithSpan<FilterBlock<'a>>),
41}
42
43impl<'a: 'l, 'l> Node<'a> {
44    pub(super) fn parse_template(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Vec<Box<Self>>> {
45        let mut nodes = vec![];
46        let mut allow_extends = true;
47        while let Some(node) = parse_with_unexpected_fallback(
48            opt(move |i: &mut _| Self::one(i, allow_extends)),
49            unexpected_tag,
50        )
51        .parse_next(i)?
52        {
53            if allow_extends {
54                match &*node {
55                    // Since comments don't impact generated code, we allow them before `extends`.
56                    Node::Comment(_) => {}
57                    // If it only contains whitespace characters, it's fine too.
58                    Node::Lit(lit) if lit.val.is_empty() => {}
59                    // Everything else must not come before an `extends` block.
60                    _ => allow_extends = false,
61                }
62            }
63            nodes.push(node);
64        }
65
66        if !i.is_empty() {
67            opt(unexpected_tag).parse_next(i)?;
68            return cut_error!(
69                "cannot parse entire template\n\
70                you should never encounter this error\n\
71                please report this error to <https://github.com/askama-rs/askama/issues>",
72                *i,
73            );
74        }
75        Ok(nodes)
76    }
77
78    fn many(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Vec<Box<Self>>> {
79        repeat(0.., |i: &mut _| Self::one(i, false)).parse_next(i)
80    }
81
82    fn one(i: &mut InputStream<'a, 'l>, allow_extends: bool) -> ParseResult<'a, Box<Self>> {
83        let node = alt((Lit::parse, Comment::parse, Self::expr, Self::parse)).parse_next(i)?;
84        if !allow_extends && let Node::Extends(node) = &*node {
85            return cut_error!("`extends` block must come first in a template", node.span());
86        }
87        Ok(node)
88    }
89
90    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Self>> {
91        let start = i.checkpoint();
92        let (span, tag) = (
93            block_start.span(),
94            peek(preceded((opt(Whitespace::parse), skip_ws0), identifier)),
95        )
96            .parse_next(i)?;
97
98        let func = match tag {
99            "block" => BlockDef::parse,
100            "break" => Self::r#break,
101            "call" => Call::parse,
102            "continue" => Self::r#continue,
103            "decl" | "declare" => Declare::parse,
104            "extends" => Extends::parse,
105            "filter" => FilterBlock::parse,
106            "for" => Loop::parse,
107            "if" => If::parse,
108            "import" => Import::parse,
109            "include" => Include::parse,
110            "let" | "set" => Let::parse,
111            "macro" => Macro::parse,
112            "match" => Match::parse,
113            "mut" => Compound::parse,
114            "raw" => Raw::parse,
115            _ => {
116                i.reset(&start);
117                return fail.parse_next(i);
118            }
119        };
120
121        let _level_guard = i.state.level.nest(i)?;
122        let node = func(i)?;
123        let closed =
124            cut_node(None, alt((ws(eof).value(false), block_end.value(true)))).parse_next(i)?;
125        match closed {
126            true => Ok(node),
127            false => {
128                Err(
129                    ErrorContext::unclosed("block", i.state.syntax.block_end, Span::new(span))
130                        .cut(),
131                )
132            }
133        }
134    }
135
136    fn r#break(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
137        let mut p = (
138            opt(Whitespace::parse),
139            ws(keyword("break").span()),
140            opt(Whitespace::parse),
141        );
142
143        let (pws, span, nws) = p.parse_next(i)?;
144        if !i.state.is_in_loop() {
145            return cut_error!("you can only `break` inside a `for` loop", span);
146        }
147        Ok(Box::new(Self::Break(WithSpan::new(Ws(pws, nws), span))))
148    }
149
150    fn r#continue(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
151        let mut p = (
152            opt(Whitespace::parse),
153            ws(keyword("continue").span()),
154            opt(Whitespace::parse),
155        );
156
157        let (pws, span, nws) = p.parse_next(i)?;
158        if !i.state.is_in_loop() {
159            return cut_error!("you can only `continue` inside a `for` loop", span);
160        }
161        Ok(Box::new(Self::Continue(WithSpan::new(Ws(pws, nws), span))))
162    }
163
164    fn expr(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Self>> {
165        let mut p = (
166            expr_start.span(),
167            cut_node(
168                None,
169                (
170                    opt(Whitespace::parse),
171                    ws(|i: &mut _| Expr::parse(i, false)),
172                ),
173            ),
174            cut_node(
175                None,
176                (
177                    opt(Whitespace::parse),
178                    alt((
179                        expr_end.value(true),
180                        ws(eof).value(false),
181                        deny_any_rust_token.value(false),
182                    )),
183                ),
184            ),
185        );
186
187        let (start, (pws, expr), (nws, closed)) = p.parse_next(i)?;
188        if closed {
189            Ok(Box::new(Self::Expr(Ws(pws, nws), expr)))
190        } else {
191            Err(ErrorContext::unclosed("expression", i.state.syntax.expr_end, start).cut())
192        }
193    }
194
195    #[must_use]
196    pub fn span(&self) -> Span {
197        match self {
198            Self::Lit(span) => span.span,
199            Self::Comment(span) => span.span,
200            Self::Expr(_, span) => span.span,
201            Self::Call(span) => span.span,
202            Self::Let(span) => span.span,
203            Self::Compound(span) => span.span,
204            Self::Declare(span) => span.span,
205            Self::If(span) => span.span,
206            Self::Match(span) => span.span,
207            Self::Loop(span) => span.span,
208            Self::Extends(span) => span.span,
209            Self::BlockDef(span) => span.span,
210            Self::Include(span) => span.span,
211            Self::Import(span) => span.span,
212            Self::Macro(span) => span.span,
213            Self::Raw(span) => span.span,
214            Self::Break(span) => span.span,
215            Self::Continue(span) => span.span,
216            Self::FilterBlock(span) => span.span,
217        }
218    }
219}
220
221#[inline]
222fn parse_with_unexpected_fallback<'a: 'l, 'l, O>(
223    mut parser: impl ModalParser<InputStream<'a, 'l>, O, ErrorContext>,
224    mut unexpected_parser: impl FnMut(&mut InputStream<'a, 'l>) -> ParseResult<'a, ()>,
225) -> impl ModalParser<InputStream<'a, 'l>, O, ErrorContext> {
226    #[cold]
227    #[inline(never)]
228    fn try_assign_fallback_error<'a: 'l, 'l>(
229        i: &mut InputStream<'a, 'l>,
230        unexpected_parser: &mut dyn FnMut(&mut InputStream<'a, 'l>) -> ParseResult<'a, ()>,
231        err: &mut ErrMode<ErrorContext>,
232    ) {
233        let (ErrMode::Backtrack(err_ctx) | ErrMode::Cut(err_ctx)) = &err else {
234            return;
235        };
236        if err_ctx.message.is_some() {
237            return;
238        }
239
240        let checkpoint = i.checkpoint();
241        i.input.reset_to_start();
242        if take::<_, _, ()>(err_ctx.span.start).parse_next(i).is_ok()
243            && let Err(better_err) = opt(unexpected_parser).parse_next(i)
244            && let ErrMode::Backtrack(better_ctx) | ErrMode::Cut(better_ctx) = &better_err
245            && better_ctx.message.is_some()
246        {
247            *err = better_err;
248        }
249        i.reset(&checkpoint);
250    }
251
252    move |i: &mut InputStream<'a, 'l>| {
253        let mut result = parser.parse_next(i);
254        if let Err(err) = &mut result {
255            try_assign_fallback_error(i, &mut unexpected_parser, err);
256        }
257        result
258    }
259}
260
261#[inline]
262fn cut_node<'a: 'l, 'l, O>(
263    kind: Option<&'static str>,
264    inner: impl ModalParser<InputStream<'a, 'l>, O, ErrorContext>,
265) -> impl ModalParser<InputStream<'a, 'l>, O, ErrorContext> {
266    parse_with_unexpected_fallback(cut_err(inner), move |i: &mut _| unexpected_raw_tag(kind, i))
267}
268
269fn unexpected_tag<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, ()> {
270    (block_start, opt(Whitespace::parse), |i: &mut _| {
271        unexpected_raw_tag(None, i)
272    })
273        .void()
274        .parse_next(i)
275}
276
277fn unexpected_raw_tag<'a: 'l, 'l>(
278    kind: Option<&'static str>,
279    i: &mut InputStream<'a, 'l>,
280) -> ParseResult<'a, ()> {
281    let Ok((tag, span)) = peek(ws(identifier.with_span())).parse_next(i) else {
282        let (c, span) = peek(ws(any.with_span())).parse_next(i)?;
283        return cut_error!(format!("unexpected character `{c}`"), span);
284    };
285    cut_error!(
286        match tag {
287            "end" | "elif" | "else" | "when" => match kind {
288                Some(kind) => {
289                    format!("node `{tag}` was not expected in the current context: `{kind}` block")
290                }
291                None => format!("node `{tag}` was not expected in the current context"),
292            },
293            tag if tag.starts_with("end") => format!("unexpected closing tag `{tag}`"),
294            tag => format!("unknown node `{tag}`"),
295        },
296        span,
297    )
298}
299
300#[derive(Debug, PartialEq)]
301pub struct When<'a> {
302    pub ws: Ws,
303    pub target: Vec<Target<'a>>,
304    pub nodes: Vec<Box<Node<'a>>>,
305}
306
307impl<'a: 'l, 'l> When<'a> {
308    fn r#else(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
309        let mut p = (
310            block_start,
311            opt(Whitespace::parse),
312            ws(keyword("else").span()),
313            cut_node(
314                Some("match-else"),
315                (
316                    opt(Whitespace::parse),
317                    block_end,
318                    cut_node(Some("match-else"), Node::many),
319                ),
320            ),
321        );
322
323        let (_, pws, span, (nws, _, nodes)) = p.parse_next(i)?;
324        let span = Span::new(span);
325        let inner = Self {
326            ws: Ws(pws, nws),
327            target: vec![Target::Placeholder(WithSpan::new((), span))],
328            nodes,
329        };
330        Ok(WithSpan::new(inner, span))
331    }
332
333    #[allow(clippy::self_named_constructors)]
334    fn when(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
335        let mut p = (
336            block_start,
337            opt(Whitespace::parse),
338            ws(keyword("when").span()),
339            cut_node(
340                Some("match-when"),
341                (
342                    separated(1.., ws(Target::parse), '|'),
343                    opt(Whitespace::parse),
344                    block_end,
345                    cut_node(Some("match-when"), Node::many),
346                    opt(Self::endwhen),
347                ),
348            ),
349        );
350        let (_, pws, span, (target, nws, _, mut nodes, endwhen)) = p.parse_next(i)?;
351        if let Some(endwhen) = endwhen {
352            nodes.push(endwhen);
353        }
354        Ok(WithSpan::new(
355            Self {
356                ws: Ws(pws, nws),
357                target,
358                nodes,
359            },
360            span,
361        ))
362    }
363
364    fn endwhen(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
365        let mut p = ws(terminated(
366            (
367                block_start,
368                opt(Whitespace::parse),
369                ws(keyword("endwhen").span()),
370            ),
371            cut_node(
372                Some("match-endwhen"),
373                (
374                    opt(Whitespace::parse),
375                    block_end,
376                    repeat(0.., ws(Comment::parse).void()).map(|()| ()),
377                ),
378            ),
379        ));
380        let (_, pws, span) = p.parse_next(i)?;
381        Ok(Box::new(Node::Comment(WithSpan::new(
382            Comment {
383                ws: Ws(pws, Some(Whitespace::Suppress)),
384                content: "",
385            },
386            span,
387        ))))
388    }
389}
390
391#[derive(Debug, PartialEq)]
392pub struct Cond<'a> {
393    pub ws: Ws,
394    pub cond: Option<WithSpan<CondTest<'a>>>,
395    pub nodes: Vec<Box<Node<'a>>>,
396}
397
398impl<'a: 'l, 'l> Cond<'a> {
399    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
400        let alt_else = (ws(keyword("else").span()), opt(CondTest::parse));
401        let alt_elif = |i: &mut _| {
402            let mut p = (
403                ws(keyword("elif").span()),
404                cut_node(Some("if-elif"), CondTest::parse_cond),
405            );
406            let (span, cond) = p.parse_next(i)?;
407            Ok((span.clone(), Some(WithSpan::new(cond, span))))
408        };
409
410        let (_, pws, (span, cond), nws, _, nodes) = (
411            block_start,
412            opt(Whitespace::parse),
413            alt((alt_else, alt_elif)),
414            opt(Whitespace::parse),
415            cut_node(Some("if"), block_end),
416            cut_node(Some("if"), Node::many),
417        )
418            .parse_next(i)?;
419
420        Ok(WithSpan::new(
421            Self {
422                ws: Ws(pws, nws),
423                cond,
424                nodes,
425            },
426            span,
427        ))
428    }
429}
430
431#[derive(Debug, PartialEq, Clone)]
432pub struct CondTest<'a> {
433    pub target: Option<Target<'a>>,
434    pub expr: WithSpan<Box<Expr<'a>>>,
435    pub contains_bool_lit_or_is_defined: bool,
436}
437
438impl<'a: 'l, 'l> CondTest<'a> {
439    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
440        let mut p = (
441            ws(keyword("if").span()),
442            cut_node(Some("if"), Self::parse_cond),
443        );
444        let (span, cond) = p.parse_next(i)?;
445        Ok(WithSpan::new(cond, span))
446    }
447
448    fn parse_cond(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
449        let (target, expr) = (
450            opt(delimited(
451                ws(alt((keyword("let"), keyword("set")))),
452                ws(Target::parse),
453                ws('='),
454            )),
455            ws(|i: &mut InputStream<'a, 'l>| {
456                let start_checkpoint = i.checkpoint();
457                let start_offset = i.current_token_start();
458
459                let mut expr = Expr::parse(i, false)?;
460                if let Expr::BinOp(v) = &mut *expr.inner
461                    && matches!(*v.rhs.inner, Expr::Var("set" | "let"))
462                {
463                    let _level_guard = i.state.level.nest(i)?;
464
465                    i.reset(&start_checkpoint);
466                    i.next_slice(v.rhs.span.start - start_offset);
467
468                    let (new_right, span) = Self::parse_cond.with_span().parse_next(i)?;
469                    *v.rhs.inner = Expr::LetCond(WithSpan::new(new_right, span));
470                }
471                Ok(expr)
472            }),
473        )
474            .parse_next(i)?;
475        let contains_bool_lit_or_is_defined = expr.contains_bool_lit_or_is_defined();
476        Ok(Self {
477            target,
478            expr,
479            contains_bool_lit_or_is_defined,
480        })
481    }
482}
483
484#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
485#[cfg_attr(feature = "config", derive(serde_derive::Deserialize))]
486#[cfg_attr(feature = "config", serde(field_identifier, rename_all = "lowercase"))]
487pub enum Whitespace {
488    #[default]
489    Preserve,
490    Suppress,
491    Minimize,
492}
493
494impl Whitespace {
495    fn parse<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Self> {
496        any.verify_map(Self::parse_char).parse_next(i)
497    }
498
499    fn parse_char(c: char) -> Option<Self> {
500        if c.is_ascii() {
501            Self::parse_byte(c as u8)
502        } else {
503            None
504        }
505    }
506
507    fn parse_byte(b: u8) -> Option<Whitespace> {
508        match b {
509            b'+' => Some(Self::Preserve),
510            b'-' => Some(Self::Suppress),
511            b'~' => Some(Self::Minimize),
512            _ => None,
513        }
514    }
515}
516
517impl FromStr for Whitespace {
518    type Err = String;
519
520    fn from_str(s: &str) -> Result<Self, Self::Err> {
521        match s {
522            "+" | "preserve" => Ok(Whitespace::Preserve),
523            "-" | "suppress" => Ok(Whitespace::Suppress),
524            "~" | "minimize" => Ok(Whitespace::Minimize),
525            s => Err(format!("invalid value for `whitespace`: {s:?}")),
526        }
527    }
528}
529
530fn check_block_start<'a: 'l, 'l>(
531    i: &mut InputStream<'a, 'l>,
532    start: Span,
533    node: &str,
534    expected: &str,
535) -> ParseResult<'a, ()> {
536    if i.is_empty() {
537        return cut_error!(
538            format!("expected `{expected}` to terminate `{node}` node, found nothing"),
539            start,
540        );
541    }
542    i.state.syntax.block_start.void().parse_next(i)
543}
544
545#[derive(Debug, PartialEq)]
546pub struct Loop<'a> {
547    pub ws1: Ws,
548    pub var: Target<'a>,
549    pub iter: WithSpan<Box<Expr<'a>>>,
550    pub cond: Option<WithSpan<Box<Expr<'a>>>>,
551    pub body: Vec<Box<Node<'a>>>,
552    pub ws2: Ws,
553    pub else_nodes: Vec<Box<Node<'a>>>,
554    pub ws3: Ws,
555}
556
557impl<'a: 'l, 'l> Loop<'a> {
558    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
559        fn content<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Vec<Box<Node<'a>>>> {
560            i.state.enter_loop();
561            let result = Node::many(i);
562            i.state.leave_loop();
563            result
564        }
565
566        let (pws1, span) = (opt(Whitespace::parse), ws(keyword("for").span())).parse_next(i)?;
567        let span = Span::new(span);
568
569        let if_cond = preceded(
570            ws(keyword("if")),
571            cut_node(Some("for-if"), ws(|i: &mut _| Expr::parse(i, true))),
572        );
573
574        let else_block = |i: &mut InputStream<'a, 'l>| {
575            let mut p = preceded(
576                ws(keyword("else")),
577                cut_node(
578                    Some("for-else"),
579                    (
580                        opt(Whitespace::parse),
581                        delimited(block_end, Node::many, block_start),
582                        opt(Whitespace::parse),
583                    ),
584                ),
585            );
586            let (pws, nodes, nws) = p.parse_next(i)?;
587            Ok((pws, nodes, nws))
588        };
589
590        let body_and_end = |i: &mut _| {
591            let (body, (_, pws, else_block, _, nws)) = cut_node(
592                Some("for"),
593                (
594                    content,
595                    cut_node(
596                        Some("for"),
597                        (
598                            |i: &mut _| check_block_start(i, span, "for", "endfor"),
599                            opt(Whitespace::parse),
600                            opt(else_block),
601                            end_node("for", "endfor"),
602                            opt(Whitespace::parse),
603                        ),
604                    ),
605                ),
606            )
607            .parse_next(i)?;
608            Ok((body, pws, else_block, nws))
609        };
610
611        let mut p = cut_node(
612            Some("for"),
613            (
614                ws(Target::parse),
615                ws(keyword("in")),
616                cut_node(
617                    Some("for"),
618                    (
619                        ws(|i: &mut _| Expr::parse(i, true)),
620                        opt(if_cond),
621                        opt(Whitespace::parse),
622                        block_end,
623                        body_and_end,
624                    ),
625                ),
626            ),
627        );
628        let (var, _, (iter, cond, nws1, _, (body, pws2, else_block, nws2))) = p.parse_next(i)?;
629        let (nws3, else_nodes, pws3) = else_block.unwrap_or_default();
630        Ok(Box::new(Node::Loop(WithSpan::new(
631            Self {
632                ws1: Ws(pws1, nws1),
633                var,
634                iter,
635                cond,
636                body,
637                ws2: Ws(pws2, nws3),
638                else_nodes,
639                ws3: Ws(pws3, nws2),
640            },
641            span,
642        ))))
643    }
644}
645
646#[derive(Debug, PartialEq)]
647pub struct Macro<'a> {
648    pub ws1: Ws,
649    pub name: WithSpan<&'a str>,
650    pub args: Vec<MacroArg<'a>>,
651    pub nodes: Vec<Box<Node<'a>>>,
652    pub ws2: Ws,
653}
654
655#[derive(Debug, PartialEq)]
656pub struct MacroArg<'a> {
657    pub name: WithSpan<&'a str>,
658    pub ty: Option<WithSpan<TyGenerics<'a>>>,
659    pub default: Option<WithSpan<Box<Expr<'a>>>>,
660}
661
662fn check_duplicated_name<'a>(
663    names: &mut HashSet<&'a str>,
664    arg_name: &WithSpan<&'a str>,
665) -> Result<(), ParseErr<'a>> {
666    if !names.insert(arg_name.inner) {
667        return cut_error!(
668            format!("duplicated argument `{}`", arg_name.escape_debug()),
669            arg_name.span
670        );
671    }
672    Ok(())
673}
674
675impl<'a: 'l, 'l> Macro<'a> {
676    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
677        let (pws1, keyword_span, (name, name_span)) = (
678            opt(Whitespace::parse),
679            ws(keyword("macro").span()),
680            cut_node(Some("macro"), ws(identifier.with_span())),
681        )
682            .parse_next(i)?;
683        if is_rust_keyword(name) {
684            return cut_error!(
685                format!("`{}` is not a valid name for a macro", name.escape_debug()),
686                name_span,
687            );
688        }
689        let keyword_span = Span::new(keyword_span);
690
691        let macro_arg = |i: &mut _| {
692            let mut p = (
693                ws(identifier.with_span()),
694                opt(preceded(':', ws(|i: &mut _| TyGenerics::parse(i)))),
695                opt(preceded('=', ws(|i: &mut _| Expr::parse(i, false)))),
696            );
697            let ((name, name_span), ty, default) = p.parse_next(i)?;
698            Ok(MacroArg {
699                name: WithSpan::new(name, name_span),
700                ty,
701                default,
702            })
703        };
704        let mut args = opt((
705            '('.span(),
706            opt(terminated(separated(1.., macro_arg, ','), opt(','))),
707            ws(opt(')')),
708        ));
709        let parameters = |i: &mut _| match args.parse_next(i)? {
710            Some((_, args, Some(_))) => Ok(args),
711            Some((span, _, None)) => {
712                cut_error!("expected `)` to close macro argument list", span)
713            }
714            None => Ok(None::<Vec<MacroArg<'_>>>),
715        };
716
717        let (params, nws1, _) = cut_node(
718            Some("macro"),
719            (parameters, opt(Whitespace::parse), block_end),
720        )
721        .parse_next(i)?;
722
723        if let Some(ref params) = params {
724            let mut names = HashSet::default();
725            let mut iter = params.iter();
726            for arg in iter.by_ref() {
727                check_duplicated_name(&mut names, &arg.name)?;
728                if arg.default.is_none() {
729                    continue;
730                }
731
732                for new_arg in iter.by_ref() {
733                    check_duplicated_name(&mut names, &new_arg.name)?;
734                    if new_arg.default.is_some() {
735                        continue;
736                    }
737
738                    return cut_error!(
739                        format!(
740                            "all arguments following `{}` should have a default value, \
741                            `{}` doesn't have a default value",
742                            arg.name.escape_debug(),
743                            new_arg.name.escape_debug(),
744                        ),
745                        new_arg.name.span,
746                    );
747                }
748                break;
749            }
750        }
751
752        let mut end = cut_node(
753            Some("macro"),
754            (
755                Node::many,
756                cut_node(
757                    Some("macro"),
758                    (
759                        |i: &mut _| check_block_start(i, keyword_span, "macro", "endmacro"),
760                        opt(Whitespace::parse),
761                        end_node("macro", "endmacro"),
762                        check_end_name(name, "macro"),
763                    ),
764                ),
765            ),
766        );
767        let (contents, (_, pws2, _, nws2)) = end.parse_next(i)?;
768
769        Ok(Box::new(Node::Macro(WithSpan::new(
770            Self {
771                ws1: Ws(pws1, nws1),
772                name: WithSpan::new(name, name_span),
773                args: params.unwrap_or_default(),
774                nodes: contents,
775                ws2: Ws(pws2, nws2),
776            },
777            keyword_span,
778        ))))
779    }
780}
781
782#[derive(Debug, PartialEq)]
783pub struct FilterBlock<'a> {
784    pub ws1: Ws,
785    pub filters: Filter<'a>,
786    pub nodes: Vec<Box<Node<'a>>>,
787    pub ws2: Ws,
788}
789
790impl<'a: 'l, 'l> FilterBlock<'a> {
791    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
792        let (pws1, span) = (opt(Whitespace::parse), ws(keyword("filter").span())).parse_next(i)?;
793        let span = Span::new(span);
794
795        fn filters<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Filter<'a>> {
796            let mut filter = opt(ws(filter.with_span()));
797
798            let (mut res, span) = Filter::parse.with_span().parse_next(i)?;
799            res.arguments
800                .insert(0, WithSpan::new(Box::new(Expr::FilterSource), span));
801
802            let mut level_guard = i.state.level.guard();
803            let mut i_before = *i;
804            while let Some((mut filter, span)) = filter.parse_next(i)? {
805                level_guard.nest(&i_before)?;
806                filter
807                    .arguments
808                    .insert(0, WithSpan::new(Box::new(Expr::Filter(res)), span));
809                res = filter;
810                i_before = *i;
811            }
812            Ok(res)
813        }
814
815        let mut p = (
816            cut_node(
817                Some("filter"),
818                (ws(filters), opt(Whitespace::parse), block_end),
819            ),
820            cut_node(Some("filter"), Node::many),
821            cut_node(
822                Some("filter"),
823                (
824                    |i: &mut _| check_block_start(i, span, "filter", "endfilter"),
825                    opt(Whitespace::parse),
826                    end_node("filter", "endfilter"),
827                    opt(Whitespace::parse),
828                ),
829            ),
830        );
831        let ((filters, nws1, _), nodes, (_, pws2, _, nws2)) = p.parse_next(i)?;
832
833        Ok(Box::new(Node::FilterBlock(WithSpan::new(
834            Self {
835                ws1: Ws(pws1, nws1),
836                filters,
837                nodes,
838                ws2: Ws(pws2, nws2),
839            },
840            span,
841        ))))
842    }
843}
844
845#[derive(Debug, PartialEq)]
846pub struct Import<'a> {
847    pub ws: Ws,
848    pub path: &'a str,
849    pub scope: &'a str,
850}
851
852impl<'a: 'l, 'l> Import<'a> {
853    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
854        let mut p = (
855            opt(Whitespace::parse),
856            ws(keyword("import").span()),
857            cut_node(
858                Some("import"),
859                (
860                    ws(str_lit_without_prefix),
861                    ws(keyword("as")),
862                    cut_node(Some("import"), (ws(identifier), opt(Whitespace::parse))),
863                ),
864            ),
865        );
866        let (pws, span, (path, _, (scope, nws))) = p.parse_next(i)?;
867        Ok(Box::new(Node::Import(WithSpan::new(
868            Self {
869                ws: Ws(pws, nws),
870                path,
871                scope,
872            },
873            span,
874        ))))
875    }
876}
877
878#[derive(Debug, PartialEq)]
879pub struct Call<'a> {
880    pub ws1: Ws,
881    pub caller_args: Vec<&'a str>,
882    pub scope: Option<WithSpan<&'a str>>,
883    pub name: WithSpan<&'a str>,
884    pub args: Option<Vec<WithSpan<Box<Expr<'a>>>>>,
885    pub nodes: Vec<Box<Node<'a>>>,
886    pub ws2: Ws,
887}
888
889impl<'a: 'l, 'l> Call<'a> {
890    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
891        let (pws, span) = (opt(Whitespace::parse), ws(keyword("call").span())).parse_next(i)?;
892        let keyword_span = Span::new(span);
893
894        let parameters = |i: &mut _| -> ParseResult<'_, Option<Vec<&str>>> {
895            let mut p = opt((
896                '('.span(),
897                opt(terminated(separated(0.., ws(identifier), ','), opt(','))),
898                ws(opt(')')),
899            ));
900            match p.parse_next(i)? {
901                Some((_, args, Some(_))) => Ok(args),
902                Some((span, _, None)) => {
903                    cut_error!("expected `)` to close call argument list", span)
904                }
905                None => Ok(None),
906            }
907        };
908        let mut p = (
909            parameters,
910            cut_node(
911                Some("call"),
912                (
913                    opt(|i: &mut _| {
914                        let (scope, span) =
915                            terminated(ws(identifier.with_span()), ws("::")).parse_next(i)?;
916                        Ok(WithSpan::new(scope, span))
917                    }),
918                    ws(identifier.with_span()),
919                    opt(ws(Expr::arguments)),
920                    opt(Whitespace::parse),
921                    block_end,
922                ),
923            ),
924        );
925
926        let (call_args, (scope, (name, name_span), args, nws, _)) = p.parse_next(i)?;
927        let mut end = cut_node(
928            Some("call"),
929            (
930                Node::many,
931                cut_node(
932                    Some("call"),
933                    (
934                        |i: &mut _| check_block_start(i, keyword_span, "call", "endcall"),
935                        opt(Whitespace::parse),
936                        end_node("call", "endcall"),
937                        opt(Whitespace::parse),
938                    ),
939                ),
940            ),
941        );
942        let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?;
943
944        Ok(Box::new(Node::Call(WithSpan::new(
945            Self {
946                ws1: Ws(pws, nws),
947                caller_args: call_args.unwrap_or_default(),
948                scope,
949                name: WithSpan::new(name, name_span),
950                args: args.map(|args| args.deconstruct().0),
951                nodes,
952                ws2: Ws(pws2, nws2),
953            },
954            keyword_span,
955        ))))
956    }
957}
958
959#[derive(Debug, PartialEq)]
960pub struct Match<'a> {
961    pub ws1: Ws,
962    pub expr: WithSpan<Box<Expr<'a>>>,
963    pub arms: Vec<WithSpan<When<'a>>>,
964    pub ws2: Ws,
965}
966
967impl<'a: 'l, 'l> Match<'a> {
968    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
969        let (pws1, span) = (opt(Whitespace::parse), ws(keyword("match").span())).parse_next(i)?;
970        let span = Span::new(span);
971        let mut p = cut_node(
972            Some("match"),
973            (
974                ws(|i: &mut _| Expr::parse(i, false)),
975                opt(Whitespace::parse),
976                block_end,
977                cut_node(
978                    Some("match"),
979                    (
980                        ws(repeat(0.., ws(Comment::parse))).map(|()| ()),
981                        repeat(0.., When::when).map(|v: Vec<_>| v),
982                    ),
983                ),
984                cut_node(Some("match"), opt(When::r#else)),
985                cut_node(
986                    Some("match"),
987                    (
988                        ws(|i: &mut _| check_block_start(i, span, "match", "endmatch")),
989                        opt(Whitespace::parse),
990                        end_node("match", "endmatch"),
991                        opt(Whitespace::parse),
992                    ),
993                ),
994            ),
995        );
996        let (expr, nws1, _, (_, mut arms), else_arm, (_, pws2, _, nws2)) = p.parse_next(i)?;
997
998        if let Some(arm) = else_arm {
999            arms.push(arm);
1000        }
1001        if arms.is_empty() {
1002            return cut_error!(
1003                "`match` nodes must contain at least one `when` node and/or an `else` case",
1004                span,
1005            );
1006        }
1007
1008        Ok(Box::new(Node::Match(WithSpan::new(
1009            Self {
1010                ws1: Ws(pws1, nws1),
1011                expr,
1012                arms,
1013                ws2: Ws(pws2, nws2),
1014            },
1015            span,
1016        ))))
1017    }
1018}
1019
1020#[derive(Debug, PartialEq)]
1021pub struct BlockDef<'a> {
1022    pub ws1: Ws,
1023    pub name: WithSpan<&'a str>,
1024    pub nodes: Vec<Box<Node<'a>>>,
1025    pub ws2: Ws,
1026}
1027
1028impl<'a: 'l, 'l> BlockDef<'a> {
1029    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1030        let mut start = (
1031            opt(Whitespace::parse),
1032            ws(keyword("block").span()),
1033            cut_node(
1034                Some("block"),
1035                (
1036                    ws(identifier.with_span()),
1037                    opt(Whitespace::parse),
1038                    block_end,
1039                ),
1040            ),
1041        );
1042        let (pws1, keyword_span, ((name, name_span), nws1, _)) = start.parse_next(i)?;
1043        let keyword_span = Span::new(keyword_span);
1044
1045        let mut end = cut_node(
1046            Some("block"),
1047            (
1048                Node::many,
1049                cut_node(
1050                    Some("block"),
1051                    (
1052                        |i: &mut _| check_block_start(i, keyword_span, "block", "endblock"),
1053                        opt(Whitespace::parse),
1054                        end_node("block", "endblock"),
1055                        check_end_name(name, "block"),
1056                    ),
1057                ),
1058            ),
1059        );
1060        let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?;
1061
1062        Ok(Box::new(Node::BlockDef(WithSpan::new(
1063            BlockDef {
1064                ws1: Ws(pws1, nws1),
1065                name: WithSpan::new(name, name_span),
1066                nodes,
1067                ws2: Ws(pws2, nws2),
1068            },
1069            keyword_span,
1070        ))))
1071    }
1072}
1073
1074fn check_end_name<'a: 'l, 'l>(
1075    name: &'a str,
1076    kind: &'static str,
1077) -> impl ModalParser<InputStream<'a, 'l>, Option<Whitespace>, ErrorContext> {
1078    let name = move |i: &mut InputStream<'a, 'l>| {
1079        let Some((end_name, span)) = ws(opt(identifier.with_span())).parse_next(i)? else {
1080            return Ok(());
1081        };
1082        if name == end_name {
1083            return Ok(());
1084        }
1085
1086        cut_error!(
1087            if name.is_empty() && !end_name.is_empty() {
1088                format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`")
1089            } else {
1090                format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`")
1091            },
1092            span,
1093        )
1094    };
1095    cut_node(Some(kind), preceded(name, opt(Whitespace::parse)))
1096}
1097
1098#[derive(Debug, PartialEq)]
1099pub struct Lit<'a> {
1100    pub lws: WithSpan<&'a str>,
1101    pub val: WithSpan<&'a str>,
1102    pub rws: WithSpan<&'a str>,
1103}
1104
1105impl<'a: 'l, 'l> Lit<'a> {
1106    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1107        let content = take_until(
1108            ..,
1109            (
1110                i.state.syntax.block_start,
1111                i.state.syntax.comment_start,
1112                i.state.syntax.expr_start,
1113            ),
1114        );
1115        let (content, span) = preceded(not(eof), alt((content, rest)))
1116            .verify(|s: &str| !s.is_empty())
1117            .with_span()
1118            .parse_next(i)?;
1119        Ok(Box::new(Node::Lit(Self::split_ws_parts(WithSpan::new(
1120            content, span,
1121        )))))
1122    }
1123
1124    pub(crate) fn split_ws_parts(s: WithSpan<&'a str>) -> WithSpan<Self> {
1125        let content = if let Some(mid) = s.find(|c: char| !c.is_ascii_whitespace()) {
1126            let (lws, val) = s.split_at(mid);
1127            let mid = val.trim_ascii_end().len();
1128            if val.len() != mid {
1129                let (val, rws) = val.split_at(mid);
1130                Self { lws, val, rws }
1131            } else {
1132                Self {
1133                    lws,
1134                    val,
1135                    rws: val.end(),
1136                }
1137            }
1138        } else {
1139            let end = s.end();
1140            Self {
1141                lws: s,
1142                val: end,
1143                rws: end,
1144            }
1145        };
1146        WithSpan::new(content, s.span)
1147    }
1148}
1149
1150#[derive(Debug, PartialEq)]
1151pub struct Raw<'a> {
1152    pub ws1: Ws,
1153    pub lit: WithSpan<Lit<'a>>,
1154    pub ws2: Ws,
1155}
1156
1157impl<'a: 'l, 'l> Raw<'a> {
1158    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1159        fn endraw<'a: 'l, 'l>(
1160            i: &mut InputStream<'a, 'l>,
1161        ) -> ParseResult<'a, (Ws, WithSpan<Lit<'a>>)> {
1162            let start_i = ***i;
1163            let start_idx = i.current_token_start();
1164            loop {
1165                // - find the string "endraw"
1166                // - strip any spaces before it
1167                // - look if there is a whitespace handling character
1168                // - look if there is `{%`
1169                let span = terminated(take_until(.., "endraw").span(), "endraw").parse_next(i)?;
1170                let inner = start_i[..span.end - start_idx].trim_ascii_end();
1171
1172                let mut inner_chars = inner.chars();
1173                let (inner, pws) = if let Some(c) = inner_chars.next_back()
1174                    && let Some(pws) = Whitespace::parse_char(c)
1175                {
1176                    (inner_chars.as_str(), Some(pws))
1177                } else {
1178                    (inner, None)
1179                };
1180
1181                let Some(inner) = inner.strip_suffix(i.state.syntax.block_start) else {
1182                    continue;
1183                };
1184                let span = start_idx..start_idx + inner.len();
1185
1186                // We found `{% endraw`. Do we find `%}`, too?
1187                skip_ws0(i)?;
1188                let i_before_nws = i.checkpoint();
1189                let nws = opt(Whitespace::parse).parse_next(i)?;
1190                if opt(peek(block_end)).parse_next(i)?.is_none() {
1191                    i.reset(&i_before_nws); // `block_start` might start with the `nws` character
1192                    continue;
1193                }
1194
1195                return Ok((
1196                    Ws(pws, nws),
1197                    Lit::split_ws_parts(WithSpan::new(inner, span)),
1198                ));
1199            }
1200        }
1201
1202        let mut p = (
1203            opt(Whitespace::parse),
1204            ws(keyword("raw").span()),
1205            cut_node(
1206                Some("raw"),
1207                separated_pair(opt(Whitespace::parse), block_end, endraw),
1208            ),
1209        );
1210        let (pws, span, (nws, (ws2, lit))) = p.parse_next(i)?;
1211        let ws1 = Ws(pws, nws);
1212        Ok(Box::new(Node::Raw(WithSpan::new(
1213            Self { ws1, lit, ws2 },
1214            span,
1215        ))))
1216    }
1217}
1218
1219#[derive(Debug, PartialEq)]
1220pub struct Declare<'a> {
1221    pub ws: Ws,
1222    pub var_name: WithSpan<&'a str>,
1223    pub is_mutable: bool,
1224}
1225
1226impl<'a: 'l, 'l> Declare<'a> {
1227    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1228        let mut p = (
1229            opt(Whitespace::parse),
1230            ws(alt((keyword("decl"), keyword("declare"))).span()),
1231            ws(opt(keyword("mut").span())),
1232            ws(identifier.with_span()),
1233            opt(Whitespace::parse),
1234        );
1235        let (pws, span, is_mut, (var_name, var_name_span), nws) = p.parse_next(i)?;
1236
1237        Ok(Box::new(Node::Declare(WithSpan::new(
1238            Declare {
1239                ws: Ws(pws, nws),
1240                var_name: WithSpan::new(var_name, var_name_span),
1241                is_mutable: is_mut.is_some(),
1242            },
1243            span,
1244        ))))
1245    }
1246}
1247
1248#[derive(Debug, PartialEq)]
1249pub enum LetValueOrBlock<'a> {
1250    Value(WithSpan<Box<Expr<'a>>>),
1251    Block { nodes: Vec<Box<Node<'a>>>, ws: Ws },
1252}
1253
1254#[derive(Debug, PartialEq)]
1255pub struct Let<'a> {
1256    pub ws: Ws,
1257    pub var: Target<'a>,
1258    pub val: LetValueOrBlock<'a>,
1259    pub is_mutable: bool,
1260}
1261
1262impl<'a: 'l, 'l> Let<'a> {
1263    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1264        let (pws, (tag, span), is_mut) = (
1265            opt(Whitespace::parse),
1266            ws(alt((keyword("let"), keyword("set"))).with_span()),
1267            ws(opt(keyword("mut").span())),
1268        )
1269            .parse_next(i)?;
1270
1271        let no_compound = |i: &mut _| {
1272            if let Some((op, span)) = opt(compound_assignment_op.with_span()).parse_next(i)? {
1273                cut_error!(
1274                    format!(
1275                        "the compound assignment `{op}` cannot be used with {s} {tag} {e}`, \
1276                        try `{s} mut {e}` instead",
1277                        s = i.state.syntax.block_start.escape_debug(),
1278                        e = i.state.syntax.block_end.escape_debug(),
1279                    ),
1280                    span,
1281                )
1282            } else {
1283                Ok(None)
1284            }
1285        };
1286
1287        let ((var, var_span), val, nws) = cut_node(
1288            Some("let"),
1289            (
1290                ws(Target::parse.with_span()),
1291                alt((
1292                    preceded(
1293                        ws('='),
1294                        cut_node(Some("let"), ws(|i: &mut _| Expr::parse(i, false).map(Some))),
1295                    ),
1296                    no_compound,
1297                )),
1298                opt(Whitespace::parse),
1299            ),
1300        )
1301        .parse_next(i)?;
1302
1303        if val.is_none()
1304            && let Some(kind) = match &var {
1305                Target::Name(_) => None,
1306                Target::Tuple(..) => Some("a tuple"),
1307                Target::Array(..) => Some("an array"),
1308                Target::Struct(..) => Some("a struct"),
1309                Target::NumLit(..)
1310                | Target::StrLit(..)
1311                | Target::CharLit(..)
1312                | Target::BoolLit(..) => Some("a literal"),
1313                Target::Path(..) => Some("a path or enum variant"),
1314                Target::OrChain(..) | Target::Placeholder(..) | Target::Rest(..) => {
1315                    Some("a pattern")
1316                }
1317            }
1318        {
1319            return cut_error!(
1320                format!(
1321                    "when you forward-define a variable, you cannot use {kind} in place of \
1322                    a variable name"
1323                ),
1324                var_span,
1325            );
1326        }
1327
1328        if let Some(mut_span) = &is_mut
1329            && !matches!(var, Target::Name(_))
1330        {
1331            return cut_error!(
1332                "you can only use the `mut` keyword with a variable name",
1333                mut_span.clone(),
1334            );
1335        }
1336
1337        if let Some(val) = val {
1338            return Ok(Box::new(Node::Let(WithSpan::new(
1339                Let {
1340                    ws: Ws(pws, nws),
1341                    var,
1342                    val: LetValueOrBlock::Value(val),
1343                    is_mutable: is_mut.is_some(),
1344                },
1345                span,
1346            ))));
1347        }
1348
1349        // We do this operation
1350        if block_end.parse_next(i).is_err() {
1351            return Err(
1352                ErrorContext::unclosed("block", i.state.syntax.block_end, Span::new(span)).cut(),
1353            );
1354        }
1355
1356        let (keyword, end_keyword) = if tag == "let" {
1357            ("let", "endlet")
1358        } else {
1359            ("set", "endset")
1360        };
1361
1362        let keyword_span = Span::new(span.clone());
1363        let mut end = cut_node(
1364            Some(keyword),
1365            (
1366                Node::many,
1367                cut_node(
1368                    Some(keyword),
1369                    (
1370                        |i: &mut _| check_block_start(i, keyword_span, keyword, end_keyword),
1371                        opt(Whitespace::parse),
1372                        end_node(keyword, end_keyword),
1373                        opt(Whitespace::parse),
1374                    ),
1375                ),
1376            ),
1377        );
1378        let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?;
1379
1380        Ok(Box::new(Node::Let(WithSpan::new(
1381            Let {
1382                ws: Ws(pws, nws),
1383                var,
1384                val: LetValueOrBlock::Block {
1385                    nodes,
1386                    ws: Ws(pws2, nws2),
1387                },
1388                is_mutable: is_mut.is_some(),
1389            },
1390            span,
1391        ))))
1392    }
1393}
1394
1395#[derive(Debug, PartialEq)]
1396pub struct Compound<'a> {
1397    pub op: WithSpan<BinOp<'a>>,
1398    pub ws: Ws,
1399}
1400
1401impl<'a: 'l, 'l> Compound<'a> {
1402    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1403        let (pws, span, (lhs, rhs, nws)) = (
1404            opt(Whitespace::parse),
1405            ws(keyword("mut").span()),
1406            cut_node(
1407                Some("mut"),
1408                (
1409                    |i: &mut _| Expr::parse(i, false),
1410                    opt((
1411                        ws(alt(("=", compound_assignment_op))),
1412                        cut_node(Some("mut"), ws(|i: &mut _| Expr::parse(i, false))),
1413                    )),
1414                    opt(Whitespace::parse),
1415                ),
1416            ),
1417        )
1418            .parse_next(i)?;
1419
1420        let Some((op, rhs)) = rhs else {
1421            return cut_error!(
1422                format!(
1423                    "`{s} mut {e}` expects a (compound) assignment, did you mean to \
1424                    forward declare a mutable variable with `{s} let mut {e}`?",
1425                    s = i.state.syntax.block_start.escape_debug(),
1426                    e = i.state.syntax.block_end.escape_debug(),
1427                ),
1428                span,
1429            );
1430        };
1431
1432        Ok(Box::new(Node::Compound(WithSpan::new(
1433            Compound {
1434                ws: Ws(pws, nws),
1435                op: WithSpan::new(BinOp { op, lhs, rhs }, span.clone()),
1436            },
1437            span,
1438        ))))
1439    }
1440}
1441
1442/// <https://doc.rust-lang.org/reference/expressions/operator-expr.html#compound-assignment-expressions>
1443fn compound_assignment_op<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a> {
1444    const TWO: &[[u8; 2]] = &[
1445        *b"+=", *b"-=", *b"*=", *b"/=", *b"%=", *b"&=", *b"|=", *b"^=",
1446    ];
1447
1448    alt((
1449        take(2usize).verify(|s: &str| {
1450            if let Ok(s) = s.as_bytes().try_into() {
1451                TWO.contains(&s)
1452            } else {
1453                false
1454            }
1455        }),
1456        take(3usize).verify(|s| matches!(s, "<<=" | ">>=")),
1457    ))
1458    .parse_next(i)
1459}
1460
1461#[derive(Debug, PartialEq)]
1462pub struct If<'a> {
1463    pub ws: Ws,
1464    pub branches: Vec<WithSpan<Cond<'a>>>,
1465}
1466
1467impl<'a: 'l, 'l> If<'a> {
1468    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1469        let (pws1, cond) = (opt(Whitespace::parse), CondTest::parse).parse_next(i)?;
1470        let cond_span = cond.span;
1471        let end_if = cut_node(
1472            Some("if"),
1473            (
1474                |i: &mut _| check_block_start(i, cond_span, "if", "endif"),
1475                opt(Whitespace::parse),
1476                end_node("if", "endif"),
1477                opt(Whitespace::parse),
1478            ),
1479        );
1480        let mut p = cut_node(
1481            Some("if"),
1482            (
1483                opt(Whitespace::parse),
1484                block_end,
1485                cut_node(
1486                    Some("if"),
1487                    (
1488                        Node::many,
1489                        repeat(0.., Cond::parse).map(|v: Vec<_>| v),
1490                        end_if,
1491                    ),
1492                ),
1493            ),
1494        );
1495
1496        let (nws1, _, (nodes, elifs, (_, pws2, _, nws2))) = p.parse_next(i)?;
1497        let mut branches = vec![WithSpan::new(
1498            Cond {
1499                ws: Ws(pws1, nws1),
1500                cond: Some(cond),
1501                nodes,
1502            },
1503            cond_span,
1504        )];
1505        branches.extend(elifs);
1506
1507        Ok(Box::new(Node::If(WithSpan::new(
1508            Self {
1509                ws: Ws(pws2, nws2),
1510                branches,
1511            },
1512            cond_span,
1513        ))))
1514    }
1515}
1516
1517#[derive(Debug, PartialEq)]
1518pub struct Include<'a> {
1519    pub ws: Ws,
1520    pub path: &'a str,
1521}
1522
1523impl<'a: 'l, 'l> Include<'a> {
1524    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1525        let p = (
1526            opt(Whitespace::parse),
1527            ws(keyword("include")),
1528            cut_node(
1529                Some("include"),
1530                (ws(str_lit_without_prefix), opt(Whitespace::parse)),
1531            ),
1532        );
1533        let ((pws, _, (path, nws)), span) = p.with_span().parse_next(i)?;
1534        Ok(Box::new(Node::Include(WithSpan::new(
1535            Self {
1536                ws: Ws(pws, nws),
1537                path,
1538            },
1539            span,
1540        ))))
1541    }
1542}
1543
1544#[derive(Debug, PartialEq)]
1545pub struct Extends<'a> {
1546    pub path: &'a str,
1547}
1548
1549impl<'a: 'l, 'l> Extends<'a> {
1550    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1551        let p = preceded(
1552            (opt(Whitespace::parse), ws(keyword("extends"))),
1553            cut_node(
1554                Some("extends"),
1555                terminated(ws(str_lit_without_prefix), opt(Whitespace::parse)),
1556            ),
1557        );
1558        let (path, span) = p.with_span().parse_next(i)?;
1559        Ok(Box::new(Node::Extends(WithSpan::new(Self { path }, span))))
1560    }
1561}
1562
1563#[derive(Debug, PartialEq)]
1564pub struct Comment<'a> {
1565    pub ws: Ws,
1566    pub content: &'a str,
1567}
1568
1569impl<'a: 'l, 'l> Comment<'a> {
1570    fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, Box<Node<'a>>> {
1571        fn content<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, ()> {
1572            let mut depth = 0usize;
1573            loop {
1574                take_until(
1575                    ..,
1576                    (i.state.syntax.comment_start, i.state.syntax.comment_end),
1577                )
1578                .parse_next(i)?;
1579                let is_open = opt(i.state.syntax.comment_start).parse_next(i)?.is_some();
1580                if is_open {
1581                    // cannot overflow: `i` cannot be longer than `isize::MAX`, cf. [std::alloc::Layout]
1582                    depth += 1;
1583                } else if let Some(new_depth) = depth.checked_sub(1) {
1584                    literal(i.state.syntax.comment_end).parse_next(i)?;
1585                    depth = new_depth;
1586                } else {
1587                    return Ok(());
1588                }
1589            }
1590        }
1591
1592        fn comment<'a: 'l, 'l>(i: &mut InputStream<'a, 'l>) -> ParseResult<'a> {
1593            let start = i.state.syntax.comment_start.span().parse_next(i)?;
1594            let mut content = opt(terminated(content.take(), i.state.syntax.comment_end));
1595            let Some(content) = content.parse_next(i)? else {
1596                return Err(
1597                    ErrorContext::unclosed("comment", i.state.syntax.comment_end, start).cut(),
1598                );
1599            };
1600            Ok(content)
1601        }
1602
1603        let (content, span) = comment.with_span().parse_next(i)?;
1604        let ws = match *content.as_bytes() {
1605            [b'-' | b'+' | b'~'] => {
1606                return cut_error!(
1607                    format!(
1608                        "ambiguous whitespace stripping\n\
1609                        use `{}{content} {content}{}` to apply the same whitespace stripping on \
1610                        both sides",
1611                        i.state.syntax.comment_start, i.state.syntax.comment_end,
1612                    ),
1613                    span,
1614                );
1615            }
1616            [pws, .., nws] => Ws(Whitespace::parse_byte(pws), Whitespace::parse_byte(nws)),
1617            _ => Ws(None, None),
1618        };
1619        Ok(Box::new(Node::Comment(WithSpan::new(
1620            Self { ws, content },
1621            span,
1622        ))))
1623    }
1624}
1625
1626/// First field is "minus/plus sign was used on the left part of the item".
1627///
1628/// Second field is "minus/plus sign was used on the right part of the item".
1629#[derive(Clone, Copy, Debug, PartialEq)]
1630pub struct Ws(pub Option<Whitespace>, pub Option<Whitespace>);
1631
1632fn end_node<'a: 'l, 'g: 'a, 'l>(
1633    node: &'g str,
1634    expected: &'g str,
1635) -> impl ModalParser<InputStream<'a, 'l>, &'a str, ErrorContext> + 'g {
1636    move |i: &mut InputStream<'a, 'l>| {
1637        let start = i.checkpoint();
1638        let (actual, span) = ws(identifier.with_span()).parse_next(i)?;
1639        if actual == expected {
1640            Ok(actual)
1641        } else if actual.starts_with("end") {
1642            i.reset(&start);
1643            cut_error!(
1644                format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"),
1645                span,
1646            )
1647        } else {
1648            i.reset(&start);
1649            fail.parse_next(i)
1650        }
1651    }
1652}