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>), LetDecl(Ws, Target<'a>), Let(Ws, Target<'a>, Expr<'a>), Cond(Vec>, Ws), Match(Ws, Expr<'a>, Vec>, Ws), Loop(Loop<'a>), Extends(&'a str), BlockDef(Ws, &'a str, Vec>, 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>, pub(crate) body: Vec>, pub(crate) ws2: Ws, pub(crate) else_block: Vec>, 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>), Attr(Box>, &'a str), Index(Box>, Box>), Filter(&'a str, Vec>), Unary(&'a str, Box>), BinOp(&'a str, Box>, Box>), Range(&'a str, Option>>, Option>>), Group(Box>), Tuple(Vec>), Call(Box>, Vec>), RustMacro(&'a str, &'a str), Try(Box>), } 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>); #[derive(Debug, PartialEq)] pub(crate) struct Macro<'a> { pub(crate) ws1: Ws, pub(crate) args: Vec<&'a str>, pub(crate) nodes: Vec>, pub(crate) ws2: Ws, } #[derive(Debug, PartialEq)] pub(crate) enum Target<'a> { Name(&'a str), Tuple(Vec<&'a str>, Vec>), 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 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, pub(crate) Option); pub(crate) type Cond<'a> = (Ws, Option>, Vec>); #[derive(Debug, PartialEq)] pub(crate) struct CondTest<'a> { pub(crate) target: Option>, 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 { 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, } 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>> { 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>), 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>>)> { 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>> { 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>> { 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>, 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%}\"" )); } }