aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askama_shared/src/generator.rs110
-rw-r--r--askama_shared/src/parser.rs192
-rw-r--r--testing/tests/calls.rs82
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");
+}