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