From ed28f1c1bc94dd0c5a6bffd77b71155bdf82d906 Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Fri, 27 Feb 2015 09:27:45 +1300
Subject: [PATCH] Implement if/else

---
 maud_macros/src/parse.rs   | 44 ++++++++++++++++++++++++++++++++++++--
 maud_macros/tests/tests.rs | 20 +++++++++++++++++
 2 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 261ec3a..0df8e59 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -4,7 +4,7 @@ use syntax::codemap::Span;
 use syntax::ext::base::ExtCtxt;
 use syntax::parse;
 use syntax::parse::parser::Parser as RustParser;
-use syntax::parse::token;
+use syntax::parse::token::{self, DelimToken};
 use syntax::ptr::P;
 
 use super::render::{Escape, Renderer};
@@ -97,6 +97,11 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
                 self.shift(1);
                 self.literal(tt, false)
             },
+            // If
+            [dollar!(), ident!(sp, name), ..] if name.as_str() == "if" => {
+                self.shift(2);
+                self.if_expr(sp);
+            },
             // Splice
             [ref tt @ dollar!(), dollar!(), ..] => {
                 self.shift(2);
@@ -140,6 +145,41 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
         }
     }
 
+    fn if_expr(&mut self, sp: Span) {
+        let mut cond = vec![];
+        let if_body;
+        loop { match self.input {
+            [TtDelimited(sp, ref d), ..] if d.delim == DelimToken::Brace => {
+                self.shift(1);
+                if_body = self.block(sp, &d.tts);
+                break;
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                cond.push(tt.clone());
+            },
+            [] => self.render.cx.span_fatal(sp, "expected body for this $if"),
+        }}
+        let cond = self.new_rust_parser(cond).parse_expr();
+        let else_body = match self.input {
+            [dollar!(), ident!(name), ..] if name.as_str() == "else" => {
+                self.shift(2);
+                let else_body = {
+                    // Parse a single markup, but capture the result rather
+                    // than emitting it right away
+                    let mut render = self.render.fork();
+                    mem::swap(&mut self.render, &mut render);
+                    self.markup();
+                    mem::swap(&mut self.render, &mut render);
+                    render.into_stmts()
+                };
+                Some(else_body)
+            },
+            _ => None,
+        };
+        self.render.emit_if(cond, if_body, else_body);
+    }
+
     fn splice(&mut self, sp: Span) -> P<Expr> {
         let mut tts = vec![];
         // First, munch a single token tree
@@ -194,7 +234,7 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
                 self.render.attribute_start(name.as_str());
                 {
                     // Parse a value under an attribute context
-                    let in_attr = true;
+                    let mut in_attr = true;
                     mem::swap(&mut self.in_attr, &mut in_attr);
                     self.markup();
                     mem::swap(&mut self.in_attr, &mut in_attr);
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 5ffffb2..56c0f0c 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -182,3 +182,23 @@ fn issue_1() {
     let markup = html! { "Test" };
     let _ = markup.to_string();
 }
+
+mod control {
+    #[test]
+    fn if_expr() {
+        for (number, &name) in (1..4).zip(["one", "two", "three"].iter()) {
+            let s = html! {
+                $if number == 1 {
+                    "one"
+                } $else $if number == 2 {
+                    "two"
+                } $else $if number == 3 {
+                    "three"
+                } $else {
+                    "oh noes"
+                }
+            }.to_string();
+            assert_eq!(s, name);
+        }
+    }
+}