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
11const _GRAMMAR: &str = include_str!("tera.pest");
14
15#[derive(Parser)]
16#[grammar = "parser/tera.pest"]
17pub struct TeraParser;
18
19pub 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)) .op(Op::infix(Rule::op_times, Assoc::Left)
33 | Op::infix(Rule::op_slash, Assoc::Left)
34 | Op::infix(Rule::op_modulo, Assoc::Left)); 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)); 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
45fn 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 {
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 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 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
272fn 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
288fn 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
305fn 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 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 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
360fn 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
419fn 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 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 let mut end_ws = WS::default();
972 let mut conditions = vec![];
973 let mut otherwise = None;
974
975 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 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 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 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 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 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}