1use std::str;
2
3use nom::branch::alt;
4use nom::bytes::complete::{tag, take_till};
5use nom::character::complete::{anychar, char};
6use nom::combinator::{
7 complete, consumed, cut, eof, fail, map, map_opt, not, opt, peek, recognize, value,
8};
9use nom::multi::{many0, separated_list0, separated_list1};
10use nom::sequence::{delimited, pair, preceded, tuple};
11
12use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3};
13use crate::{
14 ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan, filter, identifier, is_ws,
15 keyword, not_ws, skip_till, str_lit_without_prefix, ws,
16};
17
18#[derive(Debug, PartialEq)]
19pub enum Node<'a> {
20 Lit(WithSpan<'a, Lit<'a>>),
21 Comment(WithSpan<'a, Comment<'a>>),
22 Expr(Ws, WithSpan<'a, Expr<'a>>),
23 Call(WithSpan<'a, Call<'a>>),
24 Let(WithSpan<'a, Let<'a>>),
25 If(WithSpan<'a, If<'a>>),
26 Match(WithSpan<'a, Match<'a>>),
27 Loop(Box<WithSpan<'a, Loop<'a>>>),
28 Extends(WithSpan<'a, Extends<'a>>),
29 BlockDef(WithSpan<'a, BlockDef<'a>>),
30 Include(WithSpan<'a, Include<'a>>),
31 Import(WithSpan<'a, Import<'a>>),
32 Macro(WithSpan<'a, Macro<'a>>),
33 Raw(WithSpan<'a, Raw<'a>>),
34 Break(WithSpan<'a, Ws>),
35 Continue(WithSpan<'a, Ws>),
36 FilterBlock(WithSpan<'a, FilterBlock<'a>>),
37}
38
39impl<'a> Node<'a> {
40 pub(super) fn parse_template(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> {
41 let (i, result) = match complete(|i| Self::many(i, s))(i) {
42 Ok((i, result)) => (i, result),
43 Err(err) => {
44 if let nom::Err::Error(err) | nom::Err::Failure(err) = &err {
45 if err.message.is_none() {
46 opt(|i| unexpected_tag(i, s))(err.input)?;
47 }
48 }
49 return Err(err);
50 }
51 };
52 let (i, _) = opt(|i| unexpected_tag(i, s))(i)?;
53 let (i, is_eof) = opt(eof)(i)?;
54 if is_eof.is_none() {
55 return Err(nom::Err::Failure(ErrorContext::new(
56 "cannot parse entire template\n\
57 you should never encounter this error\n\
58 please report this error to <https://github.com/rinja-rs/rinja/issues>",
59 i,
60 )));
61 }
62 Ok((i, result))
63 }
64
65 fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> {
66 many0(alt((
67 map(|i| Lit::parse(i, s), Self::Lit),
68 map(|i| Comment::parse(i, s), Self::Comment),
69 |i| Self::expr(i, s),
70 |i| Self::parse(i, s),
71 )))(i)
72 }
73
74 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
75 #[inline]
76 fn wrap<'a, T>(
77 func: impl FnOnce(T) -> Node<'a>,
78 result: ParseResult<'a, T>,
79 ) -> ParseResult<'a, Node<'a>> {
80 result.map(|(i, n)| (i, func(n)))
81 }
82
83 let start = i;
84 let (j, tag) = preceded(
85 |i| s.tag_block_start(i),
86 peek(preceded(
87 pair(opt(Whitespace::parse), take_till(not_ws)),
88 identifier,
89 )),
90 )(i)?;
91
92 let func = match tag {
93 "call" => |i, s| wrap(Self::Call, Call::parse(i, s)),
94 "let" | "set" => |i, s| wrap(Self::Let, Let::parse(i, s)),
95 "if" => |i, s| wrap(Self::If, If::parse(i, s)),
96 "for" => |i, s| wrap(|n| Self::Loop(Box::new(n)), Loop::parse(i, s)),
97 "match" => |i, s| wrap(Self::Match, Match::parse(i, s)),
98 "extends" => |i, _s| wrap(Self::Extends, Extends::parse(i)),
99 "include" => |i, _s| wrap(Self::Include, Include::parse(i)),
100 "import" => |i, _s| wrap(Self::Import, Import::parse(i)),
101 "block" => |i, s| wrap(Self::BlockDef, BlockDef::parse(i, s)),
102 "macro" => |i, s| wrap(Self::Macro, Macro::parse(i, s)),
103 "raw" => |i, s| wrap(Self::Raw, Raw::parse(i, s)),
104 "break" => |i, s| Self::r#break(i, s),
105 "continue" => |i, s| Self::r#continue(i, s),
106 "filter" => |i, s| wrap(Self::FilterBlock, FilterBlock::parse(i, s)),
107 _ => return fail(start),
108 };
109
110 let (i, node) = s.nest(j, |i| func(i, s))?;
111
112 let (i, closed) = cut_node(
113 None,
114 alt((value(true, |i| s.tag_block_end(i)), value(false, ws(eof)))),
115 )(i)?;
116 match closed {
117 true => Ok((i, node)),
118 false => Err(ErrorContext::unclosed("block", s.syntax.block_end, start).into()),
119 }
120 }
121
122 fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
123 let mut p = tuple((
124 opt(Whitespace::parse),
125 ws(keyword("break")),
126 opt(Whitespace::parse),
127 ));
128 let (j, (pws, _, nws)) = p(i)?;
129 if !s.is_in_loop() {
130 return Err(nom::Err::Failure(ErrorContext::new(
131 "you can only `break` inside a `for` loop",
132 i,
133 )));
134 }
135 Ok((j, Self::Break(WithSpan::new(Ws(pws, nws), i))))
136 }
137
138 fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
139 let mut p = tuple((
140 opt(Whitespace::parse),
141 ws(keyword("continue")),
142 opt(Whitespace::parse),
143 ));
144 let (j, (pws, _, nws)) = p(i)?;
145 if !s.is_in_loop() {
146 return Err(nom::Err::Failure(ErrorContext::new(
147 "you can only `continue` inside a `for` loop",
148 i,
149 )));
150 }
151 Ok((j, Self::Continue(WithSpan::new(Ws(pws, nws), i))))
152 }
153
154 fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
155 let start = i;
156 let (i, (pws, expr)) = preceded(
157 |i| s.tag_expr_start(i),
158 cut_node(
159 None,
160 pair(
161 opt(Whitespace::parse),
162 ws(|i| Expr::parse(i, s.level.get())),
163 ),
164 ),
165 )(i)?;
166
167 let (i, (nws, closed)) = cut_node(
168 None,
169 pair(
170 opt(Whitespace::parse),
171 alt((value(true, |i| s.tag_expr_end(i)), value(false, ws(eof)))),
172 ),
173 )(i)?;
174 match closed {
175 true => Ok((i, Self::Expr(Ws(pws, nws), expr))),
176 false => Err(ErrorContext::unclosed("expression", s.syntax.expr_end, start).into()),
177 }
178 }
179
180 #[must_use]
181 pub fn span(&self) -> &str {
182 match self {
183 Self::Lit(span) => span.span,
184 Self::Comment(span) => span.span,
185 Self::Expr(_, span) => span.span,
186 Self::Call(span) => span.span,
187 Self::Let(span) => span.span,
188 Self::If(span) => span.span,
189 Self::Match(span) => span.span,
190 Self::Loop(span) => span.span,
191 Self::Extends(span) => span.span,
192 Self::BlockDef(span) => span.span,
193 Self::Include(span) => span.span,
194 Self::Import(span) => span.span,
195 Self::Macro(span) => span.span,
196 Self::Raw(span) => span.span,
197 Self::Break(span) => span.span,
198 Self::Continue(span) => span.span,
199 Self::FilterBlock(span) => span.span,
200 }
201 }
202}
203
204fn cut_node<'a, O>(
205 kind: Option<&'static str>,
206 inner: impl FnMut(&'a str) -> ParseResult<'a, O>,
207) -> impl FnMut(&'a str) -> ParseResult<'a, O> {
208 let mut inner: impl FnMut(&'a str) -> Result<…, …> = cut(parser:inner);
209 move |i: &'a str| {
210 let result: Result<(&'a str, O), Err>> = inner(i);
211 if let Err(nom::Err::Failure(err: &ErrorContext<'a>) | nom::Err::Error(err: &ErrorContext<'a>)) = &result {
212 if err.message.is_none() {
213 opt(|i: &str| unexpected_raw_tag(kind, i))(err.input)?;
214 }
215 }
216 result
217 }
218}
219
220fn unexpected_tag<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, ()> {
221 value(
222 (),
223 parser:tuple((
224 |i: &str| s.tag_block_start(i),
225 opt(Whitespace::parse),
226 |i: &str| unexpected_raw_tag(kind:None, i),
227 )),
228 )(i)
229}
230
231fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &'a str) -> ParseResult<'a, ()> {
232 let (_, tag: &str) = ws(inner:identifier)(i)?;
233 let msg: String = match tag {
234 "end" | "elif" | "else" | "when" => match kind {
235 Some(kind: &'static str) => {
236 format!("node `{tag}` was not expected in the current context: `{kind}` block")
237 }
238 None => format!("node `{tag}` was not expected in the current context"),
239 },
240 tag: &str if tag.starts_with("end") => format!("unexpected closing tag `{tag}`"),
241 tag: &str => format!("unknown node `{tag}`"),
242 };
243 Err(nom::Err::Failure(ErrorContext::new(message:msg, input:i)))
244}
245
246#[derive(Debug, PartialEq)]
247pub struct When<'a> {
248 pub ws: Ws,
249 pub target: Vec<Target<'a>>,
250 pub nodes: Vec<Node<'a>>,
251}
252
253impl<'a> When<'a> {
254 fn r#else(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
255 let start = i;
256 let mut p = tuple((
257 |i| s.tag_block_start(i),
258 opt(Whitespace::parse),
259 ws(keyword("else")),
260 cut_node(
261 Some("match-else"),
262 tuple((
263 opt(Whitespace::parse),
264 |i| s.tag_block_end(i),
265 cut_node(Some("match-else"), |i| Node::many(i, s)),
266 )),
267 ),
268 ));
269 let (i, (_, pws, _, (nws, _, nodes))) = p(i)?;
270 Ok((
271 i,
272 WithSpan::new(
273 Self {
274 ws: Ws(pws, nws),
275 target: vec![Target::Placeholder("_")],
276 nodes,
277 },
278 start,
279 ),
280 ))
281 }
282
283 #[allow(clippy::self_named_constructors)]
284 fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
285 let start = i;
286 let endwhen = map(
287 consumed(ws(pair(
288 delimited(
289 |i| s.tag_block_start(i),
290 opt(Whitespace::parse),
291 ws(keyword("endwhen")),
292 ),
293 cut_node(
294 Some("match-endwhen"),
295 tuple((
296 opt(Whitespace::parse),
297 |i| s.tag_block_end(i),
298 many0(value((), ws(|i| Comment::parse(i, s)))),
299 )),
300 ),
301 ))),
302 |(span, (pws, _))| {
303 // A comment node is used to pass the whitespace suppressing information to the
304 // generator. This way we don't have to fix up the next `when` node or the closing
305 // `endmatch`. Any whitespaces after `endwhen` are to be suppressed. Actually, they
306 // don't wind up in the AST anyway.
307 Node::Comment(WithSpan::new(
308 Comment {
309 ws: Ws(pws, Some(Whitespace::Suppress)),
310 content: "",
311 },
312 span,
313 ))
314 },
315 );
316 let mut p = tuple((
317 |i| s.tag_block_start(i),
318 opt(Whitespace::parse),
319 ws(keyword("when")),
320 cut_node(
321 Some("match-when"),
322 tuple((
323 separated_list1(char('|'), ws(|i| Target::parse(i, s))),
324 opt(Whitespace::parse),
325 |i| s.tag_block_end(i),
326 cut_node(Some("match-when"), |i| Node::many(i, s)),
327 opt(endwhen),
328 )),
329 ),
330 ));
331 let (i, (_, pws, _, (target, nws, _, mut nodes, endwhen))) = p(i)?;
332 if let Some(endwhen) = endwhen {
333 nodes.push(endwhen);
334 }
335 Ok((
336 i,
337 WithSpan::new(
338 Self {
339 ws: Ws(pws, nws),
340 target,
341 nodes,
342 },
343 start,
344 ),
345 ))
346 }
347}
348
349#[derive(Debug, PartialEq)]
350pub struct Cond<'a> {
351 pub ws: Ws,
352 pub cond: Option<CondTest<'a>>,
353 pub nodes: Vec<Node<'a>>,
354}
355
356impl<'a> Cond<'a> {
357 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
358 let start = i;
359 let (i, (_, pws, cond, nws, _, nodes)) = tuple((
360 |i| s.tag_block_start(i),
361 opt(Whitespace::parse),
362 alt((
363 preceded(ws(keyword("else")), opt(|i| CondTest::parse(i, s))),
364 preceded(
365 ws(keyword("elif")),
366 cut_node(Some("if-elif"), map(|i| CondTest::parse_cond(i, s), Some)),
367 ),
368 )),
369 opt(Whitespace::parse),
370 cut_node(Some("if"), |i| s.tag_block_end(i)),
371 cut_node(Some("if"), |i| Node::many(i, s)),
372 ))(i)?;
373 Ok((
374 i,
375 WithSpan::new(
376 Self {
377 ws: Ws(pws, nws),
378 cond,
379 nodes,
380 },
381 start,
382 ),
383 ))
384 }
385}
386
387#[derive(Debug, PartialEq)]
388pub struct CondTest<'a> {
389 pub target: Option<Target<'a>>,
390 pub expr: WithSpan<'a, Expr<'a>>,
391 pub contains_bool_lit_or_is_defined: bool,
392}
393
394impl<'a> CondTest<'a> {
395 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
396 preceded(
397 ws(keyword("if")),
398 cut_node(Some("if"), |i| Self::parse_cond(i, s)),
399 )(i)
400 }
401
402 fn parse_cond(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
403 let (i, (target, expr)) = pair(
404 opt(delimited(
405 ws(alt((keyword("let"), keyword("set")))),
406 ws(|i| Target::parse(i, s)),
407 ws(char('=')),
408 )),
409 ws(|i| Expr::parse(i, s.level.get())),
410 )(i)?;
411 let contains_bool_lit_or_is_defined = expr.contains_bool_lit_or_is_defined();
412 Ok((i, Self {
413 target,
414 expr,
415 contains_bool_lit_or_is_defined,
416 }))
417 }
418}
419
420#[derive(Clone, Copy, Debug, PartialEq)]
421pub enum Whitespace {
422 Preserve,
423 Suppress,
424 Minimize,
425}
426
427impl Whitespace {
428 fn parse(i: &str) -> ParseResult<'_, Self> {
429 map_opt(parser:anychar, Self::parse_char)(i)
430 }
431
432 fn parse_char(c: char) -> Option<Self> {
433 match c {
434 '+' => Some(Self::Preserve),
435 '-' => Some(Self::Suppress),
436 '~' => Some(Self::Minimize),
437 _ => None,
438 }
439 }
440}
441
442fn check_block_start<'a>(
443 i: &'a str,
444 start: &'a str,
445 s: &State<'_>,
446 node: &str,
447 expected: &str,
448) -> ParseResult<'a> {
449 if i.is_empty() {
450 return Err(nom::Err::Failure(ErrorContext::new(
451 message:format!("expected `{expected}` to terminate `{node}` node, found nothing"),
452 input:start,
453 )));
454 }
455 s.tag_block_start(i)
456}
457
458#[derive(Debug, PartialEq)]
459pub struct Loop<'a> {
460 pub ws1: Ws,
461 pub var: Target<'a>,
462 pub iter: WithSpan<'a, Expr<'a>>,
463 pub cond: Option<WithSpan<'a, Expr<'a>>>,
464 pub body: Vec<Node<'a>>,
465 pub ws2: Ws,
466 pub else_nodes: Vec<Node<'a>>,
467 pub ws3: Ws,
468}
469
470impl<'a> Loop<'a> {
471 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
472 fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Node<'a>>> {
473 s.enter_loop();
474 let result = Node::many(i, s);
475 s.leave_loop();
476 result
477 }
478
479 let start = i;
480 let if_cond = preceded(
481 ws(keyword("if")),
482 cut_node(Some("for-if"), ws(|i| Expr::parse(i, s.level.get()))),
483 );
484
485 let else_block = |i| {
486 let mut p = preceded(
487 ws(keyword("else")),
488 cut_node(
489 Some("for-else"),
490 tuple((
491 opt(Whitespace::parse),
492 delimited(
493 |i| s.tag_block_end(i),
494 |i| Node::many(i, s),
495 |i| s.tag_block_start(i),
496 ),
497 opt(Whitespace::parse),
498 )),
499 ),
500 );
501 let (i, (pws, nodes, nws)) = p(i)?;
502 Ok((i, (pws, nodes, nws)))
503 };
504
505 let body_and_end = |i| {
506 let (i, (body, (_, pws, else_block, _, nws))) = cut_node(
507 Some("for"),
508 tuple((
509 |i| content(i, s),
510 cut_node(
511 Some("for"),
512 tuple((
513 |i| check_block_start(i, start, s, "for", "endfor"),
514 opt(Whitespace::parse),
515 opt(else_block),
516 end_node("for", "endfor"),
517 opt(Whitespace::parse),
518 )),
519 ),
520 )),
521 )(i)?;
522 Ok((i, (body, pws, else_block, nws)))
523 };
524
525 let mut p = tuple((
526 opt(Whitespace::parse),
527 ws(keyword("for")),
528 cut_node(
529 Some("for"),
530 tuple((
531 ws(|i| Target::parse(i, s)),
532 ws(keyword("in")),
533 cut_node(
534 Some("for"),
535 tuple((
536 ws(|i| Expr::parse(i, s.level.get())),
537 opt(if_cond),
538 opt(Whitespace::parse),
539 |i| s.tag_block_end(i),
540 body_and_end,
541 )),
542 ),
543 )),
544 ),
545 ));
546 let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, pws2, else_block, nws2))))) = p(i)?;
547 let (nws3, else_nodes, pws3) = else_block.unwrap_or_default();
548 Ok((
549 i,
550 WithSpan::new(
551 Self {
552 ws1: Ws(pws1, nws1),
553 var,
554 iter,
555 cond,
556 body,
557 ws2: Ws(pws2, nws3),
558 else_nodes,
559 ws3: Ws(pws3, nws2),
560 },
561 start,
562 ),
563 ))
564 }
565}
566
567#[derive(Debug, PartialEq)]
568pub struct Macro<'a> {
569 pub ws1: Ws,
570 pub name: &'a str,
571 pub args: Vec<&'a str>,
572 pub nodes: Vec<Node<'a>>,
573 pub ws2: Ws,
574}
575
576impl<'a> Macro<'a> {
577 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
578 fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> {
579 delimited(
580 ws(char('(')),
581 separated_list0(char(','), ws(identifier)),
582 tuple((opt(ws(char(','))), char(')'))),
583 )(i)
584 }
585
586 let start_s = i;
587 let mut start = tuple((
588 opt(Whitespace::parse),
589 ws(keyword("macro")),
590 cut_node(
591 Some("macro"),
592 tuple((
593 ws(identifier),
594 opt(ws(parameters)),
595 opt(Whitespace::parse),
596 |i| s.tag_block_end(i),
597 )),
598 ),
599 ));
600 let (j, (pws1, _, (name, params, nws1, _))) = start(i)?;
601 if is_rust_keyword(name) {
602 return Err(nom::Err::Failure(ErrorContext::new(
603 format!("'{name}' is not a valid name for a macro"),
604 i,
605 )));
606 }
607
608 let mut end = cut_node(
609 Some("macro"),
610 tuple((
611 |i| Node::many(i, s),
612 cut_node(
613 Some("macro"),
614 tuple((
615 |i| check_block_start(i, start_s, s, "macro", "endmacro"),
616 opt(Whitespace::parse),
617 end_node("macro", "endmacro"),
618 cut_node(
619 Some("macro"),
620 preceded(
621 opt(|before| {
622 let (after, end_name) = ws(identifier)(before)?;
623 check_end_name(before, after, name, end_name, "macro")
624 }),
625 opt(Whitespace::parse),
626 ),
627 ),
628 )),
629 ),
630 )),
631 );
632 let (i, (contents, (_, pws2, _, nws2))) = end(j)?;
633
634 Ok((
635 i,
636 WithSpan::new(
637 Self {
638 ws1: Ws(pws1, nws1),
639 name,
640 args: params.unwrap_or_default(),
641 nodes: contents,
642 ws2: Ws(pws2, nws2),
643 },
644 start_s,
645 ),
646 ))
647 }
648}
649
650#[derive(Debug, PartialEq)]
651pub struct FilterBlock<'a> {
652 pub ws1: Ws,
653 pub filters: Filter<'a>,
654 pub nodes: Vec<Node<'a>>,
655 pub ws2: Ws,
656}
657
658impl<'a> FilterBlock<'a> {
659 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
660 let mut level = s.level.get();
661 let start_s = i;
662 let mut start = tuple((
663 opt(Whitespace::parse),
664 ws(keyword("filter")),
665 cut_node(
666 Some("filter"),
667 tuple((
668 ws(identifier),
669 opt(|i| Expr::arguments(i, s.level.get(), false)),
670 many0(|i| {
671 filter(i, &mut level).map(|(j, (name, params))| (j, (name, params, i)))
672 }),
673 ws(|i| Ok((i, ()))),
674 opt(Whitespace::parse),
675 |i| s.tag_block_end(i),
676 )),
677 ),
678 ));
679 let (i, (pws1, _, (filter_name, params, extra_filters, (), nws1, _))) = start(i)?;
680
681 let mut arguments = params.unwrap_or_default();
682 arguments.insert(0, WithSpan::new(Expr::FilterSource, start_s));
683 let mut filters = Filter {
684 name: filter_name,
685 arguments,
686 };
687 for (filter_name, args, span) in extra_filters {
688 filters = Filter {
689 name: filter_name,
690 arguments: {
691 let mut args = args.unwrap_or_default();
692 args.insert(0, WithSpan::new(Expr::Filter(filters), span));
693 args
694 },
695 };
696 }
697
698 let mut end = cut_node(
699 Some("filter"),
700 tuple((
701 |i| Node::many(i, s),
702 cut_node(
703 Some("filter"),
704 tuple((
705 |i| check_block_start(i, start_s, s, "filter", "endfilter"),
706 opt(Whitespace::parse),
707 end_node("filter", "endfilter"),
708 opt(Whitespace::parse),
709 )),
710 ),
711 )),
712 );
713 let (i, (nodes, (_, pws2, _, nws2))) = end(i)?;
714
715 Ok((
716 i,
717 WithSpan::new(
718 Self {
719 ws1: Ws(pws1, nws1),
720 filters,
721 nodes,
722 ws2: Ws(pws2, nws2),
723 },
724 start_s,
725 ),
726 ))
727 }
728}
729
730#[derive(Debug, PartialEq)]
731pub struct Import<'a> {
732 pub ws: Ws,
733 pub path: &'a str,
734 pub scope: &'a str,
735}
736
737impl<'a> Import<'a> {
738 fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> {
739 let start = i;
740 let mut p = tuple((
741 opt(Whitespace::parse),
742 ws(keyword("import")),
743 cut_node(
744 Some("import"),
745 tuple((
746 ws(str_lit_without_prefix),
747 ws(keyword("as")),
748 cut_node(Some("import"), pair(ws(identifier), opt(Whitespace::parse))),
749 )),
750 ),
751 ));
752 let (i, (pws, _, (path, _, (scope, nws)))) = p(i)?;
753 Ok((
754 i,
755 WithSpan::new(
756 Self {
757 ws: Ws(pws, nws),
758 path,
759 scope,
760 },
761 start,
762 ),
763 ))
764 }
765}
766
767#[derive(Debug, PartialEq)]
768pub struct Call<'a> {
769 pub ws: Ws,
770 pub scope: Option<&'a str>,
771 pub name: &'a str,
772 pub args: Vec<WithSpan<'a, Expr<'a>>>,
773}
774
775impl<'a> Call<'a> {
776 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
777 let start = i;
778 let mut p = tuple((
779 opt(Whitespace::parse),
780 ws(keyword("call")),
781 cut_node(
782 Some("call"),
783 tuple((
784 opt(tuple((ws(identifier), ws(tag("::"))))),
785 ws(identifier),
786 opt(ws(|nested| Expr::arguments(nested, s.level.get(), true))),
787 opt(Whitespace::parse),
788 )),
789 ),
790 ));
791 let (i, (pws, _, (scope, name, args, nws))) = p(i)?;
792 let scope = scope.map(|(scope, _)| scope);
793 let args = args.unwrap_or_default();
794 Ok((
795 i,
796 WithSpan::new(
797 Self {
798 ws: Ws(pws, nws),
799 scope,
800 name,
801 args,
802 },
803 start,
804 ),
805 ))
806 }
807}
808
809#[derive(Debug, PartialEq)]
810pub struct Match<'a> {
811 pub ws1: Ws,
812 pub expr: WithSpan<'a, Expr<'a>>,
813 pub arms: Vec<WithSpan<'a, When<'a>>>,
814 pub ws2: Ws,
815}
816
817impl<'a> Match<'a> {
818 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
819 let start = i;
820 let mut p = tuple((
821 opt(Whitespace::parse),
822 ws(keyword("match")),
823 cut_node(
824 Some("match"),
825 tuple((
826 ws(|i| Expr::parse(i, s.level.get())),
827 opt(Whitespace::parse),
828 |i| s.tag_block_end(i),
829 cut_node(
830 Some("match"),
831 tuple((
832 ws(many0(ws(value((), |i| Comment::parse(i, s))))),
833 many0(|i| When::when(i, s)),
834 cut_node(
835 Some("match"),
836 tuple((
837 opt(|i| When::r#else(i, s)),
838 cut_node(
839 Some("match"),
840 tuple((
841 ws(|i| {
842 check_block_start(i, start, s, "match", "endmatch")
843 }),
844 opt(Whitespace::parse),
845 end_node("match", "endmatch"),
846 opt(Whitespace::parse),
847 )),
848 ),
849 )),
850 ),
851 )),
852 ),
853 )),
854 ),
855 ));
856 let (i, (pws1, _, (expr, nws1, _, (_, mut arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?;
857
858 if let Some(arm) = else_arm {
859 arms.push(arm);
860 }
861 if arms.is_empty() {
862 return Err(nom::Err::Failure(ErrorContext::new(
863 "`match` nodes must contain at least one `when` node and/or an `else` case",
864 start,
865 )));
866 }
867
868 Ok((
869 i,
870 WithSpan::new(
871 Self {
872 ws1: Ws(pws1, nws1),
873 expr,
874 arms,
875 ws2: Ws(pws2, nws2),
876 },
877 start,
878 ),
879 ))
880 }
881}
882
883#[derive(Debug, PartialEq)]
884pub struct BlockDef<'a> {
885 pub ws1: Ws,
886 pub name: &'a str,
887 pub nodes: Vec<Node<'a>>,
888 pub ws2: Ws,
889}
890
891impl<'a> BlockDef<'a> {
892 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
893 let start_s = i;
894 let mut start = tuple((
895 opt(Whitespace::parse),
896 ws(keyword("block")),
897 cut_node(
898 Some("block"),
899 tuple((ws(identifier), opt(Whitespace::parse), |i| {
900 s.tag_block_end(i)
901 })),
902 ),
903 ));
904 let (i, (pws1, _, (name, nws1, _))) = start(i)?;
905
906 let mut end = cut_node(
907 Some("block"),
908 tuple((
909 |i| Node::many(i, s),
910 cut_node(
911 Some("block"),
912 tuple((
913 |i| check_block_start(i, start_s, s, "block", "endblock"),
914 opt(Whitespace::parse),
915 end_node("block", "endblock"),
916 cut_node(
917 Some("block"),
918 tuple((
919 opt(|before| {
920 let (after, end_name) = ws(identifier)(before)?;
921 check_end_name(before, after, name, end_name, "block")
922 }),
923 opt(Whitespace::parse),
924 )),
925 ),
926 )),
927 ),
928 )),
929 );
930 let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?;
931
932 Ok((
933 i,
934 WithSpan::new(
935 BlockDef {
936 ws1: Ws(pws1, nws1),
937 name,
938 nodes,
939 ws2: Ws(pws2, nws2),
940 },
941 start_s,
942 ),
943 ))
944 }
945}
946
947fn check_end_name<'a>(
948 before: &'a str,
949 after: &'a str,
950 name: &'a str,
951 end_name: &'a str,
952 kind: &str,
953) -> ParseResult<'a> {
954 if name == end_name {
955 return Ok((after, end_name));
956 }
957
958 Err(nom::Err::Failure(ErrorContext::new(
959 message:match name.is_empty() && !end_name.is_empty() {
960 true => format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`"),
961 false => format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`"),
962 },
963 input:before,
964 )))
965}
966
967#[derive(Debug, PartialEq)]
968pub struct Lit<'a> {
969 pub lws: &'a str,
970 pub val: &'a str,
971 pub rws: &'a str,
972}
973
974impl<'a> Lit<'a> {
975 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
976 let start = i;
977 let (i, ()) = not(eof)(i)?;
978
979 let candidate_finder = Splitter3::new(
980 s.syntax.block_start,
981 s.syntax.comment_start,
982 s.syntax.expr_start,
983 );
984 let p_start = alt((
985 tag(s.syntax.block_start),
986 tag(s.syntax.comment_start),
987 tag(s.syntax.expr_start),
988 ));
989
990 let (i, content) = opt(recognize(skip_till(candidate_finder, p_start)))(i)?;
991 let (i, content) = match content {
992 Some("") => {
993 // {block,comment,expr}_start follows immediately.
994 return fail(i);
995 }
996 Some(content) => (i, content),
997 None => ("", i), // there is no {block,comment,expr}_start: take everything
998 };
999 Ok((i, WithSpan::new(Self::split_ws_parts(content), start)))
1000 }
1001
1002 pub(crate) fn split_ws_parts(s: &'a str) -> Self {
1003 let trimmed_start = s.trim_start_matches(is_ws);
1004 let len_start = s.len() - trimmed_start.len();
1005 let trimmed = trimmed_start.trim_end_matches(is_ws);
1006 Self {
1007 lws: &s[..len_start],
1008 val: trimmed,
1009 rws: &trimmed_start[trimmed.len()..],
1010 }
1011 }
1012}
1013
1014#[derive(Debug, PartialEq)]
1015pub struct Raw<'a> {
1016 pub ws1: Ws,
1017 pub lit: Lit<'a>,
1018 pub ws2: Ws,
1019}
1020
1021impl<'a> Raw<'a> {
1022 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
1023 let start = i;
1024 let endraw = tuple((
1025 |i| s.tag_block_start(i),
1026 opt(Whitespace::parse),
1027 ws(keyword("endraw")), // sic: ignore `{% end %}` in raw blocks
1028 opt(Whitespace::parse),
1029 peek(|i| s.tag_block_end(i)),
1030 ));
1031
1032 let mut p = tuple((
1033 opt(Whitespace::parse),
1034 ws(keyword("raw")),
1035 cut_node(
1036 Some("raw"),
1037 tuple((
1038 opt(Whitespace::parse),
1039 |i| s.tag_block_end(i),
1040 consumed(skip_till(Splitter1::new(s.syntax.block_start), endraw)),
1041 )),
1042 ),
1043 ));
1044
1045 let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?;
1046 let lit = Lit::split_ws_parts(contents);
1047 let ws1 = Ws(pws1, nws1);
1048 let ws2 = Ws(pws2, nws2);
1049 Ok((i, WithSpan::new(Self { ws1, lit, ws2 }, start)))
1050 }
1051}
1052
1053#[derive(Debug, PartialEq)]
1054pub struct Let<'a> {
1055 pub ws: Ws,
1056 pub var: Target<'a>,
1057 pub val: Option<WithSpan<'a, Expr<'a>>>,
1058}
1059
1060impl<'a> Let<'a> {
1061 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
1062 let start = i;
1063 let mut p = tuple((
1064 opt(Whitespace::parse),
1065 ws(alt((keyword("let"), keyword("set")))),
1066 cut_node(
1067 Some("let"),
1068 tuple((
1069 ws(|i| Target::parse(i, s)),
1070 opt(preceded(
1071 ws(char('=')),
1072 ws(|i| Expr::parse(i, s.level.get())),
1073 )),
1074 opt(Whitespace::parse),
1075 )),
1076 ),
1077 ));
1078 let (i, (pws, _, (var, val, nws))) = p(i)?;
1079
1080 Ok((
1081 i,
1082 WithSpan::new(
1083 Let {
1084 ws: Ws(pws, nws),
1085 var,
1086 val,
1087 },
1088 start,
1089 ),
1090 ))
1091 }
1092}
1093
1094#[derive(Debug, PartialEq)]
1095pub struct If<'a> {
1096 pub ws: Ws,
1097 pub branches: Vec<WithSpan<'a, Cond<'a>>>,
1098}
1099
1100impl<'a> If<'a> {
1101 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
1102 let start = i;
1103 let mut p = tuple((
1104 opt(Whitespace::parse),
1105 |i| CondTest::parse(i, s),
1106 cut_node(
1107 Some("if"),
1108 tuple((
1109 opt(Whitespace::parse),
1110 |i| s.tag_block_end(i),
1111 cut_node(
1112 Some("if"),
1113 tuple((
1114 |i| Node::many(i, s),
1115 many0(|i| Cond::parse(i, s)),
1116 cut_node(
1117 Some("if"),
1118 tuple((
1119 |i| check_block_start(i, start, s, "if", "endif"),
1120 opt(Whitespace::parse),
1121 end_node("if", "endif"),
1122 opt(Whitespace::parse),
1123 )),
1124 ),
1125 )),
1126 ),
1127 )),
1128 ),
1129 ));
1130
1131 let (i, (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2))))) = p(i)?;
1132 let mut branches = vec![WithSpan::new(
1133 Cond {
1134 ws: Ws(pws1, nws1),
1135 cond: Some(cond),
1136 nodes,
1137 },
1138 start,
1139 )];
1140 branches.extend(elifs);
1141
1142 Ok((
1143 i,
1144 WithSpan::new(
1145 Self {
1146 ws: Ws(pws2, nws2),
1147 branches,
1148 },
1149 start,
1150 ),
1151 ))
1152 }
1153}
1154
1155#[derive(Debug, PartialEq)]
1156pub struct Include<'a> {
1157 pub ws: Ws,
1158 pub path: &'a str,
1159}
1160
1161impl<'a> Include<'a> {
1162 fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> {
1163 let start: &'a str = i;
1164 let mut p: impl FnMut(&str) -> Result<…, …> = tuple((
1165 opt(Whitespace::parse),
1166 ws(inner:keyword("include")),
1167 cut_node(
1168 kind:Some("include"),
1169 inner:pair(first:ws(str_lit_without_prefix), second:opt(Whitespace::parse)),
1170 ),
1171 ));
1172 let (i: &str, (pws: Option, _, (path: &str, nws: Option))) = p(i)?;
1173 Ok((
1174 i,
1175 WithSpan::new(
1176 Self {
1177 ws: Ws(pws, nws),
1178 path,
1179 },
1180 span:start,
1181 ),
1182 ))
1183 }
1184}
1185
1186#[derive(Debug, PartialEq)]
1187pub struct Extends<'a> {
1188 pub path: &'a str,
1189}
1190
1191impl<'a> Extends<'a> {
1192 fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> {
1193 let start: &'a str = i;
1194
1195 let (i: &str, (pws: Option, _, (path: &str, nws: Option))) = tuple((
1196 opt(Whitespace::parse),
1197 ws(inner:keyword("extends")),
1198 cut_node(
1199 kind:Some("extends"),
1200 inner:pair(first:ws(str_lit_without_prefix), second:opt(Whitespace::parse)),
1201 ),
1202 ))(i)?;
1203 match (pws, nws) {
1204 (None, None) => Ok((i, WithSpan::new(Self { path }, span:start))),
1205 (_, _) => Err(nom::Err::Failure(ErrorContext::new(
1206 message:"whitespace control is not allowed on `extends`",
1207 input:start,
1208 ))),
1209 }
1210 }
1211}
1212
1213#[derive(Debug, PartialEq)]
1214pub struct Comment<'a> {
1215 pub ws: Ws,
1216 pub content: &'a str,
1217}
1218
1219impl<'a> Comment<'a> {
1220 fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
1221 #[derive(Debug, Clone, Copy)]
1222 enum Tag {
1223 Open,
1224 Close,
1225 }
1226
1227 fn tag<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Tag> {
1228 alt((
1229 value(Tag::Open, |i| s.tag_comment_start(i)),
1230 value(Tag::Close, |i| s.tag_comment_end(i)),
1231 ))(i)
1232 }
1233
1234 fn content<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> {
1235 let mut depth = 0usize;
1236 let start = i;
1237 loop {
1238 let splitter = Splitter2::new(s.syntax.comment_start, s.syntax.comment_end);
1239 let (k, tag) = opt(skip_till(splitter, |i| tag(i, s)))(i)?;
1240 let Some((j, tag)) = tag else {
1241 return Err(
1242 ErrorContext::unclosed("comment", s.syntax.comment_end, start).into(),
1243 );
1244 };
1245 match tag {
1246 Tag::Open => match depth.checked_add(1) {
1247 Some(new_depth) => depth = new_depth,
1248 None => {
1249 return Err(nom::Err::Failure(ErrorContext::new(
1250 "too deeply nested comments",
1251 start,
1252 )));
1253 }
1254 },
1255 Tag::Close => match depth.checked_sub(1) {
1256 Some(new_depth) => depth = new_depth,
1257 None => return Ok((j, &start[..start.len() - k.len()])),
1258 },
1259 }
1260 i = j;
1261 }
1262 }
1263
1264 let start = i;
1265 let (i, content) = preceded(
1266 |i| s.tag_comment_start(i),
1267 cut_node(Some("comment"), |i| content(i, s)),
1268 )(i)?;
1269
1270 let mut ws = Ws(None, None);
1271 if content.len() == 1 && matches!(content, "-" | "+" | "~") {
1272 return Err(nom::Err::Failure(ErrorContext::new(
1273 format!(
1274 "ambiguous whitespace stripping\n\
1275 use `{}{content} {content}{}` to apply the same whitespace stripping on both \
1276 sides",
1277 s.syntax.comment_start, s.syntax.comment_end,
1278 ),
1279 start,
1280 )));
1281 } else if content.len() >= 2 {
1282 ws.0 = Whitespace::parse_char(content.chars().next().unwrap_or_default());
1283 ws.1 = Whitespace::parse_char(content.chars().next_back().unwrap_or_default());
1284 }
1285
1286 Ok((i, WithSpan::new(Self { ws, content }, start)))
1287 }
1288}
1289
1290/// First field is "minus/plus sign was used on the left part of the item".
1291///
1292/// Second field is "minus/plus sign was used on the right part of the item".
1293#[derive(Clone, Copy, Debug, PartialEq)]
1294pub struct Ws(pub Option<Whitespace>, pub Option<Whitespace>);
1295
1296fn end_node<'a, 'g: 'a>(
1297 node: &'g str,
1298 expected: &'g str,
1299) -> impl Fn(&'a str) -> ParseResult<'a> + 'g {
1300 move |start: &'a str| {
1301 let (i: &str, actual: &str) = ws(inner:identifier)(start)?;
1302 if actual == expected {
1303 Ok((i, actual))
1304 } else if actual.starts_with("end") {
1305 Err(nom::Err::Failure(ErrorContext::new(
1306 message:format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"),
1307 input:start,
1308 )))
1309 } else {
1310 fail(start)
1311 }
1312 }
1313}
1314
1315#[doc(hidden)]
1316pub const MAX_KW_LEN: usize = 8;
1317const MAX_REPL_LEN: usize = MAX_KW_LEN + 2;
1318#[doc(hidden)]
1319pub const KWS: &[&[[u8; MAX_REPL_LEN]]] = {
1320 // FIXME: Replace `u8` with `[core:ascii::Char; MAX_REPL_LEN]` once
1321 // <https://github.com/rust-lang/rust/issues/110998> is stable.
1322
1323 const KW2: &[[u8; MAX_REPL_LEN]] = &[
1324 *b"r#as______",
1325 *b"r#do______",
1326 *b"r#fn______",
1327 *b"r#if______",
1328 *b"r#in______",
1329 ];
1330 const KW3: &[[u8; MAX_REPL_LEN]] = &[
1331 *b"r#box_____",
1332 *b"r#dyn_____",
1333 *b"r#for_____",
1334 *b"r#let_____",
1335 *b"r#mod_____",
1336 *b"r#mut_____",
1337 *b"r#pub_____",
1338 *b"r#ref_____",
1339 *b"r#try_____",
1340 *b"r#use_____",
1341 ];
1342 const KW4: &[[u8; MAX_REPL_LEN]] = &[
1343 *b"r#else____",
1344 *b"r#enum____",
1345 *b"r#impl____",
1346 *b"r#move____",
1347 *b"r#priv____",
1348 *b"r#true____",
1349 *b"r#type____",
1350 ];
1351 const KW5: &[[u8; MAX_REPL_LEN]] = &[
1352 *b"r#async___",
1353 *b"r#await___",
1354 *b"r#break___",
1355 *b"r#const___",
1356 *b"r#crate___",
1357 *b"r#false___",
1358 *b"r#final___",
1359 *b"r#macro___",
1360 *b"r#match___",
1361 *b"r#trait___",
1362 *b"r#where___",
1363 *b"r#while___",
1364 *b"r#yield___",
1365 ];
1366 const KW6: &[[u8; MAX_REPL_LEN]] = &[
1367 *b"r#become__",
1368 *b"r#extern__",
1369 *b"r#return__",
1370 *b"r#static__",
1371 *b"r#struct__",
1372 *b"r#typeof__",
1373 *b"r#unsafe__",
1374 ];
1375 const KW7: &[[u8; MAX_REPL_LEN]] = &[*b"r#unsized_", *b"r#virtual_"];
1376 const KW8: &[[u8; MAX_REPL_LEN]] = &[*b"r#abstract", *b"r#continue", *b"r#override"];
1377
1378 &[&[], &[], KW2, KW3, KW4, KW5, KW6, KW7, KW8]
1379};
1380
1381// These ones are only used in the parser, hence why they're private.
1382const KWS_EXTRA: &[&[[u8; MAX_REPL_LEN]]] = {
1383 const KW4: &[[u8; MAX_REPL_LEN]] = &[*b"r#loop____", *b"r#self____", *b"r#Self____"];
1384 const KW5: &[[u8; MAX_REPL_LEN]] = &[*b"r#super___", *b"r#union___"];
1385
1386 &[&[], &[], &[], &[], KW4, KW5, &[], &[], &[]]
1387};
1388
1389fn is_rust_keyword(ident: &str) -> bool {
1390 fn is_rust_keyword_inner(
1391 kws: &[&[[u8; MAX_REPL_LEN]]],
1392 padded_ident: &[u8; MAX_KW_LEN],
1393 ident_len: usize,
1394 ) -> bool {
1395 // Since the individual buckets are quite short, a linear search is faster than a binary search.
1396 kwsIter<'_, [u8; 10]>[ident_len]
1397 .iter()
1398 .any(|&probe: [u8; 10]| padded_ident == &probe[2..])
1399 }
1400 if ident.len() > MAX_KW_LEN {
1401 return false;
1402 }
1403 let ident_len: usize = ident.len();
1404
1405 let mut padded_ident: [u8; 8] = [b'_'; MAX_KW_LEN];
1406 padded_ident[..ident.len()].copy_from_slice(src:ident.as_bytes());
1407
1408 is_rust_keyword_inner(KWS, &padded_ident, ident_len)
1409 || is_rust_keyword_inner(KWS_EXTRA, &padded_ident, ident_len)
1410}
1411
1412#[cfg(test)]
1413mod kws_tests {
1414 use super::{KWS, KWS_EXTRA, MAX_REPL_LEN, is_rust_keyword};
1415
1416 fn ensure_utf8_inner(entry: &[&[[u8; MAX_REPL_LEN]]]) {
1417 for kws in entry {
1418 for kw in *kws {
1419 assert!(std::str::from_utf8(kw).is_ok(), "not UTF-8: {kw:?}");
1420 }
1421 }
1422 }
1423
1424 // Ensure that all strings are UTF-8, because we use `from_utf8_unchecked()` further down.
1425 #[test]
1426 fn ensure_utf8() {
1427 assert_eq!(KWS.len(), KWS_EXTRA.len());
1428 ensure_utf8_inner(KWS);
1429 ensure_utf8_inner(KWS_EXTRA);
1430 }
1431
1432 #[test]
1433 fn test_is_rust_keyword() {
1434 assert!(is_rust_keyword("super"));
1435 assert!(is_rust_keyword("become"));
1436 assert!(!is_rust_keyword("supeeeer"));
1437 assert!(!is_rust_keyword("sur"));
1438 }
1439}
1440