aboutsummaryrefslogtreecommitdiffstats
path: root/askama_derive
diff options
context:
space:
mode:
authorLibravatar René Kijewski <rene.kijewski@fu-berlin.de>2023-01-30 12:52:50 +0100
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2023-01-30 14:19:46 +0100
commit7b6f1df433a7f11612608644342b898cd6be8ff5 (patch)
tree478aafb88f9814fe235ee983213da5aa74a13e0a /askama_derive
parent63b98ec7d379768d771966c6aa44de20862e0994 (diff)
downloadaskama-7b6f1df433a7f11612608644342b898cd6be8ff5.tar.gz
askama-7b6f1df433a7f11612608644342b898cd6be8ff5.tar.bz2
askama-7b6f1df433a7f11612608644342b898cd6be8ff5.zip
derive: refactor parser
`parser.rs` was a single file containing almost 2000 lines. This PR split the file into multiple, smaller files. `Expr`, `Node`, and `Target` each get an own file. Each struct gets a `parse()` method that return `Result<Self>`, and every other detail is private to the file. This PR should make this essential part of Askama more easy to understand, and make future modifications easier.
Diffstat (limited to '')
-rw-r--r--askama_derive/src/parser.rs1940
-rw-r--r--askama_derive/src/parser/expr.rs346
-rw-r--r--askama_derive/src/parser/mod.rs314
-rw-r--r--askama_derive/src/parser/node.rs671
-rw-r--r--askama_derive/src/parser/tests.rs668
5 files changed, 1999 insertions, 1940 deletions
diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs
deleted file mode 100644
index 5045731..0000000
--- a/askama_derive/src/parser.rs
+++ /dev/null
@@ -1,1940 +0,0 @@
-use std::cell::Cell;
-use std::str;
-
-use nom::branch::alt;
-use nom::bytes::complete::{escaped, is_not, tag, take_till, take_until};
-use nom::character::complete::{anychar, char, digit1};
-use nom::combinator::{complete, consumed, cut, eof, map, not, opt, peek, recognize, value};
-use nom::error::{Error, ErrorKind};
-use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
-use nom::sequence::{delimited, pair, preceded, terminated, tuple};
-use nom::{self, error_position, AsChar, IResult, InputTakeAtPosition};
-
-use crate::config::Syntax;
-use crate::CompileError;
-
-#[derive(Debug, PartialEq)]
-pub(crate) enum Node<'a> {
- Lit(&'a str, &'a str, &'a str),
- Comment(Ws),
- Expr(Ws, Expr<'a>),
- Call(Ws, Option<&'a str>, &'a str, Vec<Expr<'a>>),
- LetDecl(Ws, Target<'a>),
- Let(Ws, Target<'a>, Expr<'a>),
- Cond(Vec<Cond<'a>>, Ws),
- Match(Ws, Expr<'a>, Vec<When<'a>>, Ws),
- Loop(Loop<'a>),
- Extends(&'a str),
- BlockDef(Ws, &'a str, Vec<Node<'a>>, Ws),
- Include(Ws, &'a str),
- Import(Ws, &'a str, &'a str),
- Macro(&'a str, Macro<'a>),
- Raw(Ws, &'a str, &'a str, &'a str, Ws),
- Break(Ws),
- Continue(Ws),
-}
-
-#[derive(Debug, PartialEq)]
-pub(crate) struct Loop<'a> {
- pub(crate) ws1: Ws,
- pub(crate) var: Target<'a>,
- pub(crate) iter: Expr<'a>,
- pub(crate) cond: Option<Expr<'a>>,
- pub(crate) body: Vec<Node<'a>>,
- pub(crate) ws2: Ws,
- pub(crate) else_block: Vec<Node<'a>>,
- pub(crate) ws3: Ws,
-}
-
-#[derive(Debug, PartialEq)]
-pub(crate) enum Expr<'a> {
- BoolLit(&'a str),
- NumLit(&'a str),
- StrLit(&'a str),
- CharLit(&'a str),
- Var(&'a str),
- Path(Vec<&'a str>),
- Array(Vec<Expr<'a>>),
- Attr(Box<Expr<'a>>, &'a str),
- Index(Box<Expr<'a>>, Box<Expr<'a>>),
- Filter(&'a str, Vec<Expr<'a>>),
- Unary(&'a str, Box<Expr<'a>>),
- BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>),
- Range(&'a str, Option<Box<Expr<'a>>>, Option<Box<Expr<'a>>>),
- Group(Box<Expr<'a>>),
- Tuple(Vec<Expr<'a>>),
- Call(Box<Expr<'a>>, Vec<Expr<'a>>),
- RustMacro(&'a str, &'a str),
- Try(Box<Expr<'a>>),
-}
-
-impl Expr<'_> {
- /// Returns `true` if enough assumptions can be made,
- /// to determine that `self` is copyable.
- pub(crate) fn is_copyable(&self) -> bool {
- self.is_copyable_within_op(false)
- }
-
- fn is_copyable_within_op(&self, within_op: bool) -> bool {
- use Expr::*;
- match self {
- BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true,
- Unary(.., expr) => expr.is_copyable_within_op(true),
- BinOp(_, lhs, rhs) => {
- lhs.is_copyable_within_op(true) && rhs.is_copyable_within_op(true)
- }
- Range(..) => true,
- // The result of a call likely doesn't need to be borrowed,
- // as in that case the call is more likely to return a
- // reference in the first place then.
- Call(..) | Path(..) => true,
- // If the `expr` is within a `Unary` or `BinOp` then
- // an assumption can be made that the operand is copy.
- // If not, then the value is moved and adding `.clone()`
- // will solve that issue. However, if the operand is
- // implicitly borrowed, then it's likely not even possible
- // to get the template to compile.
- _ => within_op && self.is_attr_self(),
- }
- }
-
- /// Returns `true` if this is an `Attr` where the `obj` is `"self"`.
- pub(crate) fn is_attr_self(&self) -> bool {
- match self {
- Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true,
- Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => obj.is_attr_self(),
- _ => false,
- }
- }
-
- /// Returns `true` if the outcome of this expression may be used multiple times in the same
- /// `write!()` call, without evaluating the expression again, i.e. the expression should be
- /// side-effect free.
- pub(crate) fn is_cachable(&self) -> bool {
- match self {
- // Literals are the definition of pure:
- Expr::BoolLit(_) => true,
- Expr::NumLit(_) => true,
- Expr::StrLit(_) => true,
- Expr::CharLit(_) => true,
- // fmt::Display should have no effects:
- Expr::Var(_) => true,
- Expr::Path(_) => true,
- // Check recursively:
- Expr::Array(args) => args.iter().all(|arg| arg.is_cachable()),
- Expr::Attr(lhs, _) => lhs.is_cachable(),
- Expr::Index(lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(),
- Expr::Filter(_, args) => args.iter().all(|arg| arg.is_cachable()),
- Expr::Unary(_, arg) => arg.is_cachable(),
- Expr::BinOp(_, lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(),
- Expr::Range(_, lhs, rhs) => {
- lhs.as_ref().map_or(true, |v| v.is_cachable())
- && rhs.as_ref().map_or(true, |v| v.is_cachable())
- }
- Expr::Group(arg) => arg.is_cachable(),
- Expr::Tuple(args) => args.iter().all(|arg| arg.is_cachable()),
- // We have too little information to tell if the expression is pure:
- Expr::Call(_, _) => false,
- Expr::RustMacro(_, _) => false,
- Expr::Try(_) => false,
- }
- }
-}
-
-pub(crate) type When<'a> = (Ws, Target<'a>, Vec<Node<'a>>);
-
-#[derive(Debug, PartialEq)]
-pub(crate) struct Macro<'a> {
- pub(crate) ws1: Ws,
- pub(crate) args: Vec<&'a str>,
- pub(crate) nodes: Vec<Node<'a>>,
- pub(crate) ws2: Ws,
-}
-
-#[derive(Debug, PartialEq)]
-pub(crate) enum Target<'a> {
- Name(&'a str),
- Tuple(Vec<&'a str>, Vec<Target<'a>>),
- Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
- NumLit(&'a str),
- StrLit(&'a str),
- CharLit(&'a str),
- BoolLit(&'a str),
- Path(Vec<&'a str>),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub(crate) enum Whitespace {
- Preserve,
- Suppress,
- Minimize,
-}
-
-impl From<char> for Whitespace {
- fn from(c: char) -> Self {
- match c {
- '+' => Self::Preserve,
- '-' => Self::Suppress,
- '~' => Self::Minimize,
- _ => panic!("unsupported `Whitespace` conversion"),
- }
- }
-}
-
-/// First field is "minus/plus sign was used on the left part of the item".
-///
-/// Second field is "minus/plus sign was used on the right part of the item".
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub(crate) struct Ws(pub(crate) Option<Whitespace>, pub(crate) Option<Whitespace>);
-
-pub(crate) type Cond<'a> = (Ws, Option<CondTest<'a>>, Vec<Node<'a>>);
-
-#[derive(Debug, PartialEq)]
-pub(crate) struct CondTest<'a> {
- pub(crate) target: Option<Target<'a>>,
- pub(crate) expr: Expr<'a>,
-}
-
-fn is_ws(c: char) -> bool {
- matches!(c, ' ' | '\t' | '\r' | '\n')
-}
-
-fn not_ws(c: char) -> bool {
- !is_ws(c)
-}
-
-fn ws<'a, O>(
- inner: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, O> {
- delimited(take_till(not_ws), inner, take_till(not_ws))
-}
-
-fn split_ws_parts(s: &str) -> Node<'_> {
- let trimmed_start = s.trim_start_matches(is_ws);
- let len_start = s.len() - trimmed_start.len();
- let trimmed = trimmed_start.trim_end_matches(is_ws);
- Node::Lit(&s[..len_start], trimmed, &trimmed_start[trimmed.len()..])
-}
-
-/// Skips input until `end` was found, but does not consume it.
-/// Returns tuple that would be returned when parsing `end`.
-fn skip_till<'a, O>(
- end: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O)> {
- enum Next<O> {
- IsEnd(O),
- NotEnd(char),
- }
- let mut next = alt((map(end, Next::IsEnd), map(anychar, Next::NotEnd)));
- move |start: &'a str| {
- let mut i = start;
- loop {
- let (j, is_end) = next(i)?;
- match is_end {
- Next::IsEnd(lookahead) => return Ok((i, (j, lookahead))),
- Next::NotEnd(_) => i = j,
- }
- }
- }
-}
-
-fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
- move |i: &'a str| -> IResult<&'a str, &'a str> {
- let (j, v) = identifier(i)?;
- if k == v {
- Ok((j, v))
- } else {
- Err(nom::Err::Error(error_position!(i, ErrorKind::Tag)))
- }
- }
-}
-
-struct State<'a> {
- syntax: &'a Syntax,
- loop_depth: Cell<usize>,
-}
-
-fn take_content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let p_start = alt((
- tag(s.syntax.block_start.as_str()),
- tag(s.syntax.comment_start.as_str()),
- tag(s.syntax.expr_start.as_str()),
- ));
-
- let (i, _) = not(eof)(i)?;
- let (i, content) = opt(recognize(skip_till(p_start)))(i)?;
- let (i, content) = match content {
- Some("") => {
- // {block,comment,expr}_start follows immediately.
- return Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil)));
- }
- Some(content) => (i, content),
- None => ("", i), // there is no {block,comment,expr}_start: take everything
- };
- Ok((i, split_ws_parts(content)))
-}
-
-fn identifier(input: &str) -> IResult<&str, &str> {
- recognize(pair(identifier_start, opt(identifier_tail)))(input)
-}
-
-fn identifier_start(s: &str) -> IResult<&str, &str> {
- s.split_at_position1_complete(
- |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'),
- nom::error::ErrorKind::Alpha,
- )
-}
-
-fn identifier_tail(s: &str) -> IResult<&str, &str> {
- s.split_at_position1_complete(
- |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'),
- nom::error::ErrorKind::Alpha,
- )
-}
-
-fn bool_lit(i: &str) -> IResult<&str, &str> {
- alt((keyword("false"), keyword("true")))(i)
-}
-
-fn expr_bool_lit(i: &str) -> IResult<&str, Expr<'_>> {
- map(bool_lit, Expr::BoolLit)(i)
-}
-
-fn variant_bool_lit(i: &str) -> IResult<&str, Target<'_>> {
- map(bool_lit, Target::BoolLit)(i)
-}
-
-fn num_lit(i: &str) -> IResult<&str, &str> {
- recognize(pair(digit1, opt(pair(char('.'), digit1))))(i)
-}
-
-fn expr_num_lit(i: &str) -> IResult<&str, Expr<'_>> {
- map(num_lit, Expr::NumLit)(i)
-}
-
-fn expr_array_lit(i: &str) -> IResult<&str, Expr<'_>> {
- delimited(
- ws(char('[')),
- map(separated_list1(ws(char(',')), expr_any), Expr::Array),
- ws(char(']')),
- )(i)
-}
-
-fn variant_num_lit(i: &str) -> IResult<&str, Target<'_>> {
- map(num_lit, Target::NumLit)(i)
-}
-
-fn str_lit(i: &str) -> IResult<&str, &str> {
- let (i, s) = delimited(
- char('"'),
- opt(escaped(is_not("\\\""), '\\', anychar)),
- char('"'),
- )(i)?;
- Ok((i, s.unwrap_or_default()))
-}
-
-fn expr_str_lit(i: &str) -> IResult<&str, Expr<'_>> {
- map(str_lit, Expr::StrLit)(i)
-}
-
-fn variant_str_lit(i: &str) -> IResult<&str, Target<'_>> {
- map(str_lit, Target::StrLit)(i)
-}
-
-fn char_lit(i: &str) -> IResult<&str, &str> {
- let (i, s) = delimited(
- char('\''),
- opt(escaped(is_not("\\\'"), '\\', anychar)),
- char('\''),
- )(i)?;
- Ok((i, s.unwrap_or_default()))
-}
-
-fn expr_char_lit(i: &str) -> IResult<&str, Expr<'_>> {
- map(char_lit, Expr::CharLit)(i)
-}
-
-fn variant_char_lit(i: &str) -> IResult<&str, Target<'_>> {
- map(char_lit, Target::CharLit)(i)
-}
-
-fn expr_var(i: &str) -> IResult<&str, Expr<'_>> {
- map(identifier, Expr::Var)(i)
-}
-
-fn path(i: &str) -> IResult<&str, Vec<&str>> {
- let root = opt(value("", ws(tag("::"))));
- let tail = separated_list1(ws(tag("::")), identifier);
-
- match tuple((root, identifier, ws(tag("::")), tail))(i) {
- Ok((i, (root, start, _, rest))) => {
- let mut path = Vec::new();
- path.extend(root);
- path.push(start);
- path.extend(rest);
- Ok((i, path))
- }
- Err(err) => {
- if let Ok((i, name)) = identifier(i) {
- // The returned identifier can be assumed to be path if:
- // - Contains both a lowercase and uppercase character, i.e. a type name like `None`
- // - Doesn't contain any lowercase characters, i.e. it's a constant
- // In short, if it contains any uppercase characters it's a path.
- if name.contains(char::is_uppercase) {
- return Ok((i, vec![name]));
- }
- }
-
- // If `identifier()` fails then just return the original error
- Err(err)
- }
- }
-}
-
-fn expr_path(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, path) = path(i)?;
- Ok((i, Expr::Path(path)))
-}
-
-fn named_target(i: &str) -> IResult<&str, (&str, Target<'_>)> {
- let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), target)))(i)?;
- Ok((i, (src, target.unwrap_or(Target::Name(src)))))
-}
-
-fn variant_lit(i: &str) -> IResult<&str, Target<'_>> {
- alt((
- variant_str_lit,
- variant_char_lit,
- variant_num_lit,
- variant_bool_lit,
- ))(i)
-}
-
-fn target(i: &str) -> IResult<&str, Target<'_>> {
- let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
- let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some());
- let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
-
- let (i, lit) = opt(variant_lit)(i)?;
- if let Some(lit) = lit {
- return Ok((i, lit));
- }
-
- // match tuples and unused parentheses
- let (i, target_is_tuple) = opt_opening_paren(i)?;
- if target_is_tuple {
- let (i, is_empty_tuple) = opt_closing_paren(i)?;
- if is_empty_tuple {
- return Ok((i, Target::Tuple(Vec::new(), Vec::new())));
- }
-
- let (i, first_target) = target(i)?;
- let (i, is_unused_paren) = opt_closing_paren(i)?;
- if is_unused_paren {
- return Ok((i, first_target));
- }
-
- let mut targets = vec![first_target];
- let (i, _) = cut(tuple((
- fold_many0(
- preceded(ws(char(',')), target),
- || (),
- |_, target| {
- targets.push(target);
- },
- ),
- opt(ws(char(','))),
- ws(cut(char(')'))),
- )))(i)?;
- return Ok((i, Target::Tuple(Vec::new(), targets)));
- }
-
- // match structs
- let (i, path) = opt(path)(i)?;
- if let Some(path) = path {
- let i_before_matching_with = i;
- let (i, _) = opt(ws(keyword("with")))(i)?;
-
- let (i, is_unnamed_struct) = opt_opening_paren(i)?;
- if is_unnamed_struct {
- let (i, targets) = alt((
- map(char(')'), |_| Vec::new()),
- terminated(
- cut(separated_list1(ws(char(',')), target)),
- pair(opt(ws(char(','))), ws(cut(char(')')))),
- ),
- ))(i)?;
- return Ok((i, Target::Tuple(path, targets)));
- }
-
- let (i, is_named_struct) = opt_opening_brace(i)?;
- if is_named_struct {
- let (i, targets) = alt((
- map(char('}'), |_| Vec::new()),
- terminated(
- cut(separated_list1(ws(char(',')), named_target)),
- pair(opt(ws(char(','))), ws(cut(char('}')))),
- ),
- ))(i)?;
- return Ok((i, Target::Struct(path, targets)));
- }
-
- return Ok((i_before_matching_with, Target::Path(path)));
- }
-
- // neither literal nor struct nor path
- map(identifier, Target::Name)(i)
-}
-
-fn arguments(i: &str) -> IResult<&str, Vec<Expr<'_>>> {
- delimited(
- ws(char('(')),
- separated_list0(char(','), ws(expr_any)),
- ws(char(')')),
- )(i)
-}
-
-fn macro_arguments(i: &str) -> IResult<&str, &str> {
- delimited(char('('), recognize(nested_parenthesis), char(')'))(i)
-}
-
-fn nested_parenthesis(i: &str) -> IResult<&str, ()> {
- let mut nested = 0;
- let mut last = 0;
- let mut in_str = false;
- let mut escaped = false;
-
- for (i, b) in i.chars().enumerate() {
- if !(b == '(' || b == ')') || !in_str {
- match b {
- '(' => nested += 1,
- ')' => {
- if nested == 0 {
- last = i;
- break;
- }
- nested -= 1;
- }
- '"' => {
- if in_str {
- if !escaped {
- in_str = false;
- }
- } else {
- in_str = true;
- }
- }
- '\\' => {
- escaped = !escaped;
- }
- _ => (),
- }
- }
-
- if escaped && b != '\\' {
- escaped = false;
- }
- }
-
- if nested == 0 {
- Ok((&i[last..], ()))
- } else {
- Err(nom::Err::Error(error_position!(
- i,
- ErrorKind::SeparatedNonEmptyList
- )))
- }
-}
-
-fn parameters(i: &str) -> IResult<&str, Vec<&str>> {
- delimited(
- ws(char('(')),
- separated_list0(char(','), ws(identifier)),
- ws(char(')')),
- )(i)
-}
-
-fn expr_group(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, expr) = preceded(ws(char('(')), opt(expr_any))(i)?;
- let expr = match expr {
- Some(expr) => expr,
- None => {
- let (i, _) = char(')')(i)?;
- return Ok((i, Expr::Tuple(vec![])));
- }
- };
-
- let (i, comma) = ws(opt(peek(char(','))))(i)?;
- if comma.is_none() {
- let (i, _) = char(')')(i)?;
- return Ok((i, Expr::Group(Box::new(expr))));
- }
-
- let mut exprs = vec![expr];
- let (i, _) = fold_many0(
- preceded(char(','), ws(expr_any)),
- || (),
- |_, expr| {
- exprs.push(expr);
- },
- )(i)?;
- let (i, _) = pair(ws(opt(char(','))), char(')'))(i)?;
- Ok((i, Expr::Tuple(exprs)))
-}
-
-fn expr_single(i: &str) -> IResult<&str, Expr<'_>> {
- alt((
- expr_bool_lit,
- expr_num_lit,
- expr_str_lit,
- expr_char_lit,
- expr_path,
- expr_rust_macro,
- expr_array_lit,
- expr_var,
- expr_group,
- ))(i)
-}
-
-enum Suffix<'a> {
- Attr(&'a str),
- Index(Expr<'a>),
- Call(Vec<Expr<'a>>),
- Try,
-}
-
-fn expr_attr(i: &str) -> IResult<&str, Suffix<'_>> {
- map(
- preceded(
- ws(pair(char('.'), not(char('.')))),
- cut(alt((num_lit, identifier))),
- ),
- Suffix::Attr,
- )(i)
-}
-
-fn expr_index(i: &str) -> IResult<&str, Suffix<'_>> {
- map(
- preceded(ws(char('[')), cut(terminated(expr_any, ws(char(']'))))),
- Suffix::Index,
- )(i)
-}
-
-fn expr_call(i: &str) -> IResult<&str, Suffix<'_>> {
- map(arguments, Suffix::Call)(i)
-}
-
-fn expr_try(i: &str) -> IResult<&str, Suffix<'_>> {
- map(preceded(take_till(not_ws), char('?')), |_| Suffix::Try)(i)
-}
-
-fn filter(i: &str) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> {
- let (i, (_, fname, args)) = tuple((char('|'), ws(identifier), opt(arguments)))(i)?;
- Ok((i, (fname, args)))
-}
-
-fn expr_filtered(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, (obj, filters)) = tuple((expr_prefix, many0(filter)))(i)?;
-
- let mut res = obj;
- for (fname, args) in filters {
- res = Expr::Filter(fname, {
- let mut args = match args {
- Some(inner) => inner,
- None => Vec::new(),
- };
- args.insert(0, res);
- args
- });
- }
-
- Ok((i, res))
-}
-
-fn expr_prefix(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), expr_suffix)(i)?;
- for op in ops.iter().rev() {
- expr = Expr::Unary(op, Box::new(expr));
- }
- Ok((i, expr))
-}
-
-fn expr_suffix(i: &str) -> IResult<&str, Expr<'_>> {
- let (mut i, mut expr) = expr_single(i)?;
- loop {
- let (j, suffix) = opt(alt((expr_attr, expr_index, expr_call, expr_try)))(i)?;
- i = j;
- match suffix {
- Some(Suffix::Attr(attr)) => expr = Expr::Attr(expr.into(), attr),
- Some(Suffix::Index(index)) => expr = Expr::Index(expr.into(), index.into()),
- Some(Suffix::Call(args)) => expr = Expr::Call(expr.into(), args),
- Some(Suffix::Try) => expr = Expr::Try(expr.into()),
- None => break,
- }
- }
- Ok((i, expr))
-}
-
-fn expr_rust_macro(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, (mname, _, args)) = tuple((identifier, char('!'), macro_arguments))(i)?;
- Ok((i, Expr::RustMacro(mname, args)))
-}
-
-macro_rules! expr_prec_layer {
- ( $name:ident, $inner:ident, $op:expr ) => {
- fn $name(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, left) = $inner(i)?;
- let (i, right) = many0(pair(
- ws(tag($op)),
- $inner,
- ))(i)?;
- Ok((
- i,
- right.into_iter().fold(left, |left, (op, right)| {
- Expr::BinOp(op, Box::new(left), Box::new(right))
- }),
- ))
- }
- };
- ( $name:ident, $inner:ident, $( $op:expr ),+ ) => {
- fn $name(i: &str) -> IResult<&str, Expr<'_>> {
- let (i, left) = $inner(i)?;
- let (i, right) = many0(pair(
- ws(alt(($( tag($op) ),+,))),
- $inner,
- ))(i)?;
- Ok((
- i,
- right.into_iter().fold(left, |left, (op, right)| {
- Expr::BinOp(op, Box::new(left), Box::new(right))
- }),
- ))
- }
- }
-}
-
-expr_prec_layer!(expr_muldivmod, expr_filtered, "*", "/", "%");
-expr_prec_layer!(expr_addsub, expr_muldivmod, "+", "-");
-expr_prec_layer!(expr_shifts, expr_addsub, ">>", "<<");
-expr_prec_layer!(expr_band, expr_shifts, "&");
-expr_prec_layer!(expr_bxor, expr_band, "^");
-expr_prec_layer!(expr_bor, expr_bxor, "|");
-expr_prec_layer!(expr_compare, expr_bor, "==", "!=", ">=", ">", "<=", "<");
-expr_prec_layer!(expr_and, expr_compare, "&&");
-expr_prec_layer!(expr_or, expr_and, "||");
-
-fn expr_handle_ws(i: &str) -> IResult<&str, Whitespace> {
- alt((char('-'), char('+'), char('~')))(i).map(|(s, r)| (s, Whitespace::from(r)))
-}
-
-fn expr_any(i: &str) -> IResult<&str, Expr<'_>> {
- let range_right = |i| pair(ws(alt((tag("..="), tag("..")))), opt(expr_or))(i);
- alt((
- map(range_right, |(op, right)| {
- Expr::Range(op, None, right.map(Box::new))
- }),
- map(
- pair(expr_or, opt(range_right)),
- |(left, right)| match right {
- Some((op, right)) => Expr::Range(op, Some(Box::new(left)), right.map(Box::new)),
- None => left,
- },
- ),
- ))(i)
-}
-
-fn expr_node<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- |i| tag_expr_start(i, s),
- cut(tuple((
- opt(expr_handle_ws),
- ws(expr_any),
- opt(expr_handle_ws),
- |i| tag_expr_end(i, s),
- ))),
- ));
- let (i, (_, (pws, expr, nws, _))) = p(i)?;
- Ok((i, Node::Expr(Ws(pws, nws), expr)))
-}
-
-fn block_call(i: &str) -> IResult<&str, Node<'_>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("call")),
- cut(tuple((
- opt(tuple((ws(identifier), ws(tag("::"))))),
- ws(identifier),
- ws(arguments),
- opt(expr_handle_ws),
- ))),
- ));
- let (i, (pws, _, (scope, name, args, nws))) = p(i)?;
- let scope = scope.map(|(scope, _)| scope);
- Ok((i, Node::Call(Ws(pws, nws), scope, name, args)))
-}
-
-fn cond_if(i: &str) -> IResult<&str, CondTest<'_>> {
- let mut p = preceded(
- ws(keyword("if")),
- cut(tuple((
- opt(delimited(
- ws(alt((keyword("let"), keyword("set")))),
- ws(target),
- ws(char('=')),
- )),
- ws(expr_any),
- ))),
- );
- let (i, (target, expr)) = p(i)?;
- Ok((i, CondTest { target, expr }))
-}
-
-fn cond_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Cond<'a>> {
- let mut p = tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("else")),
- cut(tuple((
- opt(cond_if),
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(|i| parse_template(i, s)),
- ))),
- ));
- let (i, (_, pws, _, (cond, nws, _, block))) = p(i)?;
- Ok((i, (Ws(pws, nws), cond, block)))
-}
-
-fn block_if<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- cond_if,
- cut(tuple((
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(tuple((
- |i| parse_template(i, s),
- many0(|i| cond_block(i, s)),
- cut(tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("endif")),
- opt(expr_handle_ws),
- ))),
- ))),
- ))),
- ));
- let (i, (pws1, cond, (nws1, _, (block, elifs, (_, pws2, _, nws2))))) = p(i)?;
-
- let mut res = vec![(Ws(pws1, nws1), Some(cond), block)];
- res.extend(elifs);
- Ok((i, Node::Cond(res, Ws(pws2, nws2))))
-}
-
-fn match_else_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> {
- let mut p = tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("else")),
- cut(tuple((
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(|i| parse_template(i, s)),
- ))),
- ));
- let (i, (_, pws, _, (nws, _, block))) = p(i)?;
- Ok((i, (Ws(pws, nws), Target::Name("_"), block)))
-}
-
-fn when_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> {
- let mut p = tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("when")),
- cut(tuple((
- ws(target),
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(|i| parse_template(i, s)),
- ))),
- ));
- let (i, (_, pws, _, (target, nws, _, block))) = p(i)?;
- Ok((i, (Ws(pws, nws), target, block)))
-}
-
-fn block_match<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("match")),
- cut(tuple((
- ws(expr_any),
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(tuple((
- ws(many0(ws(value((), |i| block_comment(i, s))))),
- many1(|i| when_block(i, s)),
- cut(tuple((
- opt(|i| match_else_block(i, s)),
- cut(tuple((
- ws(|i| tag_block_start(i, s)),
- opt(expr_handle_ws),
- ws(keyword("endmatch")),
- opt(expr_handle_ws),
- ))),
- ))),
- ))),
- ))),
- ));
- let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?;
-
- let mut arms = arms;
- if let Some(arm) = else_arm {
- arms.push(arm);
- }
-
- Ok((i, Node::Match(Ws(pws1, nws1), expr, arms, Ws(pws2, nws2))))
-}
-
-fn block_let(i: &str) -> IResult<&str, Node<'_>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(alt((keyword("let"), keyword("set")))),
- cut(tuple((
- ws(target),
- opt(tuple((ws(char('=')), ws(expr_any)))),
- opt(expr_handle_ws),
- ))),
- ));
- let (i, (pws, _, (var, val, nws))) = p(i)?;
-
- Ok((
- i,
- if let Some((_, val)) = val {
- Node::Let(Ws(pws, nws), var, val)
- } else {
- Node::LetDecl(Ws(pws, nws), var)
- },
- ))
-}
-
-fn parse_loop_content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
- s.loop_depth.set(s.loop_depth.get() + 1);
- let result = parse_template(i, s);
- s.loop_depth.set(s.loop_depth.get() - 1);
- result
-}
-
-fn block_for<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let if_cond = preceded(ws(keyword("if")), cut(ws(expr_any)));
- let else_block = |i| {
- let mut p = preceded(
- ws(keyword("else")),
- cut(tuple((
- opt(expr_handle_ws),
- delimited(
- |i| tag_block_end(i, s),
- |i| parse_template(i, s),
- |i| tag_block_start(i, s),
- ),
- opt(expr_handle_ws),
- ))),
- );
- let (i, (pws, nodes, nws)) = p(i)?;
- Ok((i, (pws, nodes, nws)))
- };
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("for")),
- cut(tuple((
- ws(target),
- ws(keyword("in")),
- cut(tuple((
- ws(expr_any),
- opt(if_cond),
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- cut(tuple((
- |i| parse_loop_content(i, s),
- cut(tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- opt(else_block),
- ws(keyword("endfor")),
- opt(expr_handle_ws),
- ))),
- ))),
- ))),
- ))),
- ));
- let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) =
- p(i)?;
- let (nws3, else_block, pws3) = else_block.unwrap_or_default();
- Ok((
- i,
- Node::Loop(Loop {
- ws1: Ws(pws1, nws1),
- var,
- iter,
- cond,
- body,
- ws2: Ws(pws2, nws3),
- else_block,
- ws3: Ws(pws3, nws2),
- }),
- ))
-}
-
-fn block_extends(i: &str) -> IResult<&str, Node<'_>> {
- let (i, (_, name)) = tuple((ws(keyword("extends")), ws(str_lit)))(i)?;
- Ok((i, Node::Extends(name)))
-}
-
-fn block_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut start = tuple((
- opt(expr_handle_ws),
- ws(keyword("block")),
- cut(tuple((ws(identifier), opt(expr_handle_ws), |i| {
- tag_block_end(i, s)
- }))),
- ));
- let (i, (pws1, _, (name, nws1, _))) = start(i)?;
-
- let mut end = cut(tuple((
- |i| parse_template(i, s),
- cut(tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("endblock")),
- cut(tuple((opt(ws(keyword(name))), opt(expr_handle_ws)))),
- ))),
- )));
- let (i, (contents, (_, pws2, _, (_, nws2)))) = end(i)?;
-
- Ok((
- i,
- Node::BlockDef(Ws(pws1, nws1), name, contents, Ws(pws2, nws2)),
- ))
-}
-
-fn block_include(i: &str) -> IResult<&str, Node<'_>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("include")),
- cut(pair(ws(str_lit), opt(expr_handle_ws))),
- ));
- let (i, (pws, _, (name, nws))) = p(i)?;
- Ok((i, Node::Include(Ws(pws, nws), name)))
-}
-
-fn block_import(i: &str) -> IResult<&str, Node<'_>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("import")),
- cut(tuple((
- ws(str_lit),
- ws(keyword("as")),
- cut(pair(ws(identifier), opt(expr_handle_ws))),
- ))),
- ));
- let (i, (pws, _, (name, _, (scope, nws)))) = p(i)?;
- Ok((i, Node::Import(Ws(pws, nws), name, scope)))
-}
-
-fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut start = tuple((
- opt(expr_handle_ws),
- ws(keyword("macro")),
- cut(tuple((
- ws(identifier),
- ws(parameters),
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- ))),
- ));
- let (i, (pws1, _, (name, params, nws1, _))) = start(i)?;
-
- let mut end = cut(tuple((
- |i| parse_template(i, s),
- cut(tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("endmacro")),
- cut(tuple((opt(ws(keyword(name))), opt(expr_handle_ws)))),
- ))),
- )));
- let (i, (contents, (_, pws2, _, (_, nws2)))) = end(i)?;
-
- assert_ne!(name, "super", "invalid macro name 'super'");
-
- Ok((
- i,
- Node::Macro(
- name,
- Macro {
- ws1: Ws(pws1, nws1),
- args: params,
- nodes: contents,
- ws2: Ws(pws2, nws2),
- },
- ),
- ))
-}
-
-fn block_raw<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let endraw = tuple((
- |i| tag_block_start(i, s),
- opt(expr_handle_ws),
- ws(keyword("endraw")),
- opt(expr_handle_ws),
- peek(|i| tag_block_end(i, s)),
- ));
-
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("raw")),
- cut(tuple((
- opt(expr_handle_ws),
- |i| tag_block_end(i, s),
- consumed(skip_till(endraw)),
- ))),
- ));
-
- let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?;
- let (lws, val, rws) = match split_ws_parts(contents) {
- Node::Lit(lws, val, rws) => (lws, val, rws),
- _ => unreachable!(),
- };
- let ws1 = Ws(pws1, nws1);
- let ws2 = Ws(pws2, nws2);
- Ok((i, Node::Raw(ws1, lws, val, rws, ws2)))
-}
-
-fn break_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("break")),
- opt(expr_handle_ws),
- ));
- let (j, (pws, _, nws)) = p(i)?;
- if s.loop_depth.get() == 0 {
- return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag)));
- }
- Ok((j, Node::Break(Ws(pws, nws))))
-}
-
-fn continue_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- opt(expr_handle_ws),
- ws(keyword("continue")),
- opt(expr_handle_ws),
- ));
- let (j, (pws, _, nws)) = p(i)?;
- if s.loop_depth.get() == 0 {
- return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag)));
- }
- Ok((j, Node::Continue(Ws(pws, nws))))
-}
-
-fn block_node<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- |i| tag_block_start(i, s),
- alt((
- block_call,
- block_let,
- |i| block_if(i, s),
- |i| block_for(i, s),
- |i| block_match(i, s),
- block_extends,
- block_include,
- block_import,
- |i| block_block(i, s),
- |i| block_macro(i, s),
- |i| block_raw(i, s),
- |i| break_statement(i, s),
- |i| continue_statement(i, s),
- )),
- cut(|i| tag_block_end(i, s)),
- ));
- let (i, (_, contents, _)) = p(i)?;
- Ok((i, contents))
-}
-
-fn block_comment_body<'a>(mut i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- let mut level = 0;
- loop {
- let (end, tail) = take_until(s.syntax.comment_end.as_str())(i)?;
- match take_until::<_, _, Error<_>>(s.syntax.comment_start.as_str())(i) {
- Ok((start, _)) if start.as_ptr() < end.as_ptr() => {
- level += 1;
- i = &start[2..];
- }
- _ if level > 0 => {
- level -= 1;
- i = &end[2..];
- }
- _ => return Ok((end, tail)),
- }
- }
-}
-
-fn block_comment<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
- let mut p = tuple((
- |i| tag_comment_start(i, s),
- cut(tuple((
- opt(expr_handle_ws),
- |i| block_comment_body(i, s),
- |i| tag_comment_end(i, s),
- ))),
- ));
- let (i, (_, (pws, tail, _))) = p(i)?;
- let nws = if tail.ends_with('-') {
- Some(Whitespace::Suppress)
- } else if tail.ends_with('+') {
- Some(Whitespace::Preserve)
- } else if tail.ends_with('~') {
- Some(Whitespace::Minimize)
- } else {
- None
- };
- Ok((i, Node::Comment(Ws(pws, nws))))
-}
-
-fn parse_template<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
- many0(alt((
- complete(|i| take_content(i, s)),
- complete(|i| block_comment(i, s)),
- complete(|i| expr_node(i, s)),
- complete(|i| block_node(i, s)),
- )))(i)
-}
-
-fn tag_block_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.block_start.as_str())(i)
-}
-fn tag_block_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.block_end.as_str())(i)
-}
-fn tag_comment_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.comment_start.as_str())(i)
-}
-fn tag_comment_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.comment_end.as_str())(i)
-}
-fn tag_expr_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.expr_start.as_str())(i)
-}
-fn tag_expr_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
- tag(s.syntax.expr_end.as_str())(i)
-}
-
-pub(crate) fn parse<'a>(src: &'a str, syntax: &'a Syntax) -> Result<Vec<Node<'a>>, CompileError> {
- let state = State {
- syntax,
- loop_depth: Cell::new(0),
- };
- match parse_template(src, &state) {
- Ok((left, res)) => {
- if !left.is_empty() {
- Err(format!("unable to parse template:\n\n{left:?}").into())
- } else {
- Ok(res)
- }
- }
-
- Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
- let nom::error::Error { input, .. } = err;
- let offset = src.len() - input.len();
- let (source_before, source_after) = src.split_at(offset);
-
- let source_after = match source_after.char_indices().enumerate().take(41).last() {
- Some((40, (i, _))) => format!("{:?}...", &source_after[..i]),
- _ => format!("{source_after:?}"),
- };
-
- let (row, last_line) = source_before.lines().enumerate().last().unwrap();
- let column = last_line.chars().count();
-
- let msg = format!(
- "problems parsing template source at row {}, column {} near:\n{}",
- row + 1,
- column,
- source_after,
- );
- Err(msg.into())
- }
-
- Err(nom::Err::Incomplete(_)) => Err("parsing incomplete".into()),
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::{Expr, Node, Whitespace, Ws};
- use crate::config::Syntax;
-
- fn check_ws_split(s: &str, res: &(&str, &str, &str)) {
- match super::split_ws_parts(s) {
- Node::Lit(lws, s, rws) => {
- assert_eq!(lws, res.0);
- assert_eq!(s, res.1);
- assert_eq!(rws, res.2);
- }
- _ => {
- panic!("fail");
- }
- }
- }
-
- #[test]
- fn test_ws_splitter() {
- check_ws_split("", &("", "", ""));
- check_ws_split("a", &("", "a", ""));
- check_ws_split("\ta", &("\t", "a", ""));
- check_ws_split("b\n", &("", "b", "\n"));
- check_ws_split(" \t\r\n", &(" \t\r\n", "", ""));
- }
-
- #[test]
- #[should_panic]
- fn test_invalid_block() {
- super::parse("{% extend \"blah\" %}", &Syntax::default()).unwrap();
- }
-
- #[test]
- fn test_parse_filter() {
- use Expr::*;
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ strvar|e }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Filter("e", vec![Var("strvar")]),)],
- );
- assert_eq!(
- super::parse("{{ 2|abs }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Filter("abs", vec![NumLit("2")]),)],
- );
- assert_eq!(
- super::parse("{{ -2|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter("abs", vec![Unary("-", NumLit("2").into())]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1 - 2)|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter(
- "abs",
- vec![Group(
- BinOp("-", NumLit("1").into(), NumLit("2").into()).into()
- )]
- ),
- )],
- );
- }
-
- #[test]
- fn test_parse_numbers() {
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ 2 }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::NumLit("2"),)],
- );
- assert_eq!(
- super::parse("{{ 2.5 }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::NumLit("2.5"),)],
- );
- }
-
- #[test]
- fn test_parse_var() {
- let s = Syntax::default();
-
- assert_eq!(
- super::parse("{{ foo }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Var("foo"))],
- );
- assert_eq!(
- super::parse("{{ foo_bar }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Var("foo_bar"))],
- );
-
- assert_eq!(
- super::parse("{{ none }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Var("none"))],
- );
- }
-
- #[test]
- fn test_parse_const() {
- let s = Syntax::default();
-
- assert_eq!(
- super::parse("{{ FOO }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO"]))],
- );
- assert_eq!(
- super::parse("{{ FOO_BAR }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO_BAR"]))],
- );
-
- assert_eq!(
- super::parse("{{ NONE }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Path(vec!["NONE"]))],
- );
- }
-
- #[test]
- fn test_parse_path() {
- let s = Syntax::default();
-
- assert_eq!(
- super::parse("{{ None }}", &s).unwrap(),
- vec![Node::Expr(Ws(None, None), Expr::Path(vec!["None"]))],
- );
- assert_eq!(
- super::parse("{{ Some(123) }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Path(vec!["Some"])),
- vec![Expr::NumLit("123")]
- ),
- )],
- );
-
- assert_eq!(
- super::parse("{{ Ok(123) }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(Box::new(Expr::Path(vec!["Ok"])), vec![Expr::NumLit("123")]),
- )],
- );
- assert_eq!(
- super::parse("{{ Err(123) }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(Box::new(Expr::Path(vec!["Err"])), vec![Expr::NumLit("123")]),
- )],
- );
- }
-
- #[test]
- fn test_parse_var_call() {
- assert_eq!(
- super::parse("{{ function(\"123\", 3) }}", &Syntax::default()).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Var("function")),
- vec![Expr::StrLit("123"), Expr::NumLit("3")]
- ),
- )],
- );
- }
-
- #[test]
- fn test_parse_path_call() {
- let s = Syntax::default();
-
- assert_eq!(
- super::parse("{{ Option::None }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Path(vec!["Option", "None"])
- )],
- );
- assert_eq!(
- super::parse("{{ Option::Some(123) }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Path(vec!["Option", "Some"])),
- vec![Expr::NumLit("123")],
- ),
- )],
- );
-
- assert_eq!(
- super::parse("{{ self::function(\"123\", 3) }}", &s).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Path(vec!["self", "function"])),
- vec![Expr::StrLit("123"), Expr::NumLit("3")],
- ),
- )],
- );
- }
-
- #[test]
- fn test_parse_root_path() {
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ std::string::String::new() }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Path(vec!["std", "string", "String", "new"])),
- vec![]
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ ::std::string::String::new() }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Expr::Call(
- Box::new(Expr::Path(vec!["", "std", "string", "String", "new"])),
- vec![]
- ),
- )],
- );
- }
-
- #[test]
- fn change_delimiters_parse_filter() {
- let syntax = Syntax {
- expr_start: "{=".to_owned(),
- expr_end: "=}".to_owned(),
- ..Syntax::default()
- };
-
- super::parse("{= strvar|e =}", &syntax).unwrap();
- }
-
- #[test]
- fn test_precedence() {
- use Expr::*;
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ a + b == c }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "==",
- BinOp("+", Var("a").into(), Var("b").into()).into(),
- Var("c").into(),
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a + b * c - d / e }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "-",
- BinOp(
- "+",
- Var("a").into(),
- BinOp("*", Var("b").into(), Var("c").into()).into(),
- )
- .into(),
- BinOp("/", Var("d").into(), Var("e").into()).into(),
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a * (b + c) / -d }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "/",
- BinOp(
- "*",
- Var("a").into(),
- Group(BinOp("+", Var("b").into(), Var("c").into()).into()).into()
- )
- .into(),
- Unary("-", Var("d").into()).into()
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a || b && c || d && e }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "||",
- BinOp(
- "||",
- Var("a").into(),
- BinOp("&&", Var("b").into(), Var("c").into()).into(),
- )
- .into(),
- BinOp("&&", Var("d").into(), Var("e").into()).into(),
- )
- )],
- );
- }
-
- #[test]
- fn test_associativity() {
- use Expr::*;
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ a + b + c }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "+",
- BinOp("+", Var("a").into(), Var("b").into()).into(),
- Var("c").into()
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a * b * c }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "*",
- BinOp("*", Var("a").into(), Var("b").into()).into(),
- Var("c").into()
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a && b && c }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "&&",
- BinOp("&&", Var("a").into(), Var("b").into()).into(),
- Var("c").into()
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a + b - c + d }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "+",
- BinOp(
- "-",
- BinOp("+", Var("a").into(), Var("b").into()).into(),
- Var("c").into()
- )
- .into(),
- Var("d").into()
- )
- )],
- );
- assert_eq!(
- super::parse("{{ a == b != c > d > e == f }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "==",
- BinOp(
- ">",
- BinOp(
- ">",
- BinOp(
- "!=",
- BinOp("==", Var("a").into(), Var("b").into()).into(),
- Var("c").into()
- )
- .into(),
- Var("d").into()
- )
- .into(),
- Var("e").into()
- )
- .into(),
- Var("f").into()
- )
- )],
- );
- }
-
- #[test]
- fn test_odd_calls() {
- use Expr::*;
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ a[b](c) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Call(
- Box::new(Index(Box::new(Var("a")), Box::new(Var("b")))),
- vec![Var("c")],
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ (a + b)(c) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Call(
- Box::new(Group(Box::new(BinOp(
- "+",
- Box::new(Var("a")),
- Box::new(Var("b"))
- )))),
- vec![Var("c")],
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ a + b(c) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "+",
- Box::new(Var("a")),
- Box::new(Call(Box::new(Var("b")), vec![Var("c")])),
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ (-a)(b) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Call(
- Box::new(Group(Box::new(Unary("-", Box::new(Var("a")))))),
- vec![Var("b")],
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ -a(b) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Unary("-", Box::new(Call(Box::new(Var("a")), vec![Var("b")])),),
- )],
- );
- }
-
- #[test]
- fn test_parse_comments() {
- let s = &Syntax::default();
-
- assert_eq!(
- super::parse("{##}", s).unwrap(),
- vec![Node::Comment(Ws(None, None))],
- );
- assert_eq!(
- super::parse("{#- #}", s).unwrap(),
- vec![Node::Comment(Ws(Some(Whitespace::Suppress), None))],
- );
- assert_eq!(
- super::parse("{# -#}", s).unwrap(),
- vec![Node::Comment(Ws(None, Some(Whitespace::Suppress)))],
- );
- assert_eq!(
- super::parse("{#--#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Suppress),
- Some(Whitespace::Suppress)
- ))],
- );
- assert_eq!(
- super::parse("{#- foo\n bar -#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Suppress),
- Some(Whitespace::Suppress)
- ))],
- );
- assert_eq!(
- super::parse("{#- foo\n {#- bar\n -#} baz -#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Suppress),
- Some(Whitespace::Suppress)
- ))],
- );
- assert_eq!(
- super::parse("{#+ #}", s).unwrap(),
- vec![Node::Comment(Ws(Some(Whitespace::Preserve), None))],
- );
- assert_eq!(
- super::parse("{# +#}", s).unwrap(),
- vec![Node::Comment(Ws(None, Some(Whitespace::Preserve)))],
- );
- assert_eq!(
- super::parse("{#++#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Preserve),
- Some(Whitespace::Preserve)
- ))],
- );
- assert_eq!(
- super::parse("{#+ foo\n bar +#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Preserve),
- Some(Whitespace::Preserve)
- ))],
- );
- assert_eq!(
- super::parse("{#+ foo\n {#+ bar\n +#} baz -+#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Preserve),
- Some(Whitespace::Preserve)
- ))],
- );
- assert_eq!(
- super::parse("{#~ #}", s).unwrap(),
- vec![Node::Comment(Ws(Some(Whitespace::Minimize), None))],
- );
- assert_eq!(
- super::parse("{# ~#}", s).unwrap(),
- vec![Node::Comment(Ws(None, Some(Whitespace::Minimize)))],
- );
- assert_eq!(
- super::parse("{#~~#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Minimize),
- Some(Whitespace::Minimize)
- ))],
- );
- assert_eq!(
- super::parse("{#~ foo\n bar ~#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Minimize),
- Some(Whitespace::Minimize)
- ))],
- );
- assert_eq!(
- super::parse("{#~ foo\n {#~ bar\n ~#} baz -~#}", s).unwrap(),
- vec![Node::Comment(Ws(
- Some(Whitespace::Minimize),
- Some(Whitespace::Minimize)
- ))],
- );
-
- assert_eq!(
- super::parse("{# foo {# bar #} {# {# baz #} qux #} #}", s).unwrap(),
- vec![Node::Comment(Ws(None, None))],
- );
- }
-
- #[test]
- fn test_parse_tuple() {
- use super::Expr::*;
- let syntax = Syntax::default();
- assert_eq!(
- super::parse("{{ () }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Tuple(vec![]),)],
- );
- assert_eq!(
- super::parse("{{ (1) }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Group(Box::new(NumLit("1"))),)],
- );
- assert_eq!(
- super::parse("{{ (1,) }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
- );
- assert_eq!(
- super::parse("{{ (1, ) }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
- );
- assert_eq!(
- super::parse("{{ (1 ,) }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
- );
- assert_eq!(
- super::parse("{{ (1 , ) }}", &syntax).unwrap(),
- vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
- );
- assert_eq!(
- super::parse("{{ (1, 2) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Tuple(vec![NumLit("1"), NumLit("2")]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1, 2,) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Tuple(vec![NumLit("1"), NumLit("2")]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1, 2, 3) }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Tuple(vec![NumLit("1"), NumLit("2"), NumLit("3")]),
- )],
- );
- assert_eq!(
- super::parse("{{ ()|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter("abs", vec![Tuple(vec![])]),
- )],
- );
- assert_eq!(
- super::parse("{{ () | abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp("|", Box::new(Tuple(vec![])), Box::new(Var("abs"))),
- )],
- );
- assert_eq!(
- super::parse("{{ (1)|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter("abs", vec![Group(Box::new(NumLit("1")))]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1) | abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "|",
- Box::new(Group(Box::new(NumLit("1")))),
- Box::new(Var("abs"))
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ (1,)|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter("abs", vec![Tuple(vec![NumLit("1")])]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1,) | abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "|",
- Box::new(Tuple(vec![NumLit("1")])),
- Box::new(Var("abs"))
- ),
- )],
- );
- assert_eq!(
- super::parse("{{ (1, 2)|abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- Filter("abs", vec![Tuple(vec![NumLit("1"), NumLit("2")])]),
- )],
- );
- assert_eq!(
- super::parse("{{ (1, 2) | abs }}", &syntax).unwrap(),
- vec![Node::Expr(
- Ws(None, None),
- BinOp(
- "|",
- Box::new(Tuple(vec![NumLit("1"), NumLit("2")])),
- Box::new(Var("abs"))
- ),
- )],
- );
- }
-
- #[test]
- fn test_missing_space_after_kw() {
- let syntax = Syntax::default();
- let err = super::parse("{%leta=b%}", &syntax).unwrap_err();
- assert!(matches!(
- &*err.msg,
- "unable to parse template:\n\n\"{%leta=b%}\""
- ));
- }
-}
diff --git a/askama_derive/src/parser/expr.rs b/askama_derive/src/parser/expr.rs
new file mode 100644
index 0000000..4546e04
--- /dev/null
+++ b/askama_derive/src/parser/expr.rs
@@ -0,0 +1,346 @@
+use std::str;
+
+use nom::branch::alt;
+use nom::bytes::complete::{tag, take_till};
+use nom::character::complete::char;
+use nom::combinator::{cut, map, not, opt, peek, recognize};
+use nom::multi::{fold_many0, many0, separated_list0, separated_list1};
+use nom::sequence::{delimited, pair, preceded, terminated, tuple};
+use nom::IResult;
+
+use super::{
+ bool_lit, char_lit, identifier, nested_parenthesis, not_ws, num_lit, path, str_lit, ws,
+};
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum Expr<'a> {
+ BoolLit(&'a str),
+ NumLit(&'a str),
+ StrLit(&'a str),
+ CharLit(&'a str),
+ Var(&'a str),
+ Path(Vec<&'a str>),
+ Array(Vec<Expr<'a>>),
+ Attr(Box<Expr<'a>>, &'a str),
+ Index(Box<Expr<'a>>, Box<Expr<'a>>),
+ Filter(&'a str, Vec<Expr<'a>>),
+ Unary(&'a str, Box<Expr<'a>>),
+ BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>),
+ Range(&'a str, Option<Box<Expr<'a>>>, Option<Box<Expr<'a>>>),
+ Group(Box<Expr<'a>>),
+ Tuple(Vec<Expr<'a>>),
+ Call(Box<Expr<'a>>, Vec<Expr<'a>>),
+ RustMacro(&'a str, &'a str),
+ Try(Box<Expr<'a>>),
+}
+
+impl Expr<'_> {
+ pub(super) fn parse(i: &str) -> IResult<&str, Expr<'_>> {
+ expr_any(i)
+ }
+
+ pub(super) fn parse_arguments(i: &str) -> IResult<&str, Vec<Expr<'_>>> {
+ arguments(i)
+ }
+
+ /// Returns `true` if enough assumptions can be made,
+ /// to determine that `self` is copyable.
+ pub(crate) fn is_copyable(&self) -> bool {
+ self.is_copyable_within_op(false)
+ }
+
+ fn is_copyable_within_op(&self, within_op: bool) -> bool {
+ use Expr::*;
+ match self {
+ BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true,
+ Unary(.., expr) => expr.is_copyable_within_op(true),
+ BinOp(_, lhs, rhs) => {
+ lhs.is_copyable_within_op(true) && rhs.is_copyable_within_op(true)
+ }
+ Range(..) => true,
+ // The result of a call likely doesn't need to be borrowed,
+ // as in that case the call is more likely to return a
+ // reference in the first place then.
+ Call(..) | Path(..) => true,
+ // If the `expr` is within a `Unary` or `BinOp` then
+ // an assumption can be made that the operand is copy.
+ // If not, then the value is moved and adding `.clone()`
+ // will solve that issue. However, if the operand is
+ // implicitly borrowed, then it's likely not even possible
+ // to get the template to compile.
+ _ => within_op && self.is_attr_self(),
+ }
+ }
+
+ /// Returns `true` if this is an `Attr` where the `obj` is `"self"`.
+ pub(crate) fn is_attr_self(&self) -> bool {
+ match self {
+ Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true,
+ Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => obj.is_attr_self(),
+ _ => false,
+ }
+ }
+
+ /// Returns `true` if the outcome of this expression may be used multiple times in the same
+ /// `write!()` call, without evaluating the expression again, i.e. the expression should be
+ /// side-effect free.
+ pub(crate) fn is_cachable(&self) -> bool {
+ match self {
+ // Literals are the definition of pure:
+ Expr::BoolLit(_) => true,
+ Expr::NumLit(_) => true,
+ Expr::StrLit(_) => true,
+ Expr::CharLit(_) => true,
+ // fmt::Display should have no effects:
+ Expr::Var(_) => true,
+ Expr::Path(_) => true,
+ // Check recursively:
+ Expr::Array(args) => args.iter().all(|arg| arg.is_cachable()),
+ Expr::Attr(lhs, _) => lhs.is_cachable(),
+ Expr::Index(lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(),
+ Expr::Filter(_, args) => args.iter().all(|arg| arg.is_cachable()),
+ Expr::Unary(_, arg) => arg.is_cachable(),
+ Expr::BinOp(_, lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(),
+ Expr::Range(_, lhs, rhs) => {
+ lhs.as_ref().map_or(true, |v| v.is_cachable())
+ && rhs.as_ref().map_or(true, |v| v.is_cachable())
+ }
+ Expr::Group(arg) => arg.is_cachable(),
+ Expr::Tuple(args) => args.iter().all(|arg| arg.is_cachable()),
+ // We have too little information to tell if the expression is pure:
+ Expr::Call(_, _) => false,
+ Expr::RustMacro(_, _) => false,
+ Expr::Try(_) => false,
+ }
+ }
+}
+
+fn expr_bool_lit(i: &str) -> IResult<&str, Expr<'_>> {
+ map(bool_lit, Expr::BoolLit)(i)
+}
+
+fn expr_num_lit(i: &str) -> IResult<&str, Expr<'_>> {
+ map(num_lit, Expr::NumLit)(i)
+}
+
+fn expr_array_lit(i: &str) -> IResult<&str, Expr<'_>> {
+ delimited(
+ ws(char('[')),
+ map(separated_list1(ws(char(',')), expr_any), Expr::Array),
+ ws(char(']')),
+ )(i)
+}
+
+fn expr_str_lit(i: &str) -> IResult<&str, Expr<'_>> {
+ map(str_lit, Expr::StrLit)(i)
+}
+
+fn expr_char_lit(i: &str) -> IResult<&str, Expr<'_>> {
+ map(char_lit, Expr::CharLit)(i)
+}
+
+fn expr_var(i: &str) -> IResult<&str, Expr<'_>> {
+ map(identifier, Expr::Var)(i)
+}
+
+fn expr_path(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, path) = path(i)?;
+ Ok((i, Expr::Path(path)))
+}
+
+fn expr_group(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, expr) = preceded(ws(char('(')), opt(expr_any))(i)?;
+ let expr = match expr {
+ Some(expr) => expr,
+ None => {
+ let (i, _) = char(')')(i)?;
+ return Ok((i, Expr::Tuple(vec![])));
+ }
+ };
+
+ let (i, comma) = ws(opt(peek(char(','))))(i)?;
+ if comma.is_none() {
+ let (i, _) = char(')')(i)?;
+ return Ok((i, Expr::Group(Box::new(expr))));
+ }
+
+ let mut exprs = vec![expr];
+ let (i, _) = fold_many0(
+ preceded(char(','), ws(expr_any)),
+ || (),
+ |_, expr| {
+ exprs.push(expr);
+ },
+ )(i)?;
+ let (i, _) = pair(ws(opt(char(','))), char(')'))(i)?;
+ Ok((i, Expr::Tuple(exprs)))
+}
+
+fn expr_single(i: &str) -> IResult<&str, Expr<'_>> {
+ alt((
+ expr_bool_lit,
+ expr_num_lit,
+ expr_str_lit,
+ expr_char_lit,
+ expr_path,
+ expr_rust_macro,
+ expr_array_lit,
+ expr_var,
+ expr_group,
+ ))(i)
+}
+
+enum Suffix<'a> {
+ Attr(&'a str),
+ Index(Expr<'a>),
+ Call(Vec<Expr<'a>>),
+ Try,
+}
+
+fn expr_attr(i: &str) -> IResult<&str, Suffix<'_>> {
+ map(
+ preceded(
+ ws(pair(char('.'), not(char('.')))),
+ cut(alt((num_lit, identifier))),
+ ),
+ Suffix::Attr,
+ )(i)
+}
+
+fn expr_index(i: &str) -> IResult<&str, Suffix<'_>> {
+ map(
+ preceded(ws(char('[')), cut(terminated(expr_any, ws(char(']'))))),
+ Suffix::Index,
+ )(i)
+}
+
+fn expr_call(i: &str) -> IResult<&str, Suffix<'_>> {
+ map(arguments, Suffix::Call)(i)
+}
+
+fn expr_try(i: &str) -> IResult<&str, Suffix<'_>> {
+ map(preceded(take_till(not_ws), char('?')), |_| Suffix::Try)(i)
+}
+
+fn filter(i: &str) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> {
+ let (i, (_, fname, args)) = tuple((char('|'), ws(identifier), opt(arguments)))(i)?;
+ Ok((i, (fname, args)))
+}
+
+fn expr_filtered(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, (obj, filters)) = tuple((expr_prefix, many0(filter)))(i)?;
+
+ let mut res = obj;
+ for (fname, args) in filters {
+ res = Expr::Filter(fname, {
+ let mut args = match args {
+ Some(inner) => inner,
+ None => Vec::new(),
+ };
+ args.insert(0, res);
+ args
+ });
+ }
+
+ Ok((i, res))
+}
+
+fn expr_prefix(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), expr_suffix)(i)?;
+ for op in ops.iter().rev() {
+ expr = Expr::Unary(op, Box::new(expr));
+ }
+ Ok((i, expr))
+}
+
+fn expr_suffix(i: &str) -> IResult<&str, Expr<'_>> {
+ let (mut i, mut expr) = expr_single(i)?;
+ loop {
+ let (j, suffix) = opt(alt((expr_attr, expr_index, expr_call, expr_try)))(i)?;
+ i = j;
+ match suffix {
+ Some(Suffix::Attr(attr)) => expr = Expr::Attr(expr.into(), attr),
+ Some(Suffix::Index(index)) => expr = Expr::Index(expr.into(), index.into()),
+ Some(Suffix::Call(args)) => expr = Expr::Call(expr.into(), args),
+ Some(Suffix::Try) => expr = Expr::Try(expr.into()),
+ None => break,
+ }
+ }
+ Ok((i, expr))
+}
+
+fn macro_arguments(i: &str) -> IResult<&str, &str> {
+ delimited(char('('), recognize(nested_parenthesis), char(')'))(i)
+}
+
+fn expr_rust_macro(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, (mname, _, args)) = tuple((identifier, char('!'), macro_arguments))(i)?;
+ Ok((i, Expr::RustMacro(mname, args)))
+}
+
+macro_rules! expr_prec_layer {
+ ( $name:ident, $inner:ident, $op:expr ) => {
+ fn $name(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, left) = $inner(i)?;
+ let (i, right) = many0(pair(
+ ws(tag($op)),
+ $inner,
+ ))(i)?;
+ Ok((
+ i,
+ right.into_iter().fold(left, |left, (op, right)| {
+ Expr::BinOp(op, Box::new(left), Box::new(right))
+ }),
+ ))
+ }
+ };
+ ( $name:ident, $inner:ident, $( $op:expr ),+ ) => {
+ fn $name(i: &str) -> IResult<&str, Expr<'_>> {
+ let (i, left) = $inner(i)?;
+ let (i, right) = many0(pair(
+ ws(alt(($( tag($op) ),+,))),
+ $inner,
+ ))(i)?;
+ Ok((
+ i,
+ right.into_iter().fold(left, |left, (op, right)| {
+ Expr::BinOp(op, Box::new(left), Box::new(right))
+ }),
+ ))
+ }
+ }
+}
+
+expr_prec_layer!(expr_muldivmod, expr_filtered, "*", "/", "%");
+expr_prec_layer!(expr_addsub, expr_muldivmod, "+", "-");
+expr_prec_layer!(expr_shifts, expr_addsub, ">>", "<<");
+expr_prec_layer!(expr_band, expr_shifts, "&");
+expr_prec_layer!(expr_bxor, expr_band, "^");
+expr_prec_layer!(expr_bor, expr_bxor, "|");
+expr_prec_layer!(expr_compare, expr_bor, "==", "!=", ">=", ">", "<=", "<");
+expr_prec_layer!(expr_and, expr_compare, "&&");
+expr_prec_layer!(expr_or, expr_and, "||");
+
+fn expr_any(i: &str) -> IResult<&str, Expr<'_>> {
+ let range_right = |i| pair(ws(alt((tag("..="), tag("..")))), opt(expr_or))(i);
+ alt((
+ map(range_right, |(op, right)| {
+ Expr::Range(op, None, right.map(Box::new))
+ }),
+ map(
+ pair(expr_or, opt(range_right)),
+ |(left, right)| match right {
+ Some((op, right)) => Expr::Range(op, Some(Box::new(left)), right.map(Box::new)),
+ None => left,
+ },
+ ),
+ ))(i)
+}
+
+fn arguments(i: &str) -> IResult<&str, Vec<Expr<'_>>> {
+ delimited(
+ ws(char('(')),
+ separated_list0(char(','), ws(expr_any)),
+ ws(char(')')),
+ )(i)
+}
diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs
new file mode 100644
index 0000000..d345a81
--- /dev/null
+++ b/askama_derive/src/parser/mod.rs
@@ -0,0 +1,314 @@
+use std::cell::Cell;
+use std::str;
+
+use nom::branch::alt;
+use nom::bytes::complete::{escaped, is_not, tag, take_till};
+use nom::character::complete::char;
+use nom::character::complete::{anychar, digit1};
+use nom::combinator::{eof, map, not, opt, recognize, value};
+use nom::error::ErrorKind;
+use nom::multi::separated_list1;
+use nom::sequence::{delimited, pair, tuple};
+use nom::{error_position, AsChar, IResult, InputTakeAtPosition};
+
+pub(crate) use self::expr::Expr;
+pub(crate) use self::node::{Cond, CondTest, Loop, Macro, Node, Target, When, Whitespace, Ws};
+use crate::config::Syntax;
+use crate::CompileError;
+
+mod expr;
+mod node;
+#[cfg(test)]
+mod tests;
+
+struct State<'a> {
+ syntax: &'a Syntax,
+ loop_depth: Cell<usize>,
+}
+
+impl State<'_> {
+ fn new(syntax: &Syntax) -> State<'_> {
+ State {
+ syntax,
+ loop_depth: Cell::new(0),
+ }
+ }
+
+ fn enter_loop(&self) {
+ self.loop_depth.set(self.loop_depth.get() + 1);
+ }
+
+ fn leave_loop(&self) {
+ self.loop_depth.set(self.loop_depth.get() - 1);
+ }
+
+ fn is_in_loop(&self) -> bool {
+ self.loop_depth.get() > 0
+ }
+}
+
+impl From<char> for Whitespace {
+ fn from(c: char) -> Self {
+ match c {
+ '+' => Self::Preserve,
+ '-' => Self::Suppress,
+ '~' => Self::Minimize,
+ _ => panic!("unsupported `Whitespace` conversion"),
+ }
+ }
+}
+
+pub(crate) fn parse<'a>(src: &'a str, syntax: &'a Syntax) -> Result<Vec<Node<'a>>, CompileError> {
+ match Node::parse(src, &State::new(syntax)) {
+ Ok((left, res)) => {
+ if !left.is_empty() {
+ Err(format!("unable to parse template:\n\n{left:?}").into())
+ } else {
+ Ok(res)
+ }
+ }
+
+ Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
+ let nom::error::Error { input, .. } = err;
+ let offset = src.len() - input.len();
+ let (source_before, source_after) = src.split_at(offset);
+
+ let source_after = match source_after.char_indices().enumerate().take(41).last() {
+ Some((40, (i, _))) => format!("{:?}...", &source_after[..i]),
+ _ => format!("{source_after:?}"),
+ };
+
+ let (row, last_line) = source_before.lines().enumerate().last().unwrap();
+ let column = last_line.chars().count();
+
+ let msg = format!(
+ "problems parsing template source at row {}, column {} near:\n{}",
+ row + 1,
+ column,
+ source_after,
+ );
+ Err(msg.into())
+ }
+
+ Err(nom::Err::Incomplete(_)) => Err("parsing incomplete".into()),
+ }
+}
+
+fn is_ws(c: char) -> bool {
+ matches!(c, ' ' | '\t' | '\r' | '\n')
+}
+
+fn not_ws(c: char) -> bool {
+ !is_ws(c)
+}
+
+fn ws<'a, O>(
+ inner: impl FnMut(&'a str) -> IResult<&'a str, O>,
+) -> impl FnMut(&'a str) -> IResult<&'a str, O> {
+ delimited(take_till(not_ws), inner, take_till(not_ws))
+}
+
+fn split_ws_parts(s: &str) -> Node<'_> {
+ let trimmed_start = s.trim_start_matches(is_ws);
+ let len_start = s.len() - trimmed_start.len();
+ let trimmed = trimmed_start.trim_end_matches(is_ws);
+ Node::Lit(&s[..len_start], trimmed, &trimmed_start[trimmed.len()..])
+}
+
+/// Skips input until `end` was found, but does not consume it.
+/// Returns tuple that would be returned when parsing `end`.
+fn skip_till<'a, O>(
+ end: impl FnMut(&'a str) -> IResult<&'a str, O>,
+) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O)> {
+ enum Next<O> {
+ IsEnd(O),
+ NotEnd(char),
+ }
+ let mut next = alt((map(end, Next::IsEnd), map(anychar, Next::NotEnd)));
+ move |start: &'a str| {
+ let mut i = start;
+ loop {
+ let (j, is_end) = next(i)?;
+ match is_end {
+ Next::IsEnd(lookahead) => return Ok((i, (j, lookahead))),
+ Next::NotEnd(_) => i = j,
+ }
+ }
+ }
+}
+
+fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
+ move |i: &'a str| -> IResult<&'a str, &'a str> {
+ let (j, v) = identifier(i)?;
+ if k == v {
+ Ok((j, v))
+ } else {
+ Err(nom::Err::Error(error_position!(i, ErrorKind::Tag)))
+ }
+ }
+}
+
+fn identifier(input: &str) -> IResult<&str, &str> {
+ recognize(pair(identifier_start, opt(identifier_tail)))(input)
+}
+
+fn identifier_start(s: &str) -> IResult<&str, &str> {
+ s.split_at_position1_complete(
+ |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'),
+ nom::error::ErrorKind::Alpha,
+ )
+}
+
+fn identifier_tail(s: &str) -> IResult<&str, &str> {
+ s.split_at_position1_complete(
+ |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'),
+ nom::error::ErrorKind::Alpha,
+ )
+}
+
+fn bool_lit(i: &str) -> IResult<&str, &str> {
+ alt((keyword("false"), keyword("true")))(i)
+}
+
+fn num_lit(i: &str) -> IResult<&str, &str> {
+ recognize(pair(digit1, opt(pair(char('.'), digit1))))(i)
+}
+
+fn str_lit(i: &str) -> IResult<&str, &str> {
+ let (i, s) = delimited(
+ char('"'),
+ opt(escaped(is_not("\\\""), '\\', anychar)),
+ char('"'),
+ )(i)?;
+ Ok((i, s.unwrap_or_default()))
+}
+
+fn char_lit(i: &str) -> IResult<&str, &str> {
+ let (i, s) = delimited(
+ char('\''),
+ opt(escaped(is_not("\\\'"), '\\', anychar)),
+ char('\''),
+ )(i)?;
+ Ok((i, s.unwrap_or_default()))
+}
+
+fn nested_parenthesis(i: &str) -> IResult<&str, ()> {
+ let mut nested = 0;
+ let mut last = 0;
+ let mut in_str = false;
+ let mut escaped = false;
+
+ for (i, b) in i.chars().enumerate() {
+ if !(b == '(' || b == ')') || !in_str {
+ match b {
+ '(' => nested += 1,
+ ')' => {
+ if nested == 0 {
+ last = i;
+ break;
+ }
+ nested -= 1;
+ }
+ '"' => {
+ if in_str {
+ if !escaped {
+ in_str = false;
+ }
+ } else {
+ in_str = true;
+ }
+ }
+ '\\' => {
+ escaped = !escaped;
+ }
+ _ => (),
+ }
+ }
+
+ if escaped && b != '\\' {
+ escaped = false;
+ }
+ }
+
+ if nested == 0 {
+ Ok((&i[last..], ()))
+ } else {
+ Err(nom::Err::Error(error_position!(
+ i,
+ ErrorKind::SeparatedNonEmptyList
+ )))
+ }
+}
+
+fn path(i: &str) -> IResult<&str, Vec<&str>> {
+ let root = opt(value("", ws(tag("::"))));
+ let tail = separated_list1(ws(tag("::")), identifier);
+
+ match tuple((root, identifier, ws(tag("::")), tail))(i) {
+ Ok((i, (root, start, _, rest))) => {
+ let mut path = Vec::new();
+ path.extend(root);
+ path.push(start);
+ path.extend(rest);
+ Ok((i, path))
+ }
+ Err(err) => {
+ if let Ok((i, name)) = identifier(i) {
+ // The returned identifier can be assumed to be path if:
+ // - Contains both a lowercase and uppercase character, i.e. a type name like `None`
+ // - Doesn't contain any lowercase characters, i.e. it's a constant
+ // In short, if it contains any uppercase characters it's a path.
+ if name.contains(char::is_uppercase) {
+ return Ok((i, vec![name]));
+ }
+ }
+
+ // If `identifier()` fails then just return the original error
+ Err(err)
+ }
+ }
+}
+
+fn take_content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let p_start = alt((
+ tag(s.syntax.block_start.as_str()),
+ tag(s.syntax.comment_start.as_str()),
+ tag(s.syntax.expr_start.as_str()),
+ ));
+
+ let (i, _) = not(eof)(i)?;
+ let (i, content) = opt(recognize(skip_till(p_start)))(i)?;
+ let (i, content) = match content {
+ Some("") => {
+ // {block,comment,expr}_start follows immediately.
+ return Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil)));
+ }
+ Some(content) => (i, content),
+ None => ("", i), // there is no {block,comment,expr}_start: take everything
+ };
+ Ok((i, split_ws_parts(content)))
+}
+
+fn tag_block_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.block_start.as_str())(i)
+}
+
+fn tag_block_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.block_end.as_str())(i)
+}
+
+fn tag_comment_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.comment_start.as_str())(i)
+}
+
+fn tag_comment_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.comment_end.as_str())(i)
+}
+
+fn tag_expr_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.expr_start.as_str())(i)
+}
+
+fn tag_expr_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ tag(s.syntax.expr_end.as_str())(i)
+}
diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs
new file mode 100644
index 0000000..3bd30d8
--- /dev/null
+++ b/askama_derive/src/parser/node.rs
@@ -0,0 +1,671 @@
+use std::str;
+
+use nom::branch::alt;
+use nom::bytes::complete::{tag, take_until};
+use nom::character::complete::char;
+use nom::combinator::{complete, consumed, cut, map, opt, peek, value};
+use nom::error::{Error, ErrorKind};
+use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
+use nom::sequence::{delimited, pair, preceded, terminated, tuple};
+use nom::{error_position, IResult};
+
+use super::{
+ bool_lit, char_lit, identifier, keyword, num_lit, path, skip_till, split_ws_parts, str_lit,
+ tag_block_end, tag_block_start, tag_comment_end, tag_comment_start, tag_expr_end,
+ tag_expr_start, take_content, ws, Expr, State,
+};
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum Node<'a> {
+ Lit(&'a str, &'a str, &'a str),
+ Comment(Ws),
+ Expr(Ws, Expr<'a>),
+ Call(Ws, Option<&'a str>, &'a str, Vec<Expr<'a>>),
+ LetDecl(Ws, Target<'a>),
+ Let(Ws, Target<'a>, Expr<'a>),
+ Cond(Vec<Cond<'a>>, Ws),
+ Match(Ws, Expr<'a>, Vec<When<'a>>, Ws),
+ Loop(Loop<'a>),
+ Extends(&'a str),
+ BlockDef(Ws, &'a str, Vec<Node<'a>>, Ws),
+ Include(Ws, &'a str),
+ Import(Ws, &'a str, &'a str),
+ Macro(&'a str, Macro<'a>),
+ Raw(Ws, &'a str, &'a str, &'a str, Ws),
+ Break(Ws),
+ Continue(Ws),
+}
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum Target<'a> {
+ Name(&'a str),
+ Tuple(Vec<&'a str>, Vec<Target<'a>>),
+ Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
+ NumLit(&'a str),
+ StrLit(&'a str),
+ CharLit(&'a str),
+ BoolLit(&'a str),
+ Path(Vec<&'a str>),
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(crate) enum Whitespace {
+ Preserve,
+ Suppress,
+ Minimize,
+}
+
+#[derive(Debug, PartialEq)]
+pub(crate) struct Loop<'a> {
+ pub(crate) ws1: Ws,
+ pub(crate) var: Target<'a>,
+ pub(crate) iter: Expr<'a>,
+ pub(crate) cond: Option<Expr<'a>>,
+ pub(crate) body: Vec<Node<'a>>,
+ pub(crate) ws2: Ws,
+ pub(crate) else_block: Vec<Node<'a>>,
+ pub(crate) ws3: Ws,
+}
+
+pub(crate) type When<'a> = (Ws, Target<'a>, Vec<Node<'a>>);
+
+#[derive(Debug, PartialEq)]
+pub(crate) struct Macro<'a> {
+ pub(crate) ws1: Ws,
+ pub(crate) args: Vec<&'a str>,
+ pub(crate) nodes: Vec<Node<'a>>,
+ pub(crate) ws2: Ws,
+}
+
+/// First field is "minus/plus sign was used on the left part of the item".
+///
+/// Second field is "minus/plus sign was used on the right part of the item".
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(crate) struct Ws(pub(crate) Option<Whitespace>, pub(crate) Option<Whitespace>);
+
+pub(crate) type Cond<'a> = (Ws, Option<CondTest<'a>>, Vec<Node<'a>>);
+
+#[derive(Debug, PartialEq)]
+pub(crate) struct CondTest<'a> {
+ pub(crate) target: Option<Target<'a>>,
+ pub(crate) expr: Expr<'a>,
+}
+
+impl Node<'_> {
+ pub(super) fn parse<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
+ parse_template(i, s)
+ }
+}
+
+impl Target<'_> {
+ pub(super) fn parse(i: &str) -> IResult<&str, Target<'_>> {
+ target(i)
+ }
+}
+
+fn expr_handle_ws(i: &str) -> IResult<&str, Whitespace> {
+ alt((char('-'), char('+'), char('~')))(i).map(|(s, r)| (s, Whitespace::from(r)))
+}
+
+fn parameters(i: &str) -> IResult<&str, Vec<&str>> {
+ delimited(
+ ws(char('(')),
+ separated_list0(char(','), ws(identifier)),
+ ws(char(')')),
+ )(i)
+}
+
+fn block_call(i: &str) -> IResult<&str, Node<'_>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("call")),
+ cut(tuple((
+ opt(tuple((ws(identifier), ws(tag("::"))))),
+ ws(identifier),
+ ws(Expr::parse_arguments),
+ opt(expr_handle_ws),
+ ))),
+ ));
+ let (i, (pws, _, (scope, name, args, nws))) = p(i)?;
+ let scope = scope.map(|(scope, _)| scope);
+ Ok((i, Node::Call(Ws(pws, nws), scope, name, args)))
+}
+
+fn cond_if(i: &str) -> IResult<&str, CondTest<'_>> {
+ let mut p = preceded(
+ ws(keyword("if")),
+ cut(tuple((
+ opt(delimited(
+ ws(alt((keyword("let"), keyword("set")))),
+ ws(Target::parse),
+ ws(char('=')),
+ )),
+ ws(Expr::parse),
+ ))),
+ );
+ let (i, (target, expr)) = p(i)?;
+ Ok((i, CondTest { target, expr }))
+}
+
+fn cond_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Cond<'a>> {
+ let mut p = tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("else")),
+ cut(tuple((
+ opt(cond_if),
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(|i| parse_template(i, s)),
+ ))),
+ ));
+ let (i, (_, pws, _, (cond, nws, _, block))) = p(i)?;
+ Ok((i, (Ws(pws, nws), cond, block)))
+}
+
+fn block_if<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ cond_if,
+ cut(tuple((
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(tuple((
+ |i| parse_template(i, s),
+ many0(|i| cond_block(i, s)),
+ cut(tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("endif")),
+ opt(expr_handle_ws),
+ ))),
+ ))),
+ ))),
+ ));
+ let (i, (pws1, cond, (nws1, _, (block, elifs, (_, pws2, _, nws2))))) = p(i)?;
+
+ let mut res = vec![(Ws(pws1, nws1), Some(cond), block)];
+ res.extend(elifs);
+ Ok((i, Node::Cond(res, Ws(pws2, nws2))))
+}
+
+fn match_else_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> {
+ let mut p = tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("else")),
+ cut(tuple((
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(|i| parse_template(i, s)),
+ ))),
+ ));
+ let (i, (_, pws, _, (nws, _, block))) = p(i)?;
+ Ok((i, (Ws(pws, nws), Target::Name("_"), block)))
+}
+
+fn when_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> {
+ let mut p = tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("when")),
+ cut(tuple((
+ ws(Target::parse),
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(|i| parse_template(i, s)),
+ ))),
+ ));
+ let (i, (_, pws, _, (target, nws, _, block))) = p(i)?;
+ Ok((i, (Ws(pws, nws), target, block)))
+}
+
+fn block_match<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("match")),
+ cut(tuple((
+ ws(Expr::parse),
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(tuple((
+ ws(many0(ws(value((), |i| block_comment(i, s))))),
+ many1(|i| when_block(i, s)),
+ cut(tuple((
+ opt(|i| match_else_block(i, s)),
+ cut(tuple((
+ ws(|i| tag_block_start(i, s)),
+ opt(expr_handle_ws),
+ ws(keyword("endmatch")),
+ opt(expr_handle_ws),
+ ))),
+ ))),
+ ))),
+ ))),
+ ));
+ let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?;
+
+ let mut arms = arms;
+ if let Some(arm) = else_arm {
+ arms.push(arm);
+ }
+
+ Ok((i, Node::Match(Ws(pws1, nws1), expr, arms, Ws(pws2, nws2))))
+}
+
+fn block_let(i: &str) -> IResult<&str, Node<'_>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(alt((keyword("let"), keyword("set")))),
+ cut(tuple((
+ ws(Target::parse),
+ opt(tuple((ws(char('=')), ws(Expr::parse)))),
+ opt(expr_handle_ws),
+ ))),
+ ));
+ let (i, (pws, _, (var, val, nws))) = p(i)?;
+
+ Ok((
+ i,
+ if let Some((_, val)) = val {
+ Node::Let(Ws(pws, nws), var, val)
+ } else {
+ Node::LetDecl(Ws(pws, nws), var)
+ },
+ ))
+}
+
+fn parse_loop_content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
+ s.enter_loop();
+ let result = parse_template(i, s);
+ s.leave_loop();
+ result
+}
+
+fn block_for<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let if_cond = preceded(ws(keyword("if")), cut(ws(Expr::parse)));
+ let else_block = |i| {
+ let mut p = preceded(
+ ws(keyword("else")),
+ cut(tuple((
+ opt(expr_handle_ws),
+ delimited(
+ |i| tag_block_end(i, s),
+ |i| parse_template(i, s),
+ |i| tag_block_start(i, s),
+ ),
+ opt(expr_handle_ws),
+ ))),
+ );
+ let (i, (pws, nodes, nws)) = p(i)?;
+ Ok((i, (pws, nodes, nws)))
+ };
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("for")),
+ cut(tuple((
+ ws(Target::parse),
+ ws(keyword("in")),
+ cut(tuple((
+ ws(Expr::parse),
+ opt(if_cond),
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ cut(tuple((
+ |i| parse_loop_content(i, s),
+ cut(tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ opt(else_block),
+ ws(keyword("endfor")),
+ opt(expr_handle_ws),
+ ))),
+ ))),
+ ))),
+ ))),
+ ));
+ let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) =
+ p(i)?;
+ let (nws3, else_block, pws3) = else_block.unwrap_or_default();
+ Ok((
+ i,
+ Node::Loop(Loop {
+ ws1: Ws(pws1, nws1),
+ var,
+ iter,
+ cond,
+ body,
+ ws2: Ws(pws2, nws3),
+ else_block,
+ ws3: Ws(pws3, nws2),
+ }),
+ ))
+}
+
+fn block_extends(i: &str) -> IResult<&str, Node<'_>> {
+ let (i, (_, name)) = tuple((ws(keyword("extends")), ws(str_lit)))(i)?;
+ Ok((i, Node::Extends(name)))
+}
+
+fn block_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut start = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("block")),
+ cut(tuple((ws(identifier), opt(expr_handle_ws), |i| {
+ tag_block_end(i, s)
+ }))),
+ ));
+ let (i, (pws1, _, (name, nws1, _))) = start(i)?;
+
+ let mut end = cut(tuple((
+ |i| parse_template(i, s),
+ cut(tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("endblock")),
+ cut(tuple((opt(ws(keyword(name))), opt(expr_handle_ws)))),
+ ))),
+ )));
+ let (i, (contents, (_, pws2, _, (_, nws2)))) = end(i)?;
+
+ Ok((
+ i,
+ Node::BlockDef(Ws(pws1, nws1), name, contents, Ws(pws2, nws2)),
+ ))
+}
+
+fn block_include(i: &str) -> IResult<&str, Node<'_>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("include")),
+ cut(pair(ws(str_lit), opt(expr_handle_ws))),
+ ));
+ let (i, (pws, _, (name, nws))) = p(i)?;
+ Ok((i, Node::Include(Ws(pws, nws), name)))
+}
+
+fn block_import(i: &str) -> IResult<&str, Node<'_>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("import")),
+ cut(tuple((
+ ws(str_lit),
+ ws(keyword("as")),
+ cut(pair(ws(identifier), opt(expr_handle_ws))),
+ ))),
+ ));
+ let (i, (pws, _, (name, _, (scope, nws)))) = p(i)?;
+ Ok((i, Node::Import(Ws(pws, nws), name, scope)))
+}
+
+fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut start = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("macro")),
+ cut(tuple((
+ ws(identifier),
+ ws(parameters),
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ ))),
+ ));
+ let (i, (pws1, _, (name, params, nws1, _))) = start(i)?;
+
+ let mut end = cut(tuple((
+ |i| parse_template(i, s),
+ cut(tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("endmacro")),
+ cut(tuple((opt(ws(keyword(name))), opt(expr_handle_ws)))),
+ ))),
+ )));
+ let (i, (contents, (_, pws2, _, (_, nws2)))) = end(i)?;
+
+ assert_ne!(name, "super", "invalid macro name 'super'");
+
+ Ok((
+ i,
+ Node::Macro(
+ name,
+ Macro {
+ ws1: Ws(pws1, nws1),
+ args: params,
+ nodes: contents,
+ ws2: Ws(pws2, nws2),
+ },
+ ),
+ ))
+}
+
+fn block_raw<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let endraw = tuple((
+ |i| tag_block_start(i, s),
+ opt(expr_handle_ws),
+ ws(keyword("endraw")),
+ opt(expr_handle_ws),
+ peek(|i| tag_block_end(i, s)),
+ ));
+
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("raw")),
+ cut(tuple((
+ opt(expr_handle_ws),
+ |i| tag_block_end(i, s),
+ consumed(skip_till(endraw)),
+ ))),
+ ));
+
+ let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?;
+ let (lws, val, rws) = match split_ws_parts(contents) {
+ Node::Lit(lws, val, rws) => (lws, val, rws),
+ _ => unreachable!(),
+ };
+ let ws1 = Ws(pws1, nws1);
+ let ws2 = Ws(pws2, nws2);
+ Ok((i, Node::Raw(ws1, lws, val, rws, ws2)))
+}
+
+fn break_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("break")),
+ opt(expr_handle_ws),
+ ));
+ let (j, (pws, _, nws)) = p(i)?;
+ if !s.is_in_loop() {
+ return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag)));
+ }
+ Ok((j, Node::Break(Ws(pws, nws))))
+}
+
+fn continue_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ opt(expr_handle_ws),
+ ws(keyword("continue")),
+ opt(expr_handle_ws),
+ ));
+ let (j, (pws, _, nws)) = p(i)?;
+ if !s.is_in_loop() {
+ return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag)));
+ }
+ Ok((j, Node::Continue(Ws(pws, nws))))
+}
+
+fn block_node<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ |i| tag_block_start(i, s),
+ alt((
+ block_call,
+ block_let,
+ |i| block_if(i, s),
+ |i| block_for(i, s),
+ |i| block_match(i, s),
+ block_extends,
+ block_include,
+ block_import,
+ |i| block_block(i, s),
+ |i| block_macro(i, s),
+ |i| block_raw(i, s),
+ |i| break_statement(i, s),
+ |i| continue_statement(i, s),
+ )),
+ cut(|i| tag_block_end(i, s)),
+ ));
+ let (i, (_, contents, _)) = p(i)?;
+ Ok((i, contents))
+}
+
+fn block_comment_body<'a>(mut i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> {
+ let mut level = 0;
+ loop {
+ let (end, tail) = take_until(s.syntax.comment_end.as_str())(i)?;
+ match take_until::<_, _, Error<_>>(s.syntax.comment_start.as_str())(i) {
+ Ok((start, _)) if start.as_ptr() < end.as_ptr() => {
+ level += 1;
+ i = &start[2..];
+ }
+ _ if level > 0 => {
+ level -= 1;
+ i = &end[2..];
+ }
+ _ => return Ok((end, tail)),
+ }
+ }
+}
+
+fn block_comment<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ |i| tag_comment_start(i, s),
+ cut(tuple((
+ opt(expr_handle_ws),
+ |i| block_comment_body(i, s),
+ |i| tag_comment_end(i, s),
+ ))),
+ ));
+ let (i, (_, (pws, tail, _))) = p(i)?;
+ let nws = if tail.ends_with('-') {
+ Some(Whitespace::Suppress)
+ } else if tail.ends_with('+') {
+ Some(Whitespace::Preserve)
+ } else if tail.ends_with('~') {
+ Some(Whitespace::Minimize)
+ } else {
+ None
+ };
+ Ok((i, Node::Comment(Ws(pws, nws))))
+}
+
+fn expr_node<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let mut p = tuple((
+ |i| tag_expr_start(i, s),
+ cut(tuple((
+ opt(expr_handle_ws),
+ ws(Expr::parse),
+ opt(expr_handle_ws),
+ |i| tag_expr_end(i, s),
+ ))),
+ ));
+ let (i, (_, (pws, expr, nws, _))) = p(i)?;
+ Ok((i, Node::Expr(Ws(pws, nws), expr)))
+}
+
+fn parse_template<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec<Node<'a>>> {
+ many0(alt((
+ complete(|i| take_content(i, s)),
+ complete(|i| block_comment(i, s)),
+ complete(|i| expr_node(i, s)),
+ complete(|i| block_node(i, s)),
+ )))(i)
+}
+
+fn variant_lit(i: &str) -> IResult<&str, Target<'_>> {
+ alt((
+ map(str_lit, Target::StrLit),
+ map(char_lit, Target::CharLit),
+ map(num_lit, Target::NumLit),
+ map(bool_lit, Target::BoolLit),
+ ))(i)
+}
+
+fn target(i: &str) -> IResult<&str, Target<'_>> {
+ let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
+ let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some());
+ let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
+
+ let (i, lit) = opt(variant_lit)(i)?;
+ if let Some(lit) = lit {
+ return Ok((i, lit));
+ }
+
+ // match tuples and unused parentheses
+ let (i, target_is_tuple) = opt_opening_paren(i)?;
+ if target_is_tuple {
+ let (i, is_empty_tuple) = opt_closing_paren(i)?;
+ if is_empty_tuple {
+ return Ok((i, Target::Tuple(Vec::new(), Vec::new())));
+ }
+
+ let (i, first_target) = target(i)?;
+ let (i, is_unused_paren) = opt_closing_paren(i)?;
+ if is_unused_paren {
+ return Ok((i, first_target));
+ }
+
+ let mut targets = vec![first_target];
+ let (i, _) = cut(tuple((
+ fold_many0(
+ preceded(ws(char(',')), target),
+ || (),
+ |_, target| {
+ targets.push(target);
+ },
+ ),
+ opt(ws(char(','))),
+ ws(cut(char(')'))),
+ )))(i)?;
+ return Ok((i, Target::Tuple(Vec::new(), targets)));
+ }
+
+ // match structs
+ let (i, path) = opt(path)(i)?;
+ if let Some(path) = path {
+ let i_before_matching_with = i;
+ let (i, _) = opt(ws(keyword("with")))(i)?;
+
+ let (i, is_unnamed_struct) = opt_opening_paren(i)?;
+ if is_unnamed_struct {
+ let (i, targets) = alt((
+ map(char(')'), |_| Vec::new()),
+ terminated(
+ cut(separated_list1(ws(char(',')), target)),
+ pair(opt(ws(char(','))), ws(cut(char(')')))),
+ ),
+ ))(i)?;
+ return Ok((i, Target::Tuple(path, targets)));
+ }
+
+ let (i, is_named_struct) = opt_opening_brace(i)?;
+ if is_named_struct {
+ let (i, targets) = alt((
+ map(char('}'), |_| Vec::new()),
+ terminated(
+ cut(separated_list1(ws(char(',')), named_target)),
+ pair(opt(ws(char(','))), ws(cut(char('}')))),
+ ),
+ ))(i)?;
+ return Ok((i, Target::Struct(path, targets)));
+ }
+
+ return Ok((i_before_matching_with, Target::Path(path)));
+ }
+
+ // neither literal nor struct nor path
+ map(identifier, Target::Name)(i)
+}
+
+fn named_target(i: &str) -> IResult<&str, (&str, Target<'_>)> {
+ let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), target)))(i)?;
+ Ok((i, (src, target.unwrap_or(Target::Name(src)))))
+}
diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs
new file mode 100644
index 0000000..045bf28
--- /dev/null
+++ b/askama_derive/src/parser/tests.rs
@@ -0,0 +1,668 @@
+use crate::config::Syntax;
+use crate::parser::{Expr, Node, Whitespace, Ws};
+
+fn check_ws_split(s: &str, res: &(&str, &str, &str)) {
+ match super::split_ws_parts(s) {
+ Node::Lit(lws, s, rws) => {
+ assert_eq!(lws, res.0);
+ assert_eq!(s, res.1);
+ assert_eq!(rws, res.2);
+ }
+ _ => {
+ panic!("fail");
+ }
+ }
+}
+
+#[test]
+fn test_ws_splitter() {
+ check_ws_split("", &("", "", ""));
+ check_ws_split("a", &("", "a", ""));
+ check_ws_split("\ta", &("\t", "a", ""));
+ check_ws_split("b\n", &("", "b", "\n"));
+ check_ws_split(" \t\r\n", &(" \t\r\n", "", ""));
+}
+
+#[test]
+#[should_panic]
+fn test_invalid_block() {
+ super::parse("{% extend \"blah\" %}", &Syntax::default()).unwrap();
+}
+
+#[test]
+fn test_parse_filter() {
+ use Expr::*;
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ strvar|e }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Filter("e", vec![Var("strvar")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ 2|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Filter("abs", vec![NumLit("2")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ -2|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter("abs", vec![Unary("-", NumLit("2").into())]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1 - 2)|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter(
+ "abs",
+ vec![Group(
+ BinOp("-", NumLit("1").into(), NumLit("2").into()).into()
+ )]
+ ),
+ )],
+ );
+}
+
+#[test]
+fn test_parse_numbers() {
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ 2 }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::NumLit("2"),)],
+ );
+ assert_eq!(
+ super::parse("{{ 2.5 }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::NumLit("2.5"),)],
+ );
+}
+
+#[test]
+fn test_parse_var() {
+ let s = Syntax::default();
+
+ assert_eq!(
+ super::parse("{{ foo }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Var("foo"))],
+ );
+ assert_eq!(
+ super::parse("{{ foo_bar }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Var("foo_bar"))],
+ );
+
+ assert_eq!(
+ super::parse("{{ none }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Var("none"))],
+ );
+}
+
+#[test]
+fn test_parse_const() {
+ let s = Syntax::default();
+
+ assert_eq!(
+ super::parse("{{ FOO }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO"]))],
+ );
+ assert_eq!(
+ super::parse("{{ FOO_BAR }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO_BAR"]))],
+ );
+
+ assert_eq!(
+ super::parse("{{ NONE }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Path(vec!["NONE"]))],
+ );
+}
+
+#[test]
+fn test_parse_path() {
+ let s = Syntax::default();
+
+ assert_eq!(
+ super::parse("{{ None }}", &s).unwrap(),
+ vec![Node::Expr(Ws(None, None), Expr::Path(vec!["None"]))],
+ );
+ assert_eq!(
+ super::parse("{{ Some(123) }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Path(vec!["Some"])),
+ vec![Expr::NumLit("123")]
+ ),
+ )],
+ );
+
+ assert_eq!(
+ super::parse("{{ Ok(123) }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(Box::new(Expr::Path(vec!["Ok"])), vec![Expr::NumLit("123")]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ Err(123) }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(Box::new(Expr::Path(vec!["Err"])), vec![Expr::NumLit("123")]),
+ )],
+ );
+}
+
+#[test]
+fn test_parse_var_call() {
+ assert_eq!(
+ super::parse("{{ function(\"123\", 3) }}", &Syntax::default()).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Var("function")),
+ vec![Expr::StrLit("123"), Expr::NumLit("3")]
+ ),
+ )],
+ );
+}
+
+#[test]
+fn test_parse_path_call() {
+ let s = Syntax::default();
+
+ assert_eq!(
+ super::parse("{{ Option::None }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Path(vec!["Option", "None"])
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ Option::Some(123) }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Path(vec!["Option", "Some"])),
+ vec![Expr::NumLit("123")],
+ ),
+ )],
+ );
+
+ assert_eq!(
+ super::parse("{{ self::function(\"123\", 3) }}", &s).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Path(vec!["self", "function"])),
+ vec![Expr::StrLit("123"), Expr::NumLit("3")],
+ ),
+ )],
+ );
+}
+
+#[test]
+fn test_parse_root_path() {
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ std::string::String::new() }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Path(vec!["std", "string", "String", "new"])),
+ vec![]
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ ::std::string::String::new() }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Expr::Call(
+ Box::new(Expr::Path(vec!["", "std", "string", "String", "new"])),
+ vec![]
+ ),
+ )],
+ );
+}
+
+#[test]
+fn change_delimiters_parse_filter() {
+ let syntax = Syntax {
+ expr_start: "{=".to_owned(),
+ expr_end: "=}".to_owned(),
+ ..Syntax::default()
+ };
+
+ super::parse("{= strvar|e =}", &syntax).unwrap();
+}
+
+#[test]
+fn test_precedence() {
+ use Expr::*;
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ a + b == c }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "==",
+ BinOp("+", Var("a").into(), Var("b").into()).into(),
+ Var("c").into(),
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a + b * c - d / e }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "-",
+ BinOp(
+ "+",
+ Var("a").into(),
+ BinOp("*", Var("b").into(), Var("c").into()).into(),
+ )
+ .into(),
+ BinOp("/", Var("d").into(), Var("e").into()).into(),
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a * (b + c) / -d }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "/",
+ BinOp(
+ "*",
+ Var("a").into(),
+ Group(BinOp("+", Var("b").into(), Var("c").into()).into()).into()
+ )
+ .into(),
+ Unary("-", Var("d").into()).into()
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a || b && c || d && e }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "||",
+ BinOp(
+ "||",
+ Var("a").into(),
+ BinOp("&&", Var("b").into(), Var("c").into()).into(),
+ )
+ .into(),
+ BinOp("&&", Var("d").into(), Var("e").into()).into(),
+ )
+ )],
+ );
+}
+
+#[test]
+fn test_associativity() {
+ use Expr::*;
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ a + b + c }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "+",
+ BinOp("+", Var("a").into(), Var("b").into()).into(),
+ Var("c").into()
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a * b * c }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "*",
+ BinOp("*", Var("a").into(), Var("b").into()).into(),
+ Var("c").into()
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a && b && c }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "&&",
+ BinOp("&&", Var("a").into(), Var("b").into()).into(),
+ Var("c").into()
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a + b - c + d }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "+",
+ BinOp(
+ "-",
+ BinOp("+", Var("a").into(), Var("b").into()).into(),
+ Var("c").into()
+ )
+ .into(),
+ Var("d").into()
+ )
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a == b != c > d > e == f }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "==",
+ BinOp(
+ ">",
+ BinOp(
+ ">",
+ BinOp(
+ "!=",
+ BinOp("==", Var("a").into(), Var("b").into()).into(),
+ Var("c").into()
+ )
+ .into(),
+ Var("d").into()
+ )
+ .into(),
+ Var("e").into()
+ )
+ .into(),
+ Var("f").into()
+ )
+ )],
+ );
+}
+
+#[test]
+fn test_odd_calls() {
+ use Expr::*;
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ a[b](c) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Call(
+ Box::new(Index(Box::new(Var("a")), Box::new(Var("b")))),
+ vec![Var("c")],
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (a + b)(c) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Call(
+ Box::new(Group(Box::new(BinOp(
+ "+",
+ Box::new(Var("a")),
+ Box::new(Var("b"))
+ )))),
+ vec![Var("c")],
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ a + b(c) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "+",
+ Box::new(Var("a")),
+ Box::new(Call(Box::new(Var("b")), vec![Var("c")])),
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (-a)(b) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Call(
+ Box::new(Group(Box::new(Unary("-", Box::new(Var("a")))))),
+ vec![Var("b")],
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ -a(b) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Unary("-", Box::new(Call(Box::new(Var("a")), vec![Var("b")])),),
+ )],
+ );
+}
+
+#[test]
+fn test_parse_comments() {
+ let s = &Syntax::default();
+
+ assert_eq!(
+ super::parse("{##}", s).unwrap(),
+ vec![Node::Comment(Ws(None, None))],
+ );
+ assert_eq!(
+ super::parse("{#- #}", s).unwrap(),
+ vec![Node::Comment(Ws(Some(Whitespace::Suppress), None))],
+ );
+ assert_eq!(
+ super::parse("{# -#}", s).unwrap(),
+ vec![Node::Comment(Ws(None, Some(Whitespace::Suppress)))],
+ );
+ assert_eq!(
+ super::parse("{#--#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Suppress),
+ Some(Whitespace::Suppress)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#- foo\n bar -#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Suppress),
+ Some(Whitespace::Suppress)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#- foo\n {#- bar\n -#} baz -#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Suppress),
+ Some(Whitespace::Suppress)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#+ #}", s).unwrap(),
+ vec![Node::Comment(Ws(Some(Whitespace::Preserve), None))],
+ );
+ assert_eq!(
+ super::parse("{# +#}", s).unwrap(),
+ vec![Node::Comment(Ws(None, Some(Whitespace::Preserve)))],
+ );
+ assert_eq!(
+ super::parse("{#++#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Preserve),
+ Some(Whitespace::Preserve)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#+ foo\n bar +#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Preserve),
+ Some(Whitespace::Preserve)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#+ foo\n {#+ bar\n +#} baz -+#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Preserve),
+ Some(Whitespace::Preserve)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#~ #}", s).unwrap(),
+ vec![Node::Comment(Ws(Some(Whitespace::Minimize), None))],
+ );
+ assert_eq!(
+ super::parse("{# ~#}", s).unwrap(),
+ vec![Node::Comment(Ws(None, Some(Whitespace::Minimize)))],
+ );
+ assert_eq!(
+ super::parse("{#~~#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Minimize),
+ Some(Whitespace::Minimize)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#~ foo\n bar ~#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Minimize),
+ Some(Whitespace::Minimize)
+ ))],
+ );
+ assert_eq!(
+ super::parse("{#~ foo\n {#~ bar\n ~#} baz -~#}", s).unwrap(),
+ vec![Node::Comment(Ws(
+ Some(Whitespace::Minimize),
+ Some(Whitespace::Minimize)
+ ))],
+ );
+
+ assert_eq!(
+ super::parse("{# foo {# bar #} {# {# baz #} qux #} #}", s).unwrap(),
+ vec![Node::Comment(Ws(None, None))],
+ );
+}
+
+#[test]
+fn test_parse_tuple() {
+ use super::Expr::*;
+ let syntax = Syntax::default();
+ assert_eq!(
+ super::parse("{{ () }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Tuple(vec![]),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1) }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Group(Box::new(NumLit("1"))),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1,) }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1, ) }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1 ,) }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1 , ) }}", &syntax).unwrap(),
+ vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)],
+ );
+ assert_eq!(
+ super::parse("{{ (1, 2) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Tuple(vec![NumLit("1"), NumLit("2")]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1, 2,) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Tuple(vec![NumLit("1"), NumLit("2")]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1, 2, 3) }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Tuple(vec![NumLit("1"), NumLit("2"), NumLit("3")]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ ()|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter("abs", vec![Tuple(vec![])]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ () | abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp("|", Box::new(Tuple(vec![])), Box::new(Var("abs"))),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1)|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter("abs", vec![Group(Box::new(NumLit("1")))]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1) | abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "|",
+ Box::new(Group(Box::new(NumLit("1")))),
+ Box::new(Var("abs"))
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1,)|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter("abs", vec![Tuple(vec![NumLit("1")])]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1,) | abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "|",
+ Box::new(Tuple(vec![NumLit("1")])),
+ Box::new(Var("abs"))
+ ),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1, 2)|abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ Filter("abs", vec![Tuple(vec![NumLit("1"), NumLit("2")])]),
+ )],
+ );
+ assert_eq!(
+ super::parse("{{ (1, 2) | abs }}", &syntax).unwrap(),
+ vec![Node::Expr(
+ Ws(None, None),
+ BinOp(
+ "|",
+ Box::new(Tuple(vec![NumLit("1"), NumLit("2")])),
+ Box::new(Var("abs"))
+ ),
+ )],
+ );
+}
+
+#[test]
+fn test_missing_space_after_kw() {
+ let syntax = Syntax::default();
+ let err = super::parse("{%leta=b%}", &syntax).unwrap_err();
+ assert!(matches!(
+ &*err.msg,
+ "unable to parse template:\n\n\"{%leta=b%}\""
+ ));
+}