From a530d73d25b025117a1c56ebf0097fd11e63551e Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Sat, 14 Mar 2015 21:08:08 +1300
Subject: [PATCH] Implement `for` expressions

---
 maud_macros/src/parse.rs   | 37 +++++++++++++++++++++++++++++++++++++
 maud_macros/src/render.rs  |  7 ++++++-
 maud_macros/tests/tests.rs | 16 ++++++++++++++++
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index bc2a948..26c1460 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -102,6 +102,11 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
                 self.shift(2);
                 self.if_expr(sp);
             },
+            // For
+            [dollar!(), ident!(sp, name), ..] if name.as_str() == "for" => {
+                self.shift(2);
+                self.for_expr(sp);
+            },
             // Splice
             [ref tt @ dollar!(), dollar!(), ..] => {
                 self.shift(2);
@@ -192,6 +197,38 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
         self.render.emit_if(if_cond, if_body, else_body);
     }
 
+    fn for_expr(&mut self, sp: Span) {
+        let mut pattern = vec![];
+        loop { match self.input {
+            [ident!(in_), ..] if in_.as_str() == "in" => {
+                self.shift(1);
+                break;
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                pattern.push(tt.clone());
+            },
+            _ => self.render.cx.span_fatal(sp, "invalid $for"),
+        }}
+        let pattern = self.new_rust_parser(pattern).parse_pat();
+        let mut iterable = vec![];
+        let body;
+        loop { match self.input {
+            [TtDelimited(sp, ref d), ..] if d.delim == DelimToken::Brace => {
+                self.shift(1);
+                body = self.block(sp, &d.tts);
+                break;
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                iterable.push(tt.clone());
+            },
+            _ => self.render.cx.span_fatal(sp, "invalid $for"),
+        }}
+        let iterable = self.new_rust_parser(iterable).parse_expr();
+        self.render.emit_for(pattern, iterable, body);
+    }
+
     fn splice(&mut self, sp: Span) -> P<Expr> {
         let mut tts = vec![];
         // First, munch a single token tree
diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs
index f415950..20b9810 100644
--- a/maud_macros/src/render.rs
+++ b/maud_macros/src/render.rs
@@ -1,4 +1,4 @@
-use syntax::ast::{Expr, Ident, Stmt};
+use syntax::ast::{Expr, Ident, Pat, Stmt};
 use syntax::ext::base::ExtCtxt;
 use syntax::ext::build::AstBuilder;
 use syntax::parse::token;
@@ -135,4 +135,9 @@ impl<'cx, 's> Renderer<'cx, 's> {
         };
         self.stmts.push(stmt);
     }
+
+    pub fn emit_for(&mut self, pattern: P<Pat>, iterable: P<Expr>, body: Vec<P<Stmt>>) {
+        let stmt = quote_stmt!(self.cx, for $pattern in $iterable { $body });
+        self.stmts.push(stmt);
+    }
 }
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index d322049..4ce1763 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -201,4 +201,20 @@ mod control {
             assert_eq!(s, name);
         }
     }
+
+    #[test]
+    fn for_expr() {
+        let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"];
+        let s = html! {
+            ul $for pony in &ponies {
+                li $pony
+            }
+        }.to_string();
+        assert_eq!(s, concat!(
+                "<ul>",
+                "<li>Apple Bloom</li>",
+                "<li>Scootaloo</li>",
+                "<li>Sweetie Belle</li>",
+                "</ul>"));
+    }
 }