diff options
Diffstat (limited to 'askama_parser')
| -rw-r--r-- | askama_parser/Cargo.toml | 17 | ||||
| -rw-r--r-- | askama_parser/src/expr.rs | 285 | ||||
| -rw-r--r-- | askama_parser/src/lib.rs | 384 | ||||
| -rw-r--r-- | askama_parser/src/node.rs | 674 | ||||
| -rw-r--r-- | askama_parser/src/tests.rs | 712 | 
5 files changed, 2072 insertions, 0 deletions
| diff --git a/askama_parser/Cargo.toml b/askama_parser/Cargo.toml new file mode 100644 index 0000000..8af58df --- /dev/null +++ b/askama_parser/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "askama_parser" +version = "0.1.0" +description = "Parser for Askama templates" +documentation = "https://docs.rs/askama" +keywords = ["markup", "template", "jinja2", "html"] +categories = ["template-engine"] +homepage = "https://github.com/djc/askama" +repository = "https://github.com/djc/askama" +license = "MIT OR Apache-2.0" +workspace = ".." +readme = "README.md" +edition = "2021" +rust-version = "1.58" + +[dependencies] +nom = { version = "7", default-features = false, features = ["alloc"] } diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs new file mode 100644 index 0000000..03620e7 --- /dev/null +++ b/askama_parser/src/expr.rs @@ -0,0 +1,285 @@ +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::error::ErrorKind; +use nom::multi::{fold_many0, many0, separated_list0, separated_list1}; +use nom::sequence::{delimited, pair, preceded, terminated, tuple}; +use nom::{error_position, IResult}; + +use super::{ +    bool_lit, char_lit, identifier, nested_parenthesis, not_ws, num_lit, path, str_lit, ws, +}; + +#[derive(Debug, PartialEq)] +pub 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(Vec<&'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) +    } +} + +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_array_lit, +        expr_var, +        expr_group, +    ))(i) +} + +enum Suffix<'a> { +    Attr(&'a str), +    Index(Expr<'a>), +    Call(Vec<Expr<'a>>), +    // The value is the arguments of the macro call. +    MacroCall(&'a str), +    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_macro(i: &str) -> IResult<&str, Suffix<'_>> { +    preceded( +        pair(ws(char('!')), char('(')), +        cut(terminated( +            map(recognize(nested_parenthesis), Suffix::MacroCall), +            char(')'), +        )), +    )(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, expr_macro, +        )))(i)?; +        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()), +            Some(Suffix::MacroCall(args)) => match expr { +                Expr::Path(path) => expr = Expr::RustMacro(path, args), +                Expr::Var(name) => expr = Expr::RustMacro(vec![name], args), +                _ => return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))), +            }, +            None => break, +        } +        i = j; +    } +    Ok((i, expr)) +} + +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_parser/src/lib.rs b/askama_parser/src/lib.rs new file mode 100644 index 0000000..336004e --- /dev/null +++ b/askama_parser/src/lib.rs @@ -0,0 +1,384 @@ +#![deny(unreachable_pub)] +#![deny(elided_lifetimes_in_paths)] + +use std::cell::Cell; +use std::{fmt, 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 use self::expr::Expr; +pub use self::node::{Cond, CondTest, Loop, Macro, Node, Target, When, Whitespace, Ws}; + +mod expr; +mod node; +#[cfg(test)] +mod tests; + +struct State<'a> { +    syntax: &'a Syntax<'a>, +    loop_depth: Cell<usize>, +} + +impl<'a> State<'a> { +    fn new(syntax: &'a Syntax<'a>) -> State<'a> { +        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"), +        } +    } +} + +mod _parsed { +    use std::mem; + +    use super::{parse, Node, ParseError, Syntax}; + +    pub struct Parsed { +        #[allow(dead_code)] +        source: String, +        nodes: Vec<Node<'static>>, +    } + +    impl Parsed { +        pub fn new(source: String, syntax: &Syntax<'_>) -> Result<Self, ParseError> { +            // Self-referential borrowing: `self` will keep the source alive as `String`, +            // internally we will transmute it to `&'static str` to satisfy the compiler. +            // However, we only expose the nodes with a lifetime limited to `self`. +            let src = unsafe { mem::transmute::<&str, &'static str>(source.as_str()) }; +            let nodes = match parse(src, syntax) { +                Ok(nodes) => nodes, +                Err(e) => return Err(e), +            }; + +            Ok(Self { source, nodes }) +        } + +        // The return value's lifetime must be limited to `self` to uphold the unsafe invariant. +        pub fn nodes(&self) -> &[Node<'_>] { +            &self.nodes +        } +    } +} + +pub use _parsed::Parsed; + +pub fn parse<'a>(src: &'a str, syntax: &Syntax<'_>) -> Result<Vec<Node<'a>>, ParseError> { +    match Node::parse(src, &State::new(syntax)) { +        Ok((left, res)) => { +            if !left.is_empty() { +                Err(ParseError(format!("unable to parse template:\n\n{left:?}"))) +            } 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(ParseError(msg)) +        } + +        Err(nom::Err::Incomplete(_)) => Err(ParseError("parsing incomplete".into())), +    } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseError(String); + +impl std::error::Error for ParseError {} + +impl fmt::Display for ParseError { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        self.0.fmt(f) +    } +} + +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), +        tag(s.syntax.comment_start), +        tag(s.syntax.expr_start), +    )); + +    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)(i) +} + +fn tag_block_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { +    tag(s.syntax.block_end)(i) +} + +fn tag_comment_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { +    tag(s.syntax.comment_start)(i) +} + +fn tag_comment_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { +    tag(s.syntax.comment_end)(i) +} + +fn tag_expr_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { +    tag(s.syntax.expr_start)(i) +} + +fn tag_expr_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { +    tag(s.syntax.expr_end)(i) +} + +#[derive(Debug)] +pub struct Syntax<'a> { +    pub block_start: &'a str, +    pub block_end: &'a str, +    pub expr_start: &'a str, +    pub expr_end: &'a str, +    pub comment_start: &'a str, +    pub comment_end: &'a str, +} + +impl Default for Syntax<'static> { +    fn default() -> Self { +        Self { +            block_start: "{%", +            block_end: "%}", +            expr_start: "{{", +            expr_end: "}}", +            comment_start: "{#", +            comment_end: "#}", +        } +    } +} diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs new file mode 100644 index 0000000..8719d38 --- /dev/null +++ b/askama_parser/src/node.rs @@ -0,0 +1,674 @@ +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 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 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 enum Whitespace { +    Preserve, +    Suppress, +    Minimize, +} + +#[derive(Debug, PartialEq)] +pub struct Loop<'a> { +    pub ws1: Ws, +    pub var: Target<'a>, +    pub iter: Expr<'a>, +    pub cond: Option<Expr<'a>>, +    pub body: Vec<Node<'a>>, +    pub ws2: Ws, +    pub else_block: Vec<Node<'a>>, +    pub ws3: Ws, +} + +pub type When<'a> = (Ws, Target<'a>, Vec<Node<'a>>); + +#[derive(Debug, PartialEq)] +pub struct Macro<'a> { +    pub ws1: Ws, +    pub args: Vec<&'a str>, +    pub nodes: Vec<Node<'a>>, +    pub 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 struct Ws(pub Option<Whitespace>, pub Option<Whitespace>); + +pub type Cond<'a> = (Ws, Option<CondTest<'a>>, Vec<Node<'a>>); + +#[derive(Debug, PartialEq)] +pub struct CondTest<'a> { +    pub target: Option<Target<'a>>, +    pub 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), +            opt(ws(Expr::parse_arguments)), +            opt(expr_handle_ws), +        ))), +    )); +    let (i, (pws, _, (scope, name, args, nws))) = p(i)?; +    let scope = scope.map(|(scope, _)| scope); +    let args = args.unwrap_or_default(); +    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), +            opt(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'"); + +    let params = params.unwrap_or_default(); + +    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)(i)?; +        match take_until::<_, _, Error<_>>(s.syntax.comment_start)(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_parser/src/tests.rs b/askama_parser/src/tests.rs new file mode 100644 index 0000000..0e785eb --- /dev/null +++ b/askama_parser/src/tests.rs @@ -0,0 +1,712 @@ +use super::{Expr, Node, Syntax, 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 test_rust_macro() { +    let syntax = Syntax::default(); +    assert_eq!( +        super::parse("{{ vec!(1, 2, 3) }}", &syntax).unwrap(), +        vec![Node::Expr( +            Ws(None, None), +            Expr::RustMacro(vec!["vec"], "1, 2, 3",), +        )], +    ); +    assert_eq!( +        super::parse("{{ alloc::vec!(1, 2, 3) }}", &syntax).unwrap(), +        vec![Node::Expr( +            Ws(None, None), +            Expr::RustMacro(vec!["alloc", "vec"], "1, 2, 3",), +        )], +    ); +    assert_eq!( +        super::parse("{{a!()}}", &syntax).unwrap(), +        [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], +    ); +    assert_eq!( +        super::parse("{{a !()}}", &syntax).unwrap(), +        [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], +    ); +    assert_eq!( +        super::parse("{{a! ()}}", &syntax).unwrap(), +        [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], +    ); +    assert_eq!( +        super::parse("{{a ! ()}}", &syntax).unwrap(), +        [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], +    ); +    assert_eq!( +        super::parse("{{A!()}}", &syntax).unwrap(), +        [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["A"], ""),)], +    ); +    assert_eq!( +        &*super::parse("{{a.b.c!( hello )}}", &syntax) +            .unwrap_err() +            .to_string(), +        "problems parsing template source at row 1, column 7 near:\n\"!( hello )}}\"", +    ); +} + +#[test] +fn change_delimiters_parse_filter() { +    let syntax = Syntax { +        expr_start: "{=", +        expr_end: "=}", +        ..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.to_string(), +        "unable to parse template:\n\n\"{%leta=b%}\"" +    )); +} | 
