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