Repository: djc/askama
Pull request #178 (2018-12-10 19:51:30)

fix rust macro arguments
#174 

this or #176 

djc: Thanks!

Patch:
diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs
index 047e975..95a7d17 100644
--- a/askama_derive/src/generator.rs
+++ b/askama_derive/src/generator.rs
@@ -1,6 +1,8 @@
 use super::{get_template_source, Context, Heritage};
 use crate::input::TemplateInput;
-use crate::parser::{Cond, Expr, MatchParameter, MatchParameters, MatchVariant, Node, Target, When, WS};
+use crate::parser::{
+    Cond, Expr, MatchParameter, MatchParameters, MatchVariant, Node, Target, When, WS,
+};
 use askama_shared::filters;
 
 use proc_macro2::Span;
@@ -773,14 +775,14 @@ impl<'a> Generator<'a> {
             Expr::MethodCall(ref obj, method, ref args) => {
                 self.visit_method_call(buf, obj, method, args)
             }
-            Expr::RustMacro(name, ref args) => self.visit_rust_macro(buf, name, args),
+            Expr::RustMacro(name, args) => self.visit_rust_macro(buf, name, args),
         }
     }
 
-    fn visit_rust_macro(&mut self, buf: &mut Buffer, name: &str, args: &[Expr]) -> DisplayWrap {
+    fn visit_rust_macro(&mut self, buf: &mut Buffer, name: &str, args: &str) -> DisplayWrap {
         buf.write(name);
         buf.write("!(");
-        self._visit_args(buf, args);
+        buf.write(args);
         buf.write(")");
 
         DisplayWrap::Unwrapped
diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs
index 7968ac1..81935f6 100644
--- a/askama_derive/src/parser.rs
+++ b/askama_derive/src/parser.rs
@@ -21,7 +21,7 @@ pub enum 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>>),
-    RustMacro(&'a str, Vec<Expr<'a>>),
+    RustMacro(&'a str, &'a str),
 }
 
 #[derive(Debug)]
@@ -302,6 +302,60 @@ named!(arguments<Input, Vec<Expr>>, do_parse!(
     (args.unwrap_or_default())
 ));
 
+named!(macro_arguments<Input, &str>,
+    delimited!(char!('('), nested_parenthesis, char!(')'))
+);
+
+fn nested_parenthesis(i: Input) -> Result<(Input, &str), nom::Err<Input>> {
+    let mut nested = 0;
+    let mut last = 0;
+    let mut in_str = false;
+    let mut escaped = false;
+
+    for (i, b) in i.iter().enumerate() {
+        if !(*b == b'(' || *b == b')') || !in_str {
+            match *b {
+                b'(' => {
+                    nested += 1
+                },
+                b')' => {
+                    if nested == 0 {
+                        last = i;
+                        break;
+                    }
+                    nested -= 1;
+                },
+                b'"' => {
+                    if in_str {
+                        if !escaped {
+                            in_str = false;
+                        }
+                    } else {
+                        in_str = true;
+                    }
+                },
+                b'\\' => {
+                    escaped = !escaped;
+                },
+                _ => (),
+            }
+        }
+
+        if escaped && *b != b'\\' {
+            escaped = false;
+        }
+    }
+
+    if nested == 0 {
+        Ok((Input(&i[last..]), str::from_utf8(&i[..last]).unwrap()))
+    } else {
+        Err(nom::Err::Error(error_position!(
+            i,
+            nom::ErrorKind::Custom(0)
+        )))
+    }
+}
+
 named!(parameters<Input, Vec<&str>>, do_parse!(
     tag!("(") >>
     vals: opt!(do_parse!(
@@ -477,11 +531,10 @@ named!(expr_unary<Input, Expr>, do_parse!(
 named!(rust_macro<Input, Expr>, do_parse!(
     mname: identifier >>
     tag!("!") >>
-    args: arguments >>
+    args: macro_arguments >>
     (Expr::RustMacro(mname, args))
 ));
 
-
 macro_rules! expr_prec_layer {
     ( $name:ident, $inner:ident, $( $op:expr ),* ) => {
         named!($name<Input, Expr>, do_parse!(
@@ -503,9 +556,7 @@ 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_compare, expr_bor, "==", "!=", ">=", ">", "<=", "<");
 expr_prec_layer!(expr_and, expr_compare, "&&");
 expr_prec_layer!(expr_or, expr_and, "||");
 


Test patch:
diff --git a/testing/templates/rust-macro-args.html b/testing/templates/rust-macro-args.html
new file mode 100644
index 0000000..b009a73
--- /dev/null
+++ b/testing/templates/rust-macro-args.html
@@ -0,0 +1,26 @@
+{{
+        call_a_or_b_on_tail!(
+            (a: compute_len, b: zero),
+                the recursive part that skips over all these
+                tokens doesn't much care whether we will call a
+                or call b: only the terminal rules care.
+        )
+}}
+{{
+        call_a_or_b_on_tail!(
+            (a: compute_len, b: zero),
+                and now, to justify the existence of two paths
+                we will also call a: its input should somehow
+                be self-referential, so let's make it return
+                some ninety one!
+        )
+}}
+{{
+        call_a_or_b_on_tail!(
+            (a: compute_len, b: zero),
+                and now, to justify the existence of two paths
+                we will also call a: its input should somehow
+                be self-referential, so let's make it return
+                some ninety "(\"()"nine!
+        )
+}}
diff --git a/testing/tests/rust_macro.rs b/testing/tests/rust_macro.rs
index 8a114f9..b92bbeb 100644
--- a/testing/tests/rust_macro.rs
+++ b/testing/tests/rust_macro.rs
@@ -15,3 +15,35 @@ fn main() {
     let template = RustMacrosTemplate {};
     assert_eq!("Hello, world!", template.render().unwrap());
 }
+
+macro_rules! call_a_or_b_on_tail {
+    ((a: $a:expr, b: $b:expr), call a: $($tail:tt)*) => {
+        $a(stringify!($($tail)*))
+    };
+
+    ((a: $a:expr, b: $b:expr), call b: $($tail:tt)*) => {
+        $b(stringify!($($tail)*))
+    };
+
+    ($ab:tt, $_skip:tt $($tail:tt)*) => {
+        call_a_or_b_on_tail!($ab, $($tail)*)
+    };
+}
+
+fn compute_len(s: &str) -> usize {
+    s.len()
+}
+
+fn zero(_s: &str) -> usize {
+    0
+}
+
+#[derive(Template)]
+#[template(path = "rust-macro-args.html")]
+struct RustMacrosArgTemplate {}
+
+#[test]
+fn args() {
+    let template = RustMacrosArgTemplate {};
+    assert_eq!("0\n91\n99", template.render().unwrap());
+}