From e40e93796f45d34d038c6a20c4b034eb3b384b12 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 1 Jul 2023 16:00:06 +0200 Subject: Extract askama_parser crate --- Cargo.toml | 2 + askama_derive/Cargo.toml | 2 +- askama_derive/src/config.rs | 2 +- askama_derive/src/generator.rs | 2 +- askama_derive/src/heritage.rs | 2 +- askama_derive/src/input.rs | 2 +- askama_derive/src/lib.rs | 4 +- askama_derive/src/parser/expr.rs | 285 --------------- askama_derive/src/parser/mod.rs | 381 -------------------- askama_derive/src/parser/node.rs | 674 ------------------------------------ askama_derive/src/parser/tests.rs | 712 -------------------------------------- askama_parser/Cargo.toml | 17 + askama_parser/src/expr.rs | 285 +++++++++++++++ askama_parser/src/lib.rs | 384 ++++++++++++++++++++ askama_parser/src/node.rs | 674 ++++++++++++++++++++++++++++++++++++ askama_parser/src/tests.rs | 712 ++++++++++++++++++++++++++++++++++++++ 16 files changed, 2081 insertions(+), 2059 deletions(-) delete mode 100644 askama_derive/src/parser/expr.rs delete mode 100644 askama_derive/src/parser/mod.rs delete mode 100644 askama_derive/src/parser/node.rs delete mode 100644 askama_derive/src/parser/tests.rs create mode 100644 askama_parser/Cargo.toml create mode 100644 askama_parser/src/expr.rs create mode 100644 askama_parser/src/lib.rs create mode 100644 askama_parser/src/node.rs create mode 100644 askama_parser/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index bd8b8da..60326cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "askama_derive", "askama_escape", "askama_mendes", + "askama_parser", "askama_rocket", "askama_tide", "askama_warp", @@ -18,5 +19,6 @@ default-members = [ "askama", "askama_derive", "askama_escape", + "askama_parser", "testing", ] diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 5b33e6d..cb4635f 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -31,9 +31,9 @@ with-tide = [] with-warp = [] [dependencies] +parser = { package = "askama_parser", version = "0.1", path = "../askama_parser" } mime = "0.3" mime_guess = "2" -nom = "7" proc-macro2 = "1" quote = "1" serde = { version = "1.0", optional = true, features = ["derive"] } diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index e456c67..6533fdc 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -5,8 +5,8 @@ use std::{env, fs}; #[cfg(feature = "serde")] use serde::Deserialize; -use crate::parser::{Syntax, Whitespace}; use crate::CompileError; +use parser::{Syntax, Whitespace}; #[derive(Debug)] pub(crate) struct Config<'a> { diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index b6c6151..0c482b5 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,8 +1,8 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; -use crate::parser::{Cond, CondTest, Expr, Loop, Node, Parsed, Target, When, Whitespace, Ws}; use crate::CompileError; +use parser::{Cond, CondTest, Expr, Loop, Node, Parsed, Target, When, Whitespace, Ws}; use proc_macro::TokenStream; use quote::{quote, ToTokens}; diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index dbb2b1f..38c2cc8 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; -use crate::parser::{Loop, Macro, Node}; use crate::CompileError; +use parser::{Loop, Macro, Node}; pub(crate) struct Heritage<'a> { pub(crate) root: &'a Context<'a>, diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index be425a3..d4e8ad9 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -1,7 +1,7 @@ use crate::config::Config; use crate::generator::TemplateArgs; -use crate::parser::Syntax; use crate::CompileError; +use parser::Syntax; use std::path::{Path, PathBuf}; use std::str::FromStr; diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 1483438..8a737aa 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -7,12 +7,12 @@ use std::fmt; use proc_macro::TokenStream; use proc_macro2::Span; +use parser::ParseError; + mod config; mod generator; mod heritage; mod input; -mod parser; -use parser::ParseError; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { diff --git a/askama_derive/src/parser/expr.rs b/askama_derive/src/parser/expr.rs deleted file mode 100644 index deefb48..0000000 --- a/askama_derive/src/parser/expr.rs +++ /dev/null @@ -1,285 +0,0 @@ -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(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(Vec<&'a str>, &'a str), - Try(Box>), -} - -impl Expr<'_> { - pub(super) fn parse(i: &str) -> IResult<&str, Expr<'_>> { - expr_any(i) - } - - pub(super) fn parse_arguments(i: &str) -> IResult<&str, Vec>> { - 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>), - // 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>>)> { - 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>> { - delimited( - ws(char('(')), - separated_list0(char(','), ws(expr_any)), - ws(char(')')), - )(i) -} diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs deleted file mode 100644 index 8da96f5..0000000 --- a/askama_derive/src/parser/mod.rs +++ /dev/null @@ -1,381 +0,0 @@ -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(crate) use self::expr::Expr; -pub(crate) 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, -} - -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 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(crate) struct Parsed { - #[allow(dead_code)] - source: String, - nodes: Vec>, - } - - impl Parsed { - pub(crate) fn new(source: String, syntax: &Syntax<'_>) -> Result { - // 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(crate) fn nodes(&self) -> &[Node<'_>] { - &self.nodes - } - } -} - -pub(crate) use _parsed::Parsed; - -pub(crate) fn parse<'a>(src: &'a str, syntax: &Syntax<'_>) -> Result>, 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(crate) 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 { - 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(crate) struct Syntax<'a> { - pub(crate) block_start: &'a str, - pub(crate) block_end: &'a str, - pub(crate) expr_start: &'a str, - pub(crate) expr_end: &'a str, - pub(crate) comment_start: &'a str, - pub(crate) 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_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs deleted file mode 100644 index ce303bc..0000000 --- a/askama_derive/src/parser/node.rs +++ /dev/null @@ -1,674 +0,0 @@ -use std::str; - -use nom::branch::alt; -use nom::bytes::complete::{tag, take_until}; -use nom::character::complete::char; -use nom::combinator::{complete, consumed, cut, map, opt, peek, value}; -use nom::error::{Error, ErrorKind}; -use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; -use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use nom::{error_position, IResult}; - -use super::{ - bool_lit, char_lit, identifier, keyword, num_lit, path, skip_till, split_ws_parts, str_lit, - tag_block_end, tag_block_start, tag_comment_end, tag_comment_start, tag_expr_end, - tag_expr_start, take_content, ws, Expr, State, -}; - -#[derive(Debug, PartialEq)] -pub(crate) enum Node<'a> { - Lit(&'a str, &'a str, &'a str), - Comment(Ws), - Expr(Ws, Expr<'a>), - Call(Ws, Option<&'a str>, &'a str, Vec>), - 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) 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, -} - -#[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, -} - -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, -} - -/// 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>, -} - -impl Node<'_> { - pub(super) fn parse<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec>> { - 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>> { - 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>> { - many0(alt(( - complete(|i| take_content(i, s)), - complete(|i| block_comment(i, s)), - complete(|i| expr_node(i, s)), - complete(|i| block_node(i, s)), - )))(i) -} - -fn variant_lit(i: &str) -> IResult<&str, Target<'_>> { - alt(( - map(str_lit, Target::StrLit), - map(char_lit, Target::CharLit), - map(num_lit, Target::NumLit), - map(bool_lit, Target::BoolLit), - ))(i) -} - -fn target(i: &str) -> IResult<&str, Target<'_>> { - let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some()); - let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some()); - let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some()); - - let (i, lit) = opt(variant_lit)(i)?; - if let Some(lit) = lit { - return Ok((i, lit)); - } - - // match tuples and unused parentheses - let (i, target_is_tuple) = opt_opening_paren(i)?; - if target_is_tuple { - let (i, is_empty_tuple) = opt_closing_paren(i)?; - if is_empty_tuple { - return Ok((i, Target::Tuple(Vec::new(), Vec::new()))); - } - - let (i, first_target) = target(i)?; - let (i, is_unused_paren) = opt_closing_paren(i)?; - if is_unused_paren { - return Ok((i, first_target)); - } - - let mut targets = vec![first_target]; - let (i, _) = cut(tuple(( - fold_many0( - preceded(ws(char(',')), target), - || (), - |_, target| { - targets.push(target); - }, - ), - opt(ws(char(','))), - ws(cut(char(')'))), - )))(i)?; - return Ok((i, Target::Tuple(Vec::new(), targets))); - } - - // match structs - let (i, path) = opt(path)(i)?; - if let Some(path) = path { - let i_before_matching_with = i; - let (i, _) = opt(ws(keyword("with")))(i)?; - - let (i, is_unnamed_struct) = opt_opening_paren(i)?; - if is_unnamed_struct { - let (i, targets) = alt(( - map(char(')'), |_| Vec::new()), - terminated( - cut(separated_list1(ws(char(',')), target)), - pair(opt(ws(char(','))), ws(cut(char(')')))), - ), - ))(i)?; - return Ok((i, Target::Tuple(path, targets))); - } - - let (i, is_named_struct) = opt_opening_brace(i)?; - if is_named_struct { - let (i, targets) = alt(( - map(char('}'), |_| Vec::new()), - terminated( - cut(separated_list1(ws(char(',')), named_target)), - pair(opt(ws(char(','))), ws(cut(char('}')))), - ), - ))(i)?; - return Ok((i, Target::Struct(path, targets))); - } - - return Ok((i_before_matching_with, Target::Path(path))); - } - - // neither literal nor struct nor path - map(identifier, Target::Name)(i) -} - -fn named_target(i: &str) -> IResult<&str, (&str, Target<'_>)> { - let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), target)))(i)?; - Ok((i, (src, target.unwrap_or(Target::Name(src))))) -} diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs deleted file mode 100644 index 0e785eb..0000000 --- a/askama_derive/src/parser/tests.rs +++ /dev/null @@ -1,712 +0,0 @@ -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%}\"" - )); -} 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>), + 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(Vec<&'a str>, &'a str), + Try(Box>), +} + +impl Expr<'_> { + pub(super) fn parse(i: &str) -> IResult<&str, Expr<'_>> { + expr_any(i) + } + + pub(super) fn parse_arguments(i: &str) -> IResult<&str, Vec>> { + 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>), + // 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>>)> { + 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>> { + 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, +} + +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 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>, + } + + impl Parsed { + pub fn new(source: String, syntax: &Syntax<'_>) -> Result { + // 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>, 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 { + 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>), + 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 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 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>, + pub body: Vec>, + pub ws2: Ws, + pub else_block: Vec>, + pub ws3: Ws, +} + +pub type When<'a> = (Ws, Target<'a>, Vec>); + +#[derive(Debug, PartialEq)] +pub struct Macro<'a> { + pub ws1: Ws, + pub args: Vec<&'a str>, + pub nodes: Vec>, + 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, pub Option); + +pub type Cond<'a> = (Ws, Option>, Vec>); + +#[derive(Debug, PartialEq)] +pub struct CondTest<'a> { + pub target: Option>, + pub expr: Expr<'a>, +} + +impl Node<'_> { + pub(super) fn parse<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec>> { + 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>> { + 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>> { + 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%}\"" + )); +} -- cgit