tera/parser/
mod.rs

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