diff options
| author | 2022-01-27 10:33:13 +0100 | |
|---|---|---|
| committer | 2022-01-27 10:33:13 +0100 | |
| commit | cb351fe6b1dda644a4ec023dc850cdfe83732503 (patch) | |
| tree | 1c872f05b81ecada0010462380edfc0fde1dfde4 | |
| parent | bb7c60ece5dcfea9d31f79f445f80fdd4e0bf3ca (diff) | |
| download | askama-cb351fe6b1dda644a4ec023dc850cdfe83732503.tar.gz askama-cb351fe6b1dda644a4ec023dc850cdfe83732503.tar.bz2 askama-cb351fe6b1dda644a4ec023dc850cdfe83732503.zip | |
Unify handling of calls (#614)
Instead of having `Expr::VarCall`, `Expr::PathCall` and
`Expr::MethodCall`, this PR unifies the handling of calls by removing
the former three variants, and introducing `Expr::Call`.
Diffstat (limited to '')
| -rw-r--r-- | askama_shared/src/generator.rs | 110 | ||||
| -rw-r--r-- | askama_shared/src/parser.rs | 192 | ||||
| -rw-r--r-- | testing/tests/calls.rs | 82 | 
3 files changed, 239 insertions, 145 deletions
| diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 53729c6..fabe277 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -603,7 +603,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {              // If `iter` is a call then we assume it's something that returns              // an iterator. If not then the user can explicitly add the needed              // call without issues. -            Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => { +            Expr::Call(..) | Expr::Index(..) => {                  buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code))              }              // If accessing `self` then it most likely needs to be @@ -1049,9 +1049,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {              Expr::StrLit(s) => self.visit_str_lit(buf, s),              Expr::CharLit(s) => self.visit_char_lit(buf, s),              Expr::Var(s) => self.visit_var(buf, s), -            Expr::VarCall(var, ref args) => self.visit_var_call(buf, var, args)?,              Expr::Path(ref path) => self.visit_path(buf, path), -            Expr::PathCall(ref path, ref args) => self.visit_path_call(buf, path, args)?,              Expr::Array(ref elements) => self.visit_array(buf, elements)?,              Expr::Attr(ref obj, name) => self.visit_attr(buf, obj, name)?,              Expr::Index(ref obj, ref key) => self.visit_index(buf, obj, key)?, @@ -1060,9 +1058,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {              Expr::BinOp(op, ref left, ref right) => self.visit_binop(buf, op, left, right)?,              Expr::Range(op, ref left, ref right) => self.visit_range(buf, op, left, right)?,              Expr::Group(ref inner) => self.visit_group(buf, inner)?, -            Expr::MethodCall(ref obj, method, ref args) => { -                self.visit_method_call(buf, obj, method, args)? -            } +            Expr::Call(ref obj, ref args) => self.visit_call(buf, obj, args)?,              Expr::RustMacro(name, args) => self.visit_rust_macro(buf, name, args),          })      } @@ -1234,20 +1230,15 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {                  buf.write("&(");              } -            let scoped = matches!( -                arg, -                Expr::Filter(_, _) -                    | Expr::MethodCall(_, _, _) -                    | Expr::VarCall(_, _) -                    | Expr::PathCall(_, _) -            ); - -            if scoped { -                buf.writeln("{")?; -                self.visit_expr(buf, arg)?; -                buf.writeln("}")?; -            } else { -                self.visit_expr(buf, arg)?; +            match arg { +                Expr::Call(left, _) if !matches!(left.as_ref(), Expr::Path(_)) => { +                    buf.writeln("{")?; +                    self.visit_expr(buf, arg)?; +                    buf.writeln("}")?; +                } +                _ => { +                    self.visit_expr(buf, arg)?; +                }              }              if borrow { @@ -1283,7 +1274,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {              }          }          self.visit_expr(buf, obj)?; -        buf.write(&format!(".{}", attr)); +        buf.write(&format!(".{}", normalize_identifier(attr)));          Ok(DisplayWrap::Unwrapped)      } @@ -1301,15 +1292,14 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {          Ok(DisplayWrap::Unwrapped)      } -    fn visit_method_call( +    fn visit_call(          &mut self,          buf: &mut Buffer, -        obj: &Expr<'_>, -        method: &str, +        left: &Expr<'_>,          args: &[Expr<'_>],      ) -> Result<DisplayWrap, CompileError> { -        if matches!(obj, Expr::Var("loop")) { -            match method { +        match left { +            Expr::Attr(left, method) if **left == Expr::Var("loop") => match *method {                  "cycle" => match args {                      [arg] => {                          if matches!(arg, Expr::Array(arr) if arr.is_empty()) { @@ -1329,16 +1319,22 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {                      _ => return Err("loop.cycle(…) expects exactly one argument".into()),                  },                  s => return Err(format!("unknown loop method: {:?}", s).into()), +            }, +            left => { +                match left { +                    Expr::Var(name) => match self.locals.resolve(name) { +                        Some(resolved) => buf.write(&resolved), +                        None => buf.write(&format!("(&self.{})", normalize_identifier(name))), +                    }, +                    left => { +                        self.visit_expr(buf, left)?; +                    } +                } + +                buf.write("("); +                self._visit_args(buf, args)?; +                buf.write(")");              } -        } else { -            if let Expr::Var("self") = obj { -                buf.write("self"); -            } else { -                self.visit_expr(buf, obj)?; -            } -            buf.write(&format!(".{}(", normalize_identifier(method))); -            self._visit_args(buf, args)?; -            buf.write(")");          }          Ok(DisplayWrap::Unwrapped)      } @@ -1421,24 +1417,6 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {          DisplayWrap::Unwrapped      } -    fn visit_path_call( -        &mut self, -        buf: &mut Buffer, -        path: &[&str], -        args: &[Expr<'_>], -    ) -> Result<DisplayWrap, CompileError> { -        for (i, part) in path.iter().enumerate() { -            if i > 0 { -                buf.write("::"); -            } -            buf.write(part); -        } -        buf.write("("); -        self._visit_args(buf, args)?; -        buf.write(")"); -        Ok(DisplayWrap::Unwrapped) -    } -      fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {          if s == "self" {              buf.write(s); @@ -1449,24 +1427,6 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {          DisplayWrap::Unwrapped      } -    fn visit_var_call( -        &mut self, -        buf: &mut Buffer, -        s: &str, -        args: &[Expr<'_>], -    ) -> Result<DisplayWrap, CompileError> { -        buf.write("("); -        let s = normalize_identifier(s); -        if !self.locals.contains(&s) && s != "self" { -            buf.write("self."); -        } -        buf.write(s); -        buf.write(")("); -        self._visit_args(buf, args)?; -        buf.write(")"); -        Ok(DisplayWrap::Unwrapped) -    } -      fn visit_bool_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {          buf.write(s);          DisplayWrap::Unwrapped @@ -1693,14 +1653,6 @@ where          }      } -    fn contains(&self, key: &K) -> bool { -        self.scopes.iter().rev().any(|set| set.contains_key(key)) -            || match self.parent { -                Some(set) => set.contains(key), -                None => false, -            } -    } -      /// Iterates the scopes in reverse and returns `Some(LocalMeta)`      /// from the first scope where `key` exists.      fn get(&self, key: &K) -> Option<&V> { diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index f5685b9..f429628 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -52,9 +52,7 @@ pub enum Expr<'a> {      StrLit(&'a str),      CharLit(&'a str),      Var(&'a str), -    VarCall(&'a str, Vec<Expr<'a>>),      Path(Vec<&'a str>), -    PathCall(Vec<&'a str>, Vec<Expr<'a>>),      Array(Vec<Expr<'a>>),      Attr(Box<Expr<'a>>, &'a str),      Index(Box<Expr<'a>>, Box<Expr<'a>>), @@ -63,7 +61,7 @@ pub enum Expr<'a> {      BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>),      Range(&'a str, Option<Box<Expr<'a>>>, Option<Box<Expr<'a>>>),      Group(Box<Expr<'a>>), -    MethodCall(Box<Expr<'a>>, &'a str, Vec<Expr<'a>>), +    Call(Box<Expr<'a>>, Vec<Expr<'a>>),      RustMacro(&'a str, &'a str),  } @@ -86,7 +84,7 @@ impl Expr<'_> {              // The result of a call likely doesn't need to be borrowed,              // as in that case the call is more likely to return a              // reference in the first place then. -            VarCall(..) | Path(..) | PathCall(..) | MethodCall(..) => true, +            Call(..) | Path(..) => true,              // If the `expr` is within a `Unary` or `BinOp` then              // an assumption can be made that the operand is copy.              // If not, then the value is moved and adding `.clone()` @@ -296,11 +294,6 @@ fn expr_var(i: &str) -> IResult<&str, Expr<'_>> {      map(identifier, Expr::Var)(i)  } -fn expr_var_call(i: &str) -> IResult<&str, Expr<'_>> { -    let (i, (s, args)) = tuple((ws(identifier), arguments))(i)?; -    Ok((i, Expr::VarCall(s, args))) -} -  fn path(i: &str) -> IResult<&str, Vec<&str>> {      let root = opt(value("", ws(tag("::"))));      let tail = separated_list1(ws(tag("::")), identifier); @@ -335,11 +328,6 @@ fn expr_path(i: &str) -> IResult<&str, Expr<'_>> {      Ok((i, Expr::Path(path)))  } -fn expr_path_call(i: &str) -> IResult<&str, Expr<'_>> { -    let (i, (path, args)) = tuple((ws(path), arguments))(i)?; -    Ok((i, Expr::PathCall(path, args))) -} -  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))))) @@ -510,52 +498,39 @@ fn expr_single(i: &str) -> IResult<&str, Expr<'_>> {          expr_num_lit,          expr_str_lit,          expr_char_lit, -        expr_path_call,          expr_path,          expr_rust_macro,          expr_array_lit, -        expr_var_call,          expr_var,          expr_group,      ))(i)  } -fn attr(i: &str) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> { -    let (i, (_, attr, args)) = tuple(( -        ws(char('.')), -        alt((num_lit, identifier)), -        ws(opt(arguments)), -    ))(i)?; -    Ok((i, (attr, args))) +enum Suffix<'a> { +    Attr(&'a str), +    Index(Expr<'a>), +    Call(Vec<Expr<'a>>),  } -fn expr_attr(i: &str) -> IResult<&str, Expr<'_>> { -    let (i, (obj, attrs)) = tuple((expr_single, many0(attr)))(i)?; - -    let mut res = obj; -    for (aname, args) in attrs { -        res = if let Some(args) = args { -            Expr::MethodCall(Box::new(res), aname, args) -        } else { -            Expr::Attr(Box::new(res), aname) -        }; -    } - -    Ok((i, res)) +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, Expr<'_>> { -    let key = opt(tuple((ws(char('[')), expr_any, ws(char(']'))))); -    let (i, (obj, key)) = tuple((expr_attr, key))(i)?; -    let key = key.map(|(_, key, _)| key); +fn expr_index(i: &str) -> IResult<&str, Suffix<'_>> { +    map( +        preceded(ws(char('[')), cut(terminated(expr_any, ws(char(']'))))), +        Suffix::Index, +    )(i) +} -    Ok(( -        i, -        match key { -            Some(key) => Expr::Index(Box::new(obj), Box::new(key)), -            None => obj, -        }, -    )) +fn expr_call(i: &str) -> IResult<&str, Suffix<'_>> { +    map(arguments, Suffix::Call)(i)  }  fn filter(i: &str) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> { @@ -564,7 +539,7 @@ fn filter(i: &str) -> IResult<&str, (&str, Option<Vec<Expr<'_>>>)> {  }  fn expr_filtered(i: &str) -> IResult<&str, Expr<'_>> { -    let (i, (obj, filters)) = tuple((expr_unary, many0(filter)))(i)?; +    let (i, (obj, filters)) = tuple((expr_prefix, many0(filter)))(i)?;      let mut res = obj;      for (fname, args) in filters { @@ -581,15 +556,27 @@ fn expr_filtered(i: &str) -> IResult<&str, Expr<'_>> {      Ok((i, res))  } -fn expr_unary(i: &str) -> IResult<&str, Expr<'_>> { -    let (i, (op, expr)) = tuple((opt(alt((ws(tag("!")), ws(tag("-"))))), expr_index))(i)?; -    Ok(( -        i, -        match op { -            Some(op) => Expr::Unary(op, Box::new(expr)), -            None => expr, -        }, -    )) +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)))(i)?; +        i = j; +        match suffix { +            Some(Suffix::Attr(attr)) => expr = Expr::Attr(expr.into(), attr), +            Some(Suffix::Index(index)) => expr = Expr::Index(expr.into(), index.into()), +            Some(Suffix::Call(args)) => expr = Expr::Call(expr.into(), args), +            None => break, +        } +    } +    Ok((i, expr))  }  fn expr_rust_macro(i: &str) -> IResult<&str, Expr<'_>> { @@ -1348,7 +1335,10 @@ mod tests {              super::parse("{{ Some(123) }}", &s).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall(vec!["Some"], vec![Expr::NumLit("123")],), +                Expr::Call( +                    Box::new(Expr::Path(vec!["Some"])), +                    vec![Expr::NumLit("123")] +                ),              )],          ); @@ -1356,14 +1346,14 @@ mod tests {              super::parse("{{ Ok(123) }}", &s).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall(vec!["Ok"], vec![Expr::NumLit("123")],), +                Expr::Call(Box::new(Expr::Path(vec!["Ok"])), vec![Expr::NumLit("123")]),              )],          );          assert_eq!(              super::parse("{{ Err(123) }}", &s).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall(vec!["Err"], vec![Expr::NumLit("123")],), +                Expr::Call(Box::new(Expr::Path(vec!["Err"])), vec![Expr::NumLit("123")]),              )],          );      } @@ -1374,7 +1364,10 @@ mod tests {              super::parse("{{ function(\"123\", 3) }}", &Syntax::default()).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::VarCall("function", vec![Expr::StrLit("123"), Expr::NumLit("3")]), +                Expr::Call( +                    Box::new(Expr::Var("function")), +                    vec![Expr::StrLit("123"), Expr::NumLit("3")] +                ),              )],          );      } @@ -1394,7 +1387,10 @@ mod tests {              super::parse("{{ Option::Some(123) }}", &s).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall(vec!["Option", "Some"], vec![Expr::NumLit("123")],), +                Expr::Call( +                    Box::new(Expr::Path(vec!["Option", "Some"])), +                    vec![Expr::NumLit("123")], +                ),              )],          ); @@ -1402,8 +1398,8 @@ mod tests {              super::parse("{{ self::function(\"123\", 3) }}", &s).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall( -                    vec!["self", "function"], +                Expr::Call( +                    Box::new(Expr::Path(vec!["self", "function"])),                      vec![Expr::StrLit("123"), Expr::NumLit("3")],                  ),              )], @@ -1417,14 +1413,20 @@ mod tests {              super::parse("{{ std::string::String::new() }}", &syntax).unwrap(),              vec![Node::Expr(                  Ws(false, false), -                Expr::PathCall(vec!["std", "string", "String", "new"], vec![]), +                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(false, false), -                Expr::PathCall(vec!["", "std", "string", "String", "new"], vec![]), +                Expr::Call( +                    Box::new(Expr::Path(vec!["", "std", "string", "String", "new"])), +                    vec![] +                ),              )],          );      } @@ -1587,6 +1589,64 @@ mod tests {      }      #[test] +    fn test_odd_calls() { +        use Expr::*; +        let syntax = Syntax::default(); +        assert_eq!( +            super::parse("{{ a[b](c) }}", &syntax).unwrap(), +            vec![Node::Expr( +                Ws(false, false), +                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(false, false), +                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(false, false), +                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(false, false), +                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(false, false), +                Unary("-", Box::new(Call(Box::new(Var("a")), vec![Var("b")])),), +            )], +        ); +    } + +    #[test]      fn test_parse_comments() {          let s = &Syntax::default(); diff --git a/testing/tests/calls.rs b/testing/tests/calls.rs new file mode 100644 index 0000000..052f34f --- /dev/null +++ b/testing/tests/calls.rs @@ -0,0 +1,82 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "{{ func(value) }}", ext = "txt")] +struct OneFunction { +    func: fn(&i32) -> i32, +    value: i32, +} + +#[test] +fn test_one_func() { +    let t = OneFunction { +        func: |&i| 2 * i, +        value: 123, +    }; +    assert_eq!(t.render().unwrap(), "246"); +} + +#[derive(Template)] +#[template(source = "{{ self.func(value) }}", ext = "txt")] +struct OneFunctionSelf { +    value: i32, +} + +impl OneFunctionSelf { +    fn func(&self, i: &i32) -> i32 { +        2 * i +    } +} + +#[test] +fn test_one_func_self() { +    let t = OneFunctionSelf { value: 123 }; +    assert_eq!(t.render().unwrap(), "246"); +} + +#[derive(Template)] +#[template(source = "{{ func[index](value) }}", ext = "txt")] +struct OneFunctionIndex<'a> { +    func: &'a [fn(&i32) -> i32], +    value: i32, +    index: usize, +} + +#[test] +fn test_one_func_index() { +    let t = OneFunctionIndex { +        func: &[|_| panic!(), |&i| 2 * i, |_| panic!(), |_| panic!()], +        value: 123, +        index: 1, +    }; +    assert_eq!(t.render().unwrap(), "246"); +} + +struct AddToGetAFunction; + +impl std::ops::Add<usize> for &AddToGetAFunction { +    type Output = fn(&i32) -> i32; + +    fn add(self, rhs: usize) -> Self::Output { +        assert_eq!(rhs, 1); +        |&i| 2 * i +    } +} + +#[derive(Template)] +#[template(source = "{{ (func + index)(value) }}", ext = "txt")] +struct OneFunctionBinop<'a> { +    func: &'a AddToGetAFunction, +    value: i32, +    index: usize, +} + +#[test] +fn test_one_func_binop() { +    let t = OneFunctionBinop { +        func: &AddToGetAFunction, +        value: 123, +        index: 1, +    }; +    assert_eq!(t.render().unwrap(), "246"); +} | 
