diff options
Diffstat (limited to '')
| -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 | 
3 files changed, 242 insertions, 67 deletions
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]>>>;  | 
