diff options
Diffstat (limited to 'askama_shared')
-rw-r--r-- | askama_shared/Cargo.toml | 4 | ||||
-rw-r--r-- | askama_shared/src/generator.rs | 60 | ||||
-rw-r--r-- | askama_shared/src/heritage.rs | 2 | ||||
-rw-r--r-- | askama_shared/src/parser.rs | 247 |
4 files changed, 243 insertions, 70 deletions
diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml index f5ac4b6..fdc5a4c 100644 --- a/askama_shared/Cargo.toml +++ b/askama_shared/Cargo.toml @@ -19,9 +19,7 @@ yaml = ["serde", "serde_yaml"] [dependencies] askama_escape = { version = "0.10", path = "../askama_escape" } humansize = { version = "1.1.0", optional = true } -# default for features for nom don't work result in linker errors: -# https://github.com/rust-lang/rust/issues/62146 -nom = { version = "6", features = ["std"] } +nom = { version = "6", features = ["std"], default-features = false } num-traits = { version = "0.2.6", optional = true } proc-macro2 = "1" quote = "1" diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 3b73170..146f9db 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -444,8 +444,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Node::Cond(ref conds, ws) => { self.write_cond(ctx, buf, conds, ws)?; } - Node::Match(ws1, ref expr, inter, ref arms, ws2) => { - self.write_match(ctx, buf, ws1, expr, inter, arms, ws2)?; + Node::Match(ws1, ref expr, ref arms, ws2) => { + self.write_match(ctx, buf, ws1, expr, arms, ws2)?; } Node::Loop(ws1, ref var, ref iter, ref body, ws2) => { self.write_loop(ctx, buf, ws1, var, iter, body, ws2)?; @@ -505,8 +505,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { let mut has_else = false; for (i, &(cws, ref cond, ref nodes)) in conds.iter().enumerate() { self.handle_ws(cws); - if arm_sizes.is_empty() { - flushed += self.write_buf_writable(buf)?; + flushed += self.write_buf_writable(buf)?; + if i > 0 { + self.locals.pop(); } let mut arm_size = 0; @@ -518,8 +519,15 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.dedent()?; buf.write("} else if "); } + // The following syntax `*(&(...) as &bool)` is used to + // trigger Rust's automatic dereferencing, to coerce + // e.g. `&&&&&bool` to `bool`. First `&(...) as &bool` + // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` + // finally dereferences it to `bool`. + buf.write("*(&("); let expr_code = self.visit_expr_root(expr)?; buf.write(&expr_code); + buf.write(") as &bool)"); } None => { buf.dedent()?; @@ -532,14 +540,14 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { self.locals.push(); arm_size += self.handle(ctx, nodes, buf, AstLevel::Nested)?; - arm_size += self.write_buf_writable(buf)?; arm_sizes.push(arm_size); - - self.locals.pop(); } self.handle_ws(ws); + flushed += self.write_buf_writable(buf)?; buf.writeln("}")?; + self.locals.pop(); + if !has_else { arm_sizes.push(0); } @@ -553,23 +561,28 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, ws1: WS, expr: &Expr, - inter: Option<&'a str>, arms: &'a [When], ws2: WS, ) -> Result<usize, CompileError> { self.flush_ws(ws1); let flushed = self.write_buf_writable(buf)?; let mut arm_sizes = Vec::new(); - if let Some(inter) = inter { - if !inter.is_empty() { - self.next_ws = Some(inter); - } - } let expr_code = self.visit_expr_root(expr)?; buf.writeln(&format!("match &{} {{", expr_code))?; - for arm in arms { + + let mut arm_size = 0; + for (i, arm) in arms.iter().enumerate() { let &(ws, ref variant, ref params, ref body) = arm; + self.handle_ws(ws); + + if i > 0 { + arm_sizes.push(arm_size + self.write_buf_writable(buf)?); + + buf.writeln("}")?; + self.locals.pop(); + } + self.locals.push(); match *variant { Some(ref param) => { @@ -616,15 +629,17 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } } buf.writeln(" => {")?; - self.handle_ws(ws); - let arm_size = self.handle(ctx, body, buf, AstLevel::Nested)?; - arm_sizes.push(arm_size + self.write_buf_writable(buf)?); - buf.writeln("}")?; - self.locals.pop(); + + arm_size = self.handle(ctx, body, buf, AstLevel::Nested)?; } - buf.writeln("}")?; self.handle_ws(ws2); + arm_sizes.push(arm_size + self.write_buf_writable(buf)?); + buf.writeln("}")?; + self.locals.pop(); + + buf.writeln("}")?; + Ok(flushed + median(&mut arm_sizes)) } @@ -717,10 +732,11 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } names.write(arg); - values.write("&"); + values.write("&("); values.write(&self.visit_expr_root(args.get(i).ok_or_else(|| { CompileError::String(format!("macro '{}' takes more than {} arguments", name, i)) })?)?); + values.write(")"); self.locals.insert(arg); } @@ -944,7 +960,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { let expression = match wrapped { Wrapped => expr_buf.buf, Unwrapped => format!( - "::askama::MarkupDisplay::new_unsafe(&{}, {})", + "::askama::MarkupDisplay::new_unsafe(&({}), {})", expr_buf.buf, self.input.escaper ), }; diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs index bc9e701..87f23e0 100644 --- a/askama_shared/src/heritage.rs +++ b/askama_shared/src/heritage.rs @@ -89,7 +89,7 @@ impl<'a> Context<'a> { Node::Loop(_, _, _, nodes, _) => { nested.push(nodes); } - Node::Match(_, _, _, arms, _) => { + Node::Match(_, _, arms, _) => { for (_, _, _, arm) in arms { nested.push(arm); } diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 9344c07..a40e967 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -1,7 +1,7 @@ use nom::branch::alt; use nom::bytes::complete::{escaped, is_not, tag, take_until}; use nom::character::complete::{anychar, char, digit1}; -use nom::combinator::{complete, map, opt}; +use nom::combinator::{complete, map, opt, value}; use nom::error::ParseError; use nom::multi::{many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, tuple}; @@ -19,7 +19,7 @@ pub enum Node<'a> { LetDecl(WS, Target<'a>), Let(WS, Target<'a>, Expr<'a>), Cond(Vec<(WS, Option<Expr<'a>>, Vec<Node<'a>>)>, WS), - Match(WS, Expr<'a>, Option<&'a str>, Vec<When<'a>>, WS), + Match(WS, Expr<'a>, Vec<When<'a>>, WS), Loop(WS, Target<'a>, Expr<'a>, Vec<Node<'a>>, WS), Extends(Expr<'a>), BlockDef(WS, &'a str, Vec<Node<'a>>, WS), @@ -312,10 +312,12 @@ fn expr_var_call(i: &[u8]) -> IResult<&[u8], Expr> { } fn path(i: &[u8]) -> IResult<&[u8], Vec<&str>> { + let root = opt(value("", ws(tag("::")))); let tail = separated_list1(ws(tag("::")), identifier); - let (i, (start, _, rest)) = tuple((identifier, ws(tag("::")), tail))(i)?; - - let mut path = vec![start]; + let (i, (root, start, _, rest)) = tuple((root, identifier, ws(tag("::")), tail))(i)?; + let mut path = Vec::new(); + path.extend(root); + path.push(start); path.extend(rest); Ok((i, path)) } @@ -573,36 +575,32 @@ fn expr_rust_macro(i: &[u8]) -> IResult<&[u8], Expr> { macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { fn $name(i: &[u8]) -> IResult<&[u8], Expr> { - let (i, (left, op_and_right)) = tuple(( + let (i, left) = $inner(i)?; + let (i, right) = many0(pair( + ws(tag($op)), $inner, - opt(pair( - ws(tag($op)), - expr_any, - )) ))(i)?; - Ok((i, match op_and_right { - Some((op, right)) => Expr::BinOp( - str::from_utf8(op).unwrap(), Box::new(left), Box::new(right) - ), - None => left, - })) + Ok(( + i, + right.into_iter().fold(left, |left, (op, right)| { + Expr::BinOp(str::from_utf8(op).unwrap(), Box::new(left), Box::new(right)) + }), + )) } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { fn $name(i: &[u8]) -> IResult<&[u8], Expr> { - let (i, (left, op_and_right)) = tuple(( + let (i, left) = $inner(i)?; + let (i, right) = many0(pair( + ws(alt(($( tag($op) ),*,))), $inner, - opt(pair( - ws(alt(($( tag($op) ),*,))), - expr_any - )) ))(i)?; - Ok((i, match op_and_right { - Some((op, right)) => Expr::BinOp( - str::from_utf8(op).unwrap(), Box::new(left), Box::new(right) - ), - None => left, - })) + Ok(( + i, + right.into_iter().fold(left, |left, (op, right)| { + Expr::BinOp(str::from_utf8(op).unwrap(), Box::new(left), Box::new(right)) + }), + )) } } } @@ -773,8 +771,8 @@ fn block_match<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a> arms.push(arm); } - let inter = match inter { - Some(Node::Lit(lws, val, rws)) => { + match inter { + Some(Node::Lit(_, val, rws)) => { assert!( val.is_empty(), "only whitespace allowed between match and first when, found {}", @@ -785,18 +783,16 @@ fn block_match<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a> "only whitespace allowed between match and first when, found {}", rws ); - Some(lws) } - None => None, + None => {} _ => panic!("only literals allowed between match and first when"), - }; + } Ok(( i, Node::Match( WS(pws1.is_some(), nws1.is_some()), expr, - inter, arms, WS(pws2.is_some(), nws2.is_some()), ), @@ -1078,12 +1074,13 @@ pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result<Vec<Node<'a>>, #[cfg(test)] mod tests { + use super::{Expr, Node, WS}; use crate::Syntax; fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let node = super::split_ws_parts(s.as_bytes()); match node { - super::Node::Lit(lws, s, rws) => { + Node::Lit(lws, s, rws) => { assert_eq!(lws, res.0); assert_eq!(s, res.1); assert_eq!(rws, res.2); @@ -1118,12 +1115,9 @@ mod tests { fn test_parse_var_call() { assert_eq!( super::parse("{{ function(\"123\", 3) }}", &Syntax::default()).unwrap(), - vec![super::Node::Expr( - super::WS(false, false), - super::Expr::VarCall( - "function", - vec![super::Expr::StrLit("123"), super::Expr::NumLit("3")] - ), + vec![Node::Expr( + WS(false, false), + Expr::VarCall("function", vec![Expr::StrLit("123"), Expr::NumLit("3")]), )], ); } @@ -1132,17 +1126,36 @@ mod tests { fn test_parse_path_call() { assert_eq!( super::parse("{{ self::function(\"123\", 3) }}", &Syntax::default()).unwrap(), - vec![super::Node::Expr( - super::WS(false, false), - super::Expr::PathCall( + vec![Node::Expr( + WS(false, false), + Expr::PathCall( vec!["self", "function"], - vec![super::Expr::StrLit("123"), super::Expr::NumLit("3")], + 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(false, false), + Expr::PathCall(vec!["std", "string", "String", "new"], vec![]), + )], + ); + assert_eq!( + super::parse("{{ ::std::string::String::new() }}", &syntax).unwrap(), + vec![Node::Expr( + WS(false, false), + Expr::PathCall(vec!["", "std", "string", "String", "new"], vec![]), + )], + ); + } + + #[test] fn change_delimiters_parse_filter() { let syntax = Syntax { expr_start: "{~", @@ -1152,6 +1165,152 @@ mod tests { 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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(false, false), + 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() + ) + )], + ); + } } type ParserError<'a, T> = Result<(&'a [u8], T), nom::Err<nom::error::Error<&'a [u8]>>>; |