diff options
| author | 2023-01-30 12:52:50 +0100 | |
|---|---|---|
| committer | 2023-01-30 14:19:46 +0100 | |
| commit | 7b6f1df433a7f11612608644342b898cd6be8ff5 (patch) | |
| tree | 478aafb88f9814fe235ee983213da5aa74a13e0a /askama_derive/src/parser | |
| parent | 63b98ec7d379768d771966c6aa44de20862e0994 (diff) | |
| download | askama-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.rs | 1940 | ||||
| -rw-r--r-- | askama_derive/src/parser/expr.rs | 346 | ||||
| -rw-r--r-- | askama_derive/src/parser/mod.rs | 314 | ||||
| -rw-r--r-- | askama_derive/src/parser/node.rs | 671 | ||||
| -rw-r--r-- | askama_derive/src/parser/tests.rs | 668 | 
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%}\"" +    )); +} | 
