tera_introspection/parser/
mod.rs

1use std::collections::HashMap;
2
3use lazy_static::lazy_static;
4use pest::iterators::Pair;
5// use pest::pratt_parser::{Assoc, Op};
6use crate::errors::{Error, Result as TeraResult};
7use pest::prec_climber::{Assoc, Operator, PrecClimber};
8use pest::Parser;
9use pest_derive::Parser;
10
11// This include forces recompiling this source file if the grammar file changes.
12// Uncomment it when doing changes to the .pest file
13const _GRAMMAR: &str = include_str!("tera.pest");
14
15#[derive(Parser)]
16#[grammar = "parser/tera.pest"]
17pub struct TeraParser;
18
19/// The AST of Tera
20pub mod ast;
21mod whitespace;
22
23#[cfg(test)]
24mod tests;
25
26use self::ast::*;
27pub use self::whitespace::remove_whitespace;
28
29lazy_static! {
30    static ref MATH_CLIMBER: PrecClimber<Rule> = PrecClimber::new(vec![
31        // +, -
32        Operator::new(Rule::op_plus, Assoc::Left) | Operator::new(Rule::op_minus, Assoc::Left),
33        // *, /, %
34        Operator::new(Rule::op_times, Assoc::Left) |
35        Operator::new(Rule::op_slash, Assoc::Left) |
36        Operator::new(Rule::op_modulo, Assoc::Left),
37    ]);
38    static ref COMPARISON_EXPR_CLIMBER: PrecClimber<Rule> = PrecClimber::new(vec![
39        // <, <=, >, >=, ==, !=
40        Operator::new(Rule::op_lt, Assoc::Left) | Operator::new(Rule::op_lte, Assoc::Left)
41        | Operator::new(Rule::op_gt, Assoc::Left) | Operator::new(Rule::op_gte, Assoc::Left)
42        | Operator::new(Rule::op_eq, Assoc::Left) | Operator::new(Rule::op_ineq, Assoc::Left),
43    ]);
44    static ref LOGIC_EXPR_CLIMBER: PrecClimber<Rule> = PrecClimber::new(vec![
45        Operator::new(Rule::op_or, Assoc::Left),
46        Operator::new(Rule::op_and, Assoc::Left),
47    ]);
48}
49
50/// Strings are delimited by double quotes, single quotes and backticks
51/// We need to remove those before putting them in the AST
52fn replace_string_markers(input: &str) -> String {
53    match input.chars().next().unwrap() {
54        '"' => input.replace('"', ""),
55        '\'' => input.replace('\'', ""),
56        '`' => input.replace('`', ""),
57        _ => unreachable!("How did you even get there"),
58    }
59}
60
61fn parse_kwarg(pair: Pair<Rule>) -> TeraResult<(String, Expr)> {
62    let mut name = None;
63    let mut val = None;
64
65    for p in pair.into_inner() {
66        match p.as_rule() {
67            Rule::ident => name = Some(p.as_span().as_str().to_string()),
68            Rule::logic_expr => val = Some(parse_logic_expr(p)?),
69            Rule::array_filter => val = Some(parse_array_with_filters(p)?),
70            _ => unreachable!("{:?} not supposed to get there (parse_kwarg)!", p.as_rule()),
71        };
72    }
73
74    Ok((name.unwrap(), val.unwrap()))
75}
76
77fn parse_fn_call(pair: Pair<Rule>) -> TeraResult<FunctionCall> {
78    let mut name = None;
79    let mut args = HashMap::new();
80
81    for p in pair.into_inner() {
82        match p.as_rule() {
83            Rule::ident => name = Some(p.as_span().as_str().to_string()),
84            Rule::kwarg => {
85                let (name, val) = parse_kwarg(p)?;
86                args.insert(name, val);
87            }
88            _ => unreachable!(
89                "{:?} not supposed to get there (parse_fn_call)!",
90                p.as_rule()
91            ),
92        };
93    }
94
95    Ok(FunctionCall {
96        name: name.unwrap(),
97        args,
98    })
99}
100
101fn parse_filter(pair: Pair<Rule>) -> TeraResult<FunctionCall> {
102    let mut name = None;
103    let mut args = HashMap::new();
104    for p in pair.into_inner() {
105        match p.as_rule() {
106            Rule::ident => name = Some(p.as_span().as_str().to_string()),
107            Rule::kwarg => {
108                let (name, val) = parse_kwarg(p)?;
109                args.insert(name, val);
110            }
111            Rule::fn_call => {
112                return parse_fn_call(p);
113            }
114            _ => unreachable!(
115                "{:?} not supposed to get there (parse_filter)!",
116                p.as_rule()
117            ),
118        };
119    }
120
121    Ok(FunctionCall {
122        name: name.unwrap(),
123        args,
124    })
125}
126
127fn parse_test_call(pair: Pair<Rule>) -> TeraResult<(String, Vec<Expr>)> {
128    let mut name = None;
129    let mut args = vec![];
130
131    for p in pair.into_inner() {
132        match p.as_rule() {
133            Rule::ident => name = Some(p.as_span().as_str().to_string()),
134            Rule::test_arg =>
135            // iterate on the test_arg rule
136            {
137                for p2 in p.into_inner() {
138                    match p2.as_rule() {
139                        Rule::logic_expr => {
140                            args.push(parse_logic_expr(p2)?);
141                        }
142                        Rule::array => {
143                            args.push(Expr::new(parse_array(p2)?));
144                        }
145                        _ => unreachable!("Invalid arg type for test {:?}", p2.as_rule()),
146                    }
147                }
148            }
149            _ => unreachable!(
150                "{:?} not supposed to get there (parse_test_call)!",
151                p.as_rule()
152            ),
153        };
154    }
155
156    Ok((name.unwrap(), args))
157}
158
159fn parse_test(pair: Pair<Rule>) -> TeraResult<Test> {
160    let mut ident = None;
161    let mut name = None;
162    let mut args = vec![];
163
164    for p in pair.into_inner() {
165        match p.as_rule() {
166            Rule::dotted_ident => ident = Some(p.as_str().to_string()),
167            Rule::test_call => {
168                let (_name, _args) = parse_test_call(p)?;
169                name = Some(_name);
170                args = _args;
171            }
172            _ => unreachable!("{:?} not supposed to get there (parse_ident)!", p.as_rule()),
173        };
174    }
175
176    Ok(Test {
177        ident: ident.unwrap(),
178        negated: false,
179        name: name.unwrap(),
180        args,
181    })
182}
183
184fn parse_string_concat(pair: Pair<Rule>) -> TeraResult<ExprVal> {
185    let mut values = vec![];
186    let mut current_str = String::new();
187
188    // Can we fold it into a simple string?
189    for p in pair.into_inner() {
190        match p.as_rule() {
191            Rule::string => {
192                current_str.push_str(&replace_string_markers(p.as_str()));
193            }
194            Rule::int => {
195                if !current_str.is_empty() {
196                    values.push(ExprVal::String(current_str));
197                    current_str = String::new();
198                }
199                values.push(ExprVal::Int(p.as_str().parse().map_err(|_| {
200                    Error::msg(format!("Integer out of bounds: `{}`", p.as_str()))
201                })?));
202            }
203            Rule::float => {
204                if !current_str.is_empty() {
205                    values.push(ExprVal::String(current_str));
206                    current_str = String::new();
207                }
208                values.push(ExprVal::Float(p.as_str().parse().map_err(|_| {
209                    Error::msg(format!("Float out of bounds: `{}`", p.as_str()))
210                })?));
211            }
212            Rule::dotted_square_bracket_ident => {
213                if !current_str.is_empty() {
214                    values.push(ExprVal::String(current_str));
215                    current_str = String::new();
216                }
217                values.push(ExprVal::Ident(p.as_str().to_string()))
218            }
219            Rule::fn_call => {
220                if !current_str.is_empty() {
221                    values.push(ExprVal::String(current_str));
222                    current_str = String::new();
223                }
224                values.push(ExprVal::FunctionCall(parse_fn_call(p)?))
225            }
226            _ => unreachable!("Got {:?} in parse_string_concat", p),
227        };
228    }
229
230    if values.is_empty() {
231        // we only got a string
232        return Ok(ExprVal::String(current_str));
233    }
234
235    if !current_str.is_empty() {
236        values.push(ExprVal::String(current_str));
237    }
238
239    Ok(ExprVal::StringConcat(StringConcat { values }))
240}
241
242fn parse_basic_expression(pair: Pair<Rule>) -> TeraResult<ExprVal> {
243    let primary = parse_basic_expression;
244
245    let infix = |lhs: TeraResult<ExprVal>, op: Pair<Rule>, rhs: TeraResult<ExprVal>| {
246        Ok(ExprVal::Math(MathExpr {
247            lhs: Box::new(Expr::new(lhs?)),
248            operator: match op.as_rule() {
249                Rule::op_plus => MathOperator::Add,
250                Rule::op_minus => MathOperator::Sub,
251                Rule::op_times => MathOperator::Mul,
252                Rule::op_slash => MathOperator::Div,
253                Rule::op_modulo => MathOperator::Modulo,
254                _ => unreachable!(),
255            },
256            rhs: Box::new(Expr::new(rhs?)),
257        }))
258    };
259
260    let expr = match pair.as_rule() {
261        Rule::int => ExprVal::Int(
262            pair.as_str()
263                .parse()
264                .map_err(|_| Error::msg(format!("Integer out of bounds: `{}`", pair.as_str())))?,
265        ),
266        Rule::float => ExprVal::Float(
267            pair.as_str()
268                .parse()
269                .map_err(|_| Error::msg(format!("Float out of bounds: `{}`", pair.as_str())))?,
270        ),
271        Rule::boolean => match pair.as_str() {
272            "true" => ExprVal::Bool(true),
273            "True" => ExprVal::Bool(true),
274            "false" => ExprVal::Bool(false),
275            "False" => ExprVal::Bool(false),
276            _ => unreachable!(),
277        },
278        Rule::test => ExprVal::Test(parse_test(pair)?),
279        Rule::test_not => {
280            let mut test = parse_test(pair)?;
281            test.negated = true;
282            ExprVal::Test(test)
283        }
284        Rule::fn_call => ExprVal::FunctionCall(parse_fn_call(pair)?),
285        Rule::macro_call => ExprVal::MacroCall(parse_macro_call(pair)?),
286        Rule::dotted_square_bracket_ident => ExprVal::Ident(pair.as_str().to_string()),
287        Rule::basic_expr => MATH_CLIMBER.climb(pair.into_inner(), primary, infix)?,
288        _ => unreachable!(
289            "Got {:?} in parse_basic_expression: {}",
290            pair.as_rule(),
291            pair.as_str()
292        ),
293    };
294    Ok(expr)
295}
296
297/// A basic expression with optional filters
298fn parse_basic_expr_with_filters(pair: Pair<Rule>) -> TeraResult<Expr> {
299    let mut expr_val = None;
300    let mut filters = vec![];
301
302    for p in pair.into_inner() {
303        match p.as_rule() {
304            Rule::basic_expr => expr_val = Some(parse_basic_expression(p)?),
305            Rule::filter => filters.push(parse_filter(p)?),
306            _ => unreachable!("Got {:?}", p),
307        };
308    }
309
310    Ok(Expr {
311        val: expr_val.unwrap(),
312        negated: false,
313        filters,
314    })
315}
316
317/// A string expression with optional filters
318fn parse_string_expr_with_filters(pair: Pair<Rule>) -> TeraResult<Expr> {
319    let mut expr_val = None;
320    let mut filters = vec![];
321
322    for p in pair.into_inner() {
323        match p.as_rule() {
324            Rule::string => expr_val = Some(ExprVal::String(replace_string_markers(p.as_str()))),
325            Rule::string_concat => expr_val = Some(parse_string_concat(p)?),
326            Rule::filter => filters.push(parse_filter(p)?),
327            _ => unreachable!("Got {:?}", p),
328        };
329    }
330
331    Ok(Expr {
332        val: expr_val.unwrap(),
333        negated: false,
334        filters,
335    })
336}
337
338/// An array with optional filters
339fn parse_array_with_filters(pair: Pair<Rule>) -> TeraResult<Expr> {
340    let mut array = None;
341    let mut filters = vec![];
342
343    for p in pair.into_inner() {
344        match p.as_rule() {
345            Rule::array => array = Some(parse_array(p)?),
346            Rule::filter => filters.push(parse_filter(p)?),
347            _ => unreachable!("Got {:?}", p),
348        };
349    }
350
351    Ok(Expr {
352        val: array.unwrap(),
353        negated: false,
354        filters,
355    })
356}
357
358fn parse_in_condition_container(pair: Pair<Rule>) -> TeraResult<Expr> {
359    let mut expr = None;
360    for p in pair.into_inner() {
361        match p.as_rule() {
362            Rule::array_filter => expr = Some(parse_array_with_filters(p)?),
363            Rule::dotted_square_bracket_ident => {
364                expr = Some(Expr::new(ExprVal::Ident(p.as_str().to_string())))
365            }
366            Rule::string_expr_filter => expr = Some(parse_string_expr_with_filters(p)?),
367            _ => unreachable!("Got {:?} in parse_in_condition_container", p),
368        };
369    }
370    Ok(expr.unwrap())
371}
372
373fn parse_in_condition(pair: Pair<Rule>) -> TeraResult<Expr> {
374    let mut lhs = None;
375    let mut rhs = None;
376    let mut negated = false;
377
378    for p in pair.into_inner() {
379        match p.as_rule() {
380            // lhs
381            Rule::string_expr_filter => lhs = Some(parse_string_expr_with_filters(p)?),
382            Rule::basic_expr_filter => lhs = Some(parse_basic_expr_with_filters(p)?),
383            // rhs
384            Rule::in_cond_container => rhs = Some(parse_in_condition_container(p)?),
385            Rule::op_not => negated = true,
386            _ => unreachable!("Got {:?} in parse_in_condition", p),
387        };
388    }
389
390    Ok(Expr::new(ExprVal::In(In {
391        lhs: Box::new(lhs.unwrap()),
392        rhs: Box::new(rhs.unwrap()),
393        negated,
394    })))
395}
396
397/// A basic expression with optional filters with prece
398fn parse_comparison_val(pair: Pair<Rule>) -> TeraResult<Expr> {
399    let primary = parse_comparison_val;
400
401    let infix = |lhs: TeraResult<Expr>, op: Pair<Rule>, rhs: TeraResult<Expr>| {
402        Ok(Expr::new(ExprVal::Math(MathExpr {
403            lhs: Box::new(lhs?),
404            operator: match op.as_rule() {
405                Rule::op_plus => MathOperator::Add,
406                Rule::op_minus => MathOperator::Sub,
407                Rule::op_times => MathOperator::Mul,
408                Rule::op_slash => MathOperator::Div,
409                Rule::op_modulo => MathOperator::Modulo,
410                _ => unreachable!(),
411            },
412            rhs: Box::new(rhs?),
413        })))
414    };
415
416    let expr = match pair.as_rule() {
417        Rule::basic_expr_filter => parse_basic_expr_with_filters(pair)?,
418        Rule::comparison_val => MATH_CLIMBER.climb(pair.into_inner(), primary, infix)?,
419        _ => unreachable!("Got {:?} in parse_comparison_val", pair.as_rule()),
420    };
421    Ok(expr)
422}
423
424fn parse_comparison_expression(pair: Pair<Rule>) -> TeraResult<Expr> {
425    let primary = parse_comparison_expression;
426
427    let infix = |lhs: TeraResult<Expr>, op: Pair<Rule>, rhs: TeraResult<Expr>| {
428        Ok(Expr::new(ExprVal::Logic(LogicExpr {
429            lhs: Box::new(lhs?),
430            operator: match op.as_rule() {
431                Rule::op_lt => LogicOperator::Lt,
432                Rule::op_lte => LogicOperator::Lte,
433                Rule::op_gt => LogicOperator::Gt,
434                Rule::op_gte => LogicOperator::Gte,
435                Rule::op_ineq => LogicOperator::NotEq,
436                Rule::op_eq => LogicOperator::Eq,
437                _ => unreachable!(),
438            },
439            rhs: Box::new(rhs?),
440        })))
441    };
442
443    let expr = match pair.as_rule() {
444        Rule::comparison_val => parse_comparison_val(pair)?,
445        Rule::string_expr_filter => parse_string_expr_with_filters(pair)?,
446        Rule::comparison_expr => {
447            COMPARISON_EXPR_CLIMBER.climb(pair.into_inner(), primary, infix)?
448        }
449        _ => unreachable!("Got {:?} in parse_comparison_expression", pair.as_rule()),
450    };
451    Ok(expr)
452}
453
454/// An expression that can be negated
455fn parse_logic_val(pair: Pair<Rule>) -> TeraResult<Expr> {
456    let mut negated = false;
457    let mut expr = None;
458
459    for p in pair.into_inner() {
460        match p.as_rule() {
461            Rule::op_not => negated = true,
462            Rule::in_cond => expr = Some(parse_in_condition(p)?),
463            Rule::comparison_expr => expr = Some(parse_comparison_expression(p)?),
464            Rule::string_expr_filter => expr = Some(parse_string_expr_with_filters(p)?),
465            _ => unreachable!(),
466        };
467    }
468
469    let mut e = expr.unwrap();
470    e.negated = negated;
471    Ok(e)
472}
473
474fn parse_logic_expr(pair: Pair<Rule>) -> TeraResult<Expr> {
475    let primary = parse_logic_expr;
476
477    let infix = |lhs: TeraResult<Expr>, op: Pair<Rule>, rhs: TeraResult<Expr>| match op.as_rule() {
478        Rule::op_or => Ok(Expr::new(ExprVal::Logic(LogicExpr {
479            lhs: Box::new(lhs?),
480            operator: LogicOperator::Or,
481            rhs: Box::new(rhs?),
482        }))),
483        Rule::op_and => Ok(Expr::new(ExprVal::Logic(LogicExpr {
484            lhs: Box::new(lhs?),
485            operator: LogicOperator::And,
486            rhs: Box::new(rhs?),
487        }))),
488        _ => unreachable!(
489            "{:?} not supposed to get there (infix of logic_expression)!",
490            op.as_rule()
491        ),
492    };
493
494    let expr = match pair.as_rule() {
495        Rule::logic_val => parse_logic_val(pair)?,
496        Rule::logic_expr => LOGIC_EXPR_CLIMBER.climb(pair.into_inner(), primary, infix)?,
497        _ => unreachable!("Got {:?} in parse_logic_expr", pair.as_rule()),
498    };
499    Ok(expr)
500}
501
502fn parse_array(pair: Pair<Rule>) -> TeraResult<ExprVal> {
503    let mut vals = vec![];
504
505    for p in pair.into_inner() {
506        match p.as_rule() {
507            Rule::logic_val => {
508                vals.push(parse_logic_val(p)?);
509            }
510            _ => unreachable!("Got {:?} in parse_array", p.as_rule()),
511        }
512    }
513
514    Ok(ExprVal::Array(vals))
515}
516
517fn parse_string_array(pair: Pair<Rule>) -> Vec<String> {
518    let mut vals = vec![];
519
520    for p in pair.into_inner() {
521        match p.as_rule() {
522            Rule::string => {
523                vals.push(replace_string_markers(p.as_span().as_str()));
524            }
525            _ => unreachable!("Got {:?} in parse_string_array", p.as_rule()),
526        }
527    }
528
529    vals
530}
531
532fn parse_macro_call(pair: Pair<Rule>) -> TeraResult<MacroCall> {
533    let mut namespace = None;
534    let mut name = None;
535    let mut args = HashMap::new();
536
537    for p in pair.into_inner() {
538        match p.as_rule() {
539            Rule::ident => {
540                // namespace comes first
541                if namespace.is_none() {
542                    namespace = Some(p.as_span().as_str().to_string());
543                } else {
544                    name = Some(p.as_span().as_str().to_string());
545                }
546            }
547            Rule::kwarg => {
548                let (key, val) = parse_kwarg(p)?;
549                args.insert(key, val);
550            }
551            _ => unreachable!("Got {:?} in parse_macro_call", p.as_rule()),
552        }
553    }
554
555    Ok(MacroCall {
556        namespace: namespace.unwrap(),
557        name: name.unwrap(),
558        args,
559    })
560}
561
562fn parse_variable_tag(pair: Pair<Rule>) -> TeraResult<Node> {
563    let mut ws = WS::default();
564    let mut expr = None;
565
566    for p in pair.into_inner() {
567        match p.as_rule() {
568            Rule::variable_start => {
569                ws.left = p.as_span().as_str() == "{{-";
570            }
571            Rule::variable_end => {
572                ws.right = p.as_span().as_str() == "-}}";
573            }
574            Rule::logic_expr => expr = Some(parse_logic_expr(p)?),
575            Rule::array_filter => expr = Some(parse_array_with_filters(p)?),
576            _ => unreachable!("unexpected {:?} rule in parse_variable_tag", p.as_rule()),
577        }
578    }
579    Ok(Node::VariableBlock(ws, expr.unwrap()))
580}
581
582fn parse_import_macro(pair: Pair<Rule>) -> Node {
583    let mut ws = WS::default();
584    let mut file = None;
585    let mut ident = None;
586
587    for p in pair.into_inner() {
588        match p.as_rule() {
589            Rule::tag_start => {
590                ws.left = p.as_span().as_str() == "{%-";
591            }
592            Rule::string => file = Some(replace_string_markers(p.as_span().as_str())),
593            Rule::ident => ident = Some(p.as_span().as_str().to_string()),
594            Rule::tag_end => {
595                ws.right = p.as_span().as_str() == "-%}";
596            }
597            _ => unreachable!(),
598        };
599    }
600
601    Node::ImportMacro(ws, file.unwrap(), ident.unwrap())
602}
603
604fn parse_extends(pair: Pair<Rule>) -> Node {
605    let mut ws = WS::default();
606    let mut file = None;
607
608    for p in pair.into_inner() {
609        match p.as_rule() {
610            Rule::tag_start => {
611                ws.left = p.as_span().as_str() == "{%-";
612            }
613            Rule::string => file = Some(replace_string_markers(p.as_span().as_str())),
614            Rule::tag_end => {
615                ws.right = p.as_span().as_str() == "-%}";
616            }
617            _ => unreachable!(),
618        };
619    }
620
621    Node::Extends(ws, file.unwrap())
622}
623
624fn parse_include(pair: Pair<Rule>) -> Node {
625    let mut ws = WS::default();
626    let mut files = vec![];
627    let mut ignore_missing = false;
628
629    for p in pair.into_inner() {
630        match p.as_rule() {
631            Rule::tag_start => {
632                ws.left = p.as_span().as_str() == "{%-";
633            }
634            Rule::string => {
635                files.push(replace_string_markers(p.as_span().as_str()));
636            }
637            Rule::string_array => files.extend(parse_string_array(p)),
638            Rule::ignore_missing => ignore_missing = true,
639            Rule::tag_end => {
640                ws.right = p.as_span().as_str() == "-%}";
641            }
642            _ => unreachable!(),
643        };
644    }
645
646    Node::Include(ws, files, ignore_missing)
647}
648
649fn parse_set_tag(pair: Pair<Rule>, global: bool) -> TeraResult<Node> {
650    let mut ws = WS::default();
651    let mut key = None;
652    let mut expr = None;
653
654    for p in pair.into_inner() {
655        match p.as_rule() {
656            Rule::tag_start => {
657                ws.left = p.as_span().as_str() == "{%-";
658            }
659            Rule::tag_end => {
660                ws.right = p.as_span().as_str() == "-%}";
661            }
662            Rule::ident => key = Some(p.as_str().to_string()),
663            Rule::logic_expr => expr = Some(parse_logic_expr(p)?),
664            Rule::array_filter => expr = Some(parse_array_with_filters(p)?),
665            _ => unreachable!("unexpected {:?} rule in parse_set_tag", p.as_rule()),
666        }
667    }
668
669    Ok(Node::Set(
670        ws,
671        Set {
672            key: key.unwrap(),
673            value: expr.unwrap(),
674            global,
675        },
676    ))
677}
678
679fn parse_raw_tag(pair: Pair<Rule>) -> Node {
680    let mut start_ws = WS::default();
681    let mut end_ws = WS::default();
682    let mut text = None;
683
684    for p in pair.into_inner() {
685        match p.as_rule() {
686            Rule::raw_tag => {
687                for p2 in p.into_inner() {
688                    match p2.as_rule() {
689                        Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-",
690                        Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}",
691                        _ => unreachable!(),
692                    }
693                }
694            }
695            Rule::raw_text => text = Some(p.as_str().to_string()),
696            Rule::endraw_tag => {
697                for p2 in p.into_inner() {
698                    match p2.as_rule() {
699                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
700                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
701                        _ => unreachable!(),
702                    }
703                }
704            }
705            _ => unreachable!("unexpected {:?} rule in parse_raw_tag", p.as_rule()),
706        };
707    }
708
709    Node::Raw(start_ws, text.unwrap(), end_ws)
710}
711
712fn parse_filter_section(pair: Pair<Rule>) -> TeraResult<Node> {
713    let mut start_ws = WS::default();
714    let mut end_ws = WS::default();
715    let mut filter = None;
716    let mut body = vec![];
717
718    for p in pair.into_inner() {
719        match p.as_rule() {
720            Rule::filter_tag => {
721                for p2 in p.into_inner() {
722                    match p2.as_rule() {
723                        Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-",
724                        Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}",
725                        Rule::fn_call => filter = Some(parse_fn_call(p2)?),
726                        Rule::ident => {
727                            filter = Some(FunctionCall {
728                                name: p2.as_str().to_string(),
729                                args: HashMap::new(),
730                            });
731                        }
732                        _ => unreachable!("Got {:?} while parsing filter_tag", p2),
733                    }
734                }
735            }
736            Rule::content
737            | Rule::macro_content
738            | Rule::block_content
739            | Rule::filter_section_content
740            | Rule::for_content => {
741                body.extend(parse_content(p)?);
742            }
743            Rule::endfilter_tag => {
744                for p2 in p.into_inner() {
745                    match p2.as_rule() {
746                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
747                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
748                        _ => unreachable!(),
749                    }
750                }
751            }
752            _ => unreachable!("unexpected {:?} rule in parse_filter_section", p.as_rule()),
753        };
754    }
755    Ok(Node::FilterSection(
756        start_ws,
757        FilterSection {
758            filter: filter.unwrap(),
759            body,
760        },
761        end_ws,
762    ))
763}
764
765fn parse_block(pair: Pair<Rule>) -> TeraResult<Node> {
766    let mut start_ws = WS::default();
767    let mut end_ws = WS::default();
768    let mut name = None;
769    let mut body = vec![];
770
771    for p in pair.into_inner() {
772        match p.as_rule() {
773            Rule::block_tag => {
774                for p2 in p.into_inner() {
775                    match p2.as_rule() {
776                        Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-",
777                        Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}",
778                        Rule::ident => name = Some(p2.as_span().as_str().to_string()),
779                        _ => unreachable!(),
780                    };
781                }
782            }
783            Rule::block_content => body.extend(parse_content(p)?),
784            Rule::endblock_tag => {
785                for p2 in p.into_inner() {
786                    match p2.as_rule() {
787                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
788                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
789                        Rule::ident => (),
790                        _ => unreachable!(),
791                    };
792                }
793            }
794            _ => unreachable!("unexpected {:?} rule in parse_filter_section", p.as_rule()),
795        };
796    }
797
798    Ok(Node::Block(
799        start_ws,
800        Block {
801            name: name.unwrap(),
802            body,
803        },
804        end_ws,
805    ))
806}
807
808fn parse_macro_arg(p: Pair<Rule>) -> TeraResult<ExprVal> {
809    let val = match p.as_rule() {
810        Rule::int => Some(ExprVal::Int(p.as_str().parse().map_err(|_| {
811            Error::msg(format!("Integer out of bounds: `{}`", p.as_str()))
812        })?)),
813        Rule::float => Some(ExprVal::Float(p.as_str().parse().map_err(|_| {
814            Error::msg(format!("Float out of bounds: `{}`", p.as_str()))
815        })?)),
816        Rule::boolean => match p.as_str() {
817            "true" => Some(ExprVal::Bool(true)),
818            "True" => Some(ExprVal::Bool(true)),
819            "false" => Some(ExprVal::Bool(false)),
820            "False" => Some(ExprVal::Bool(false)),
821            _ => unreachable!(),
822        },
823        Rule::string => Some(ExprVal::String(replace_string_markers(p.as_str()))),
824        _ => unreachable!("Got {:?} in parse_macro_arg: {}", p.as_rule(), p.as_str()),
825    };
826
827    Ok(val.unwrap())
828}
829
830fn parse_macro_fn(pair: Pair<Rule>) -> TeraResult<(String, HashMap<String, Option<Expr>>)> {
831    let mut name = String::new();
832    let mut args = HashMap::new();
833
834    for p2 in pair.into_inner() {
835        match p2.as_rule() {
836            Rule::ident => name = p2.as_str().to_string(),
837            Rule::macro_def_arg => {
838                let mut arg_name = None;
839                let mut default_val = None;
840                for p3 in p2.into_inner() {
841                    match p3.as_rule() {
842                        Rule::ident => arg_name = Some(p3.as_str().to_string()),
843                        _ => default_val = Some(Expr::new(parse_macro_arg(p3)?)),
844                    };
845                }
846                args.insert(arg_name.unwrap(), default_val);
847            }
848            _ => continue,
849        }
850    }
851
852    Ok((name, args))
853}
854
855fn parse_macro_definition(pair: Pair<Rule>) -> TeraResult<Node> {
856    let mut start_ws = WS::default();
857    let mut end_ws = WS::default();
858    let mut name = String::new();
859    let mut args = HashMap::new();
860    let mut body = vec![];
861
862    for p in pair.into_inner() {
863        match p.as_rule() {
864            Rule::macro_tag => {
865                for p2 in p.into_inner() {
866                    match p2.as_rule() {
867                        Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-",
868                        Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}",
869                        Rule::macro_fn_wrapper => {
870                            let macro_fn = parse_macro_fn(p2)?;
871                            name = macro_fn.0;
872                            args = macro_fn.1;
873                        }
874                        _ => continue,
875                    };
876                }
877            }
878            Rule::macro_content => body.extend(parse_content(p)?),
879            Rule::endmacro_tag => {
880                for p2 in p.into_inner() {
881                    match p2.as_rule() {
882                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
883                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
884                        Rule::ident => (),
885                        _ => unreachable!(),
886                    };
887                }
888            }
889            _ => unreachable!(
890                "unexpected {:?} rule in parse_macro_definition",
891                p.as_rule()
892            ),
893        }
894    }
895
896    Ok(Node::MacroDefinition(
897        start_ws,
898        MacroDefinition { name, args, body },
899        end_ws,
900    ))
901}
902
903fn parse_forloop(pair: Pair<Rule>) -> TeraResult<Node> {
904    let mut start_ws = WS::default();
905    let mut end_ws = WS::default();
906
907    let mut key = None;
908    let mut value = None;
909    let mut container = None;
910    let mut body = vec![];
911    let mut empty_body: Option<Vec<Node>> = None;
912
913    for p in pair.into_inner() {
914        match p.as_rule() {
915            Rule::for_tag => {
916                let mut idents = vec![];
917                for p2 in p.into_inner() {
918                    match p2.as_rule() {
919                        Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-",
920                        Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}",
921                        Rule::ident => idents.push(p2.as_str().to_string()),
922                        Rule::basic_expr_filter => {
923                            container = Some(parse_basic_expr_with_filters(p2)?);
924                        }
925                        Rule::array_filter => container = Some(parse_array_with_filters(p2)?),
926                        _ => unreachable!(),
927                    };
928                }
929
930                if idents.len() == 1 {
931                    value = Some(idents[0].clone());
932                } else {
933                    key = Some(idents[0].clone());
934                    value = Some(idents[1].clone());
935                }
936            }
937            Rule::content
938            | Rule::macro_content
939            | Rule::block_content
940            | Rule::filter_section_content
941            | Rule::for_content => {
942                match empty_body {
943                    Some(ref mut empty_body) => empty_body.extend(parse_content(p)?),
944                    None => body.extend(parse_content(p)?),
945                };
946            }
947            Rule::else_tag => {
948                empty_body = Some(vec![]);
949            }
950            Rule::endfor_tag => {
951                for p2 in p.into_inner() {
952                    match p2.as_rule() {
953                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
954                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
955                        Rule::ident => (),
956                        _ => unreachable!(),
957                    };
958                }
959            }
960            _ => unreachable!("unexpected {:?} rule in parse_forloop", p.as_rule()),
961        };
962    }
963
964    Ok(Node::Forloop(
965        start_ws,
966        Forloop {
967            key,
968            value: value.unwrap(),
969            container: container.unwrap(),
970            body,
971            empty_body,
972        },
973        end_ws,
974    ))
975}
976
977fn parse_break_tag(pair: Pair<Rule>) -> Node {
978    let mut ws = WS::default();
979
980    for p in pair.into_inner() {
981        match p.as_rule() {
982            Rule::tag_start => {
983                ws.left = p.as_span().as_str() == "{%-";
984            }
985            Rule::tag_end => {
986                ws.right = p.as_span().as_str() == "-%}";
987            }
988            _ => unreachable!(),
989        };
990    }
991
992    Node::Break(ws)
993}
994
995fn parse_continue_tag(pair: Pair<Rule>) -> Node {
996    let mut ws = WS::default();
997
998    for p in pair.into_inner() {
999        match p.as_rule() {
1000            Rule::tag_start => {
1001                ws.left = p.as_span().as_str() == "{%-";
1002            }
1003            Rule::tag_end => {
1004                ws.right = p.as_span().as_str() == "-%}";
1005            }
1006            _ => unreachable!(),
1007        };
1008    }
1009
1010    Node::Continue(ws)
1011}
1012
1013fn parse_comment_tag(pair: Pair<Rule>) -> Node {
1014    let mut ws = WS::default();
1015    let mut content = String::new();
1016
1017    for p in pair.into_inner() {
1018        match p.as_rule() {
1019            Rule::comment_start => {
1020                ws.left = p.as_span().as_str() == "{#-";
1021            }
1022            Rule::comment_end => {
1023                ws.right = p.as_span().as_str() == "-#}";
1024            }
1025            Rule::comment_text => {
1026                content = p.as_str().to_owned();
1027            }
1028            _ => unreachable!(),
1029        };
1030    }
1031
1032    Node::Comment(ws, content)
1033}
1034
1035fn parse_if(pair: Pair<Rule>) -> TeraResult<Node> {
1036    // the `endif` tag ws handling
1037    let mut end_ws = WS::default();
1038    let mut conditions = vec![];
1039    let mut otherwise = None;
1040
1041    // the current node we're exploring
1042    let mut current_ws = WS::default();
1043    let mut expr = None;
1044    let mut current_body = vec![];
1045    let mut in_else = false;
1046
1047    for p in pair.into_inner() {
1048        match p.as_rule() {
1049            Rule::if_tag | Rule::elif_tag => {
1050                // Reset everything for elifs
1051                if p.as_rule() == Rule::elif_tag {
1052                    conditions.push((current_ws, expr.unwrap(), current_body));
1053                    expr = None;
1054                    current_ws = WS::default();
1055                    current_body = vec![];
1056                }
1057
1058                for p2 in p.into_inner() {
1059                    match p2.as_rule() {
1060                        Rule::tag_start => current_ws.left = p2.as_span().as_str() == "{%-",
1061                        Rule::tag_end => current_ws.right = p2.as_span().as_str() == "-%}",
1062                        Rule::logic_expr => expr = Some(parse_logic_expr(p2)?),
1063                        _ => unreachable!(),
1064                    };
1065                }
1066            }
1067            Rule::content
1068            | Rule::macro_content
1069            | Rule::block_content
1070            | Rule::for_content
1071            | Rule::filter_section_content => current_body.extend(parse_content(p)?),
1072            Rule::else_tag => {
1073                // had an elif before the else
1074                if expr.is_some() {
1075                    conditions.push((current_ws, expr.unwrap(), current_body));
1076                    expr = None;
1077                    current_ws = WS::default();
1078                    current_body = vec![];
1079                }
1080                in_else = true;
1081                for p2 in p.into_inner() {
1082                    match p2.as_rule() {
1083                        Rule::tag_start => current_ws.left = p2.as_span().as_str() == "{%-",
1084                        Rule::tag_end => current_ws.right = p2.as_span().as_str() == "-%}",
1085                        _ => unreachable!(),
1086                    };
1087                }
1088            }
1089            Rule::endif_tag => {
1090                if in_else {
1091                    otherwise = Some((current_ws, current_body));
1092                } else {
1093                    // the last elif
1094                    conditions.push((current_ws, expr.unwrap(), current_body));
1095                }
1096
1097                for p2 in p.into_inner() {
1098                    match p2.as_rule() {
1099                        Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-",
1100                        Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}",
1101                        _ => unreachable!(),
1102                    };
1103                }
1104                break;
1105            }
1106            _ => unreachable!("unreachable rule in parse_if: {:?}", p.as_rule()),
1107        }
1108    }
1109
1110    Ok(Node::If(
1111        If {
1112            conditions,
1113            otherwise,
1114        },
1115        end_ws,
1116    ))
1117}
1118
1119fn parse_content(pair: Pair<Rule>) -> TeraResult<Vec<Node>> {
1120    let mut nodes = vec![];
1121
1122    for p in pair.into_inner() {
1123        match p.as_rule() {
1124            Rule::include_tag => nodes.push(parse_include(p)),
1125            Rule::comment_tag => nodes.push(parse_comment_tag(p)),
1126            Rule::super_tag => nodes.push(Node::Super),
1127            Rule::set_tag => nodes.push(parse_set_tag(p, false)?),
1128            Rule::set_global_tag => nodes.push(parse_set_tag(p, true)?),
1129            Rule::raw => nodes.push(parse_raw_tag(p)),
1130            Rule::variable_tag => nodes.push(parse_variable_tag(p)?),
1131            Rule::macro_definition => nodes.push(parse_macro_definition(p)?),
1132            Rule::forloop => nodes.push(parse_forloop(p)?),
1133            Rule::break_tag => nodes.push(parse_break_tag(p)),
1134            Rule::continue_tag => nodes.push(parse_continue_tag(p)),
1135            Rule::content_if
1136            | Rule::macro_if
1137            | Rule::block_if
1138            | Rule::for_if
1139            | Rule::filter_section_if => nodes.push(parse_if(p)?),
1140            Rule::filter_section => nodes.push(parse_filter_section(p)?),
1141            Rule::text => nodes.push(Node::Text(p.as_span().as_str().to_string())),
1142            Rule::block => nodes.push(parse_block(p)?),
1143            _ => unreachable!("unreachable content rule: {:?}", p.as_rule()),
1144        };
1145    }
1146
1147    Ok(nodes)
1148}
1149
1150pub fn parse(input: &str) -> TeraResult<Vec<Node>> {
1151    let mut pairs = match TeraParser::parse(Rule::template, input) {
1152        Ok(p) => p,
1153        Err(e) => {
1154            let fancy_e = e.renamed_rules(|rule| {
1155                match *rule {
1156                    Rule::EOI => "end of input".to_string(),
1157                    Rule::int => "an integer".to_string(),
1158                    Rule::float => "a float".to_string(),
1159                    Rule::string
1160                    | Rule::double_quoted_string
1161                    | Rule::single_quoted_string
1162                    | Rule::backquoted_quoted_string => {
1163                        "a string".to_string()
1164                    }
1165                    Rule::string_concat => "a concatenation of strings".to_string(),
1166                    Rule::string_expr_filter => "a string or a concatenation of strings".to_string(),
1167                    Rule::all_chars => "a character".to_string(),
1168                    Rule::array => "an array of values".to_string(),
1169                    Rule::array_filter => "an array of values with an optional filter".to_string(),
1170                    Rule::string_array => "an array of strings".to_string(),
1171                    Rule::basic_val => "a value".to_string(),
1172                    Rule::basic_op => "a mathematical operator".to_string(),
1173                    Rule::comparison_op => "a comparison operator".to_string(),
1174                    Rule::boolean => "`true` or `false`".to_string(),
1175                    Rule::ident => "an identifier (must start with a-z)".to_string(),
1176                    Rule::dotted_ident => "a dotted identifier (identifiers separated by `.`)".to_string(),
1177                    Rule::dotted_square_bracket_ident => "a square bracketed identifier (identifiers separated by `.` or `[]`s)".to_string(),
1178                    Rule::square_brackets => "an identifier, string or integer inside `[]`s".to_string(),
1179                    Rule::basic_expr_filter => "an expression with an optional filter".to_string(),
1180                    Rule::comparison_val => "a comparison value".to_string(),
1181                    Rule::basic_expr | Rule::comparison_expr => "an expression".to_string(),
1182                    Rule::logic_val => "a value that can be negated".to_string(),
1183                    Rule::logic_expr => "any expressions".to_string(),
1184                    Rule::fn_call => "a function call".to_string(),
1185                    Rule::kwarg => "a keyword argument: `key=value` where `value` can be any expressions".to_string(),
1186                    Rule::kwargs => "a list of keyword arguments: `key=value` where `value` can be any expressions and separated by `,`".to_string(),
1187                    Rule::op_or => "`or`".to_string(),
1188                    Rule::op_and => "`and`".to_string(),
1189                    Rule::op_not => "`not`".to_string(),
1190                    Rule::op_lte => "`<=`".to_string(),
1191                    Rule::op_gte => "`>=`".to_string(),
1192                    Rule::op_lt => "`<`".to_string(),
1193                    Rule::op_gt => "`>`".to_string(),
1194                    Rule::op_ineq => "`!=`".to_string(),
1195                    Rule::op_eq => "`==`".to_string(),
1196                    Rule::op_plus => "`+`".to_string(),
1197                    Rule::op_minus => "`-`".to_string(),
1198                    Rule::op_times => "`*`".to_string(),
1199                    Rule::op_slash => "`/`".to_string(),
1200                    Rule::op_modulo => "`%`".to_string(),
1201                    Rule::filter => "a filter".to_string(),
1202                    Rule::test => "a test".to_string(),
1203                    Rule::test_not => "a negated test".to_string(),
1204                    Rule::test_call => "a test call".to_string(),
1205                    Rule::test_arg => "a test argument (any expressions including arrays)".to_string(),
1206                    Rule::test_args => "a list of test arguments (any expression including arrayss)".to_string(),
1207                    Rule::macro_fn | Rule::macro_fn_wrapper => "a macro function".to_string(),
1208                    Rule::macro_call => "a macro function call".to_string(),
1209                    Rule::macro_def_arg => {
1210                        "an argument name with an optional default literal value: `id`, `key=1`".to_string()
1211                    }
1212                    Rule::macro_def_args => {
1213                        "a list of argument names with an optional default literal value: `id`, `key=1`".to_string()
1214                    }
1215                    Rule::endmacro_tag => "`{% endmacro %}`".to_string(),
1216                    Rule::macro_content => "the macro content".to_string(),
1217                    Rule::filter_section_content => "the filter section content".to_string(),
1218                    Rule::set_tag => "a `set` tag`".to_string(),
1219                    Rule::set_global_tag => "a `set_global` tag`".to_string(),
1220                    Rule::block_content | Rule::content | Rule::for_content => {
1221                        "some content".to_string()
1222                    },
1223                    Rule::text => "some text".to_string(),
1224                    // Pest will error an unexpected tag as Rule::tag_start
1225                    // and just showing `{%` is not clear as some other valid
1226                    // tags will also start with `{%`
1227                    Rule::tag_start => "tag".to_string(),
1228                    Rule::tag_end => "`%}` or `-%}`".to_string(),
1229                    Rule::super_tag => "`{{ super() }}`".to_string(),
1230                    Rule::raw_tag => "`{% raw %}`".to_string(),
1231                    Rule::raw_text => "some raw text".to_string(),
1232                    Rule::raw => "a raw block (`{% raw %}...{% endraw %}`".to_string(),
1233                    Rule::endraw_tag => "`{% endraw %}`".to_string(),
1234                    Rule::ignore_missing => "ignore missing mark for include tag".to_string(),
1235                    Rule::include_tag => r#"an include tag (`{% include "..." %}`)"#.to_string(),
1236                    Rule::comment_tag => "a comment tag (`{#...#}`)".to_string(),
1237                    Rule::comment_text => "the context of a comment (`{# ... #}`)".to_string(),
1238                    Rule::variable_tag => "a variable tag (`{{ ... }}`)".to_string(),
1239                    Rule::filter_tag | Rule::filter_section => {
1240                        "a filter section (`{% filter something %}...{% endfilter %}`)".to_string()
1241                    }
1242                    Rule::for_tag | Rule::forloop => {
1243                        "a forloop (`{% for i in something %}...{% endfor %}".to_string()
1244                    },
1245                    Rule::endfilter_tag => "an endfilter tag (`{% endfilter %}`)".to_string(),
1246                    Rule::endfor_tag => "an endfor tag (`{% endfor %}`)".to_string(),
1247                    Rule::if_tag
1248                    | Rule::content_if
1249                    | Rule::block_if
1250                    | Rule::macro_if
1251                    | Rule::for_if
1252                    | Rule::filter_section_if => {
1253                        "a `if` tag".to_string()
1254                    }
1255                    Rule::elif_tag => "an `elif` tag".to_string(),
1256                    Rule::else_tag => "an `else` tag".to_string(),
1257                    Rule::endif_tag => "an endif tag (`{% endif %}`)".to_string(),
1258                    Rule::WHITESPACE => "whitespace".to_string(),
1259                    Rule::variable_start => "a variable start (`{{`)".to_string(),
1260                    Rule::variable_end => "a variable end (`}}`)".to_string(),
1261                    Rule::comment_start => "a comment start (`{#`)".to_string(),
1262                    Rule::comment_end => "a comment end (`#}`)".to_string(),
1263                    Rule::block_start => "`{{`, `{%` or `{#`".to_string(),
1264                    Rule::import_macro_tag => r#"an import macro tag (`{% import "filename" as namespace %}`"#.to_string(),
1265                    Rule::block | Rule::block_tag => r#"a block tag (`{% block block_name %}`"#.to_string(),
1266                    Rule::endblock_tag => r#"an endblock tag (`{% endblock block_name %}`"#.to_string(),
1267                    Rule::macro_definition
1268                    | Rule::macro_tag => r#"a macro definition tag (`{% macro my_macro() %}`"#.to_string(),
1269                    Rule::extends_tag => r#"an extends tag (`{% extends "myfile" %}`"#.to_string(),
1270                    Rule::template => "a template".to_string(),
1271                    Rule::break_tag => "a break tag".to_string(),
1272                    Rule::continue_tag => "a continue tag".to_string(),
1273                    Rule::top_imports => "top imports".to_string(),
1274                    Rule::in_cond => "a `in` condition".to_string(),
1275                    Rule::in_cond_container => "a `in` condition container: a string, an array or an ident".to_string(),
1276                }
1277            });
1278            return Err(Error::msg(fancy_e));
1279        }
1280    };
1281
1282    let mut nodes = vec![];
1283
1284    // We must have at least a `template` pair if we got there
1285    for p in pairs.next().unwrap().into_inner() {
1286        match p.as_rule() {
1287            Rule::extends_tag => nodes.push(parse_extends(p)),
1288            Rule::import_macro_tag => nodes.push(parse_import_macro(p)),
1289            Rule::content => nodes.extend(parse_content(p)?),
1290            Rule::comment_tag => (),
1291            Rule::EOI => (),
1292            _ => unreachable!("unknown tpl rule: {:?}", p.as_rule()),
1293        }
1294    }
1295
1296    Ok(nodes)
1297}