From b254d7fbc4b6332885dbc559f5aed92f1168c166 Mon Sep 17 00:00:00 2001
From: Wim Looman <wim@nemo157.com>
Date: Mon, 1 Feb 2016 20:05:50 +0100
Subject: [PATCH 1/3] Add support for a match keyword

---
 maud_macros/src/parse.rs   | 108 ++++++++++++++++++++++++++++++++++++-
 maud_macros/src/render.rs  |   5 ++
 maud_macros/tests/tests.rs |  48 +++++++++++++++++
 3 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 0735bc9..dda7e99 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -1,5 +1,6 @@
 use std::mem;
-use syntax::ast::{Expr, ExprParen, Lit, Stmt, TokenTree};
+use std::rc::Rc;
+use syntax::ast::{Expr, ExprParen, Lit, Stmt, TokenTree, Delimited};
 use syntax::ext::quote::rt::ToTokens;
 use syntax::codemap::Span;
 use syntax::errors::{DiagnosticBuilder, FatalError};
@@ -44,6 +45,12 @@ macro_rules! question {
 macro_rules! semi {
     () => (TokenTree::Token(_, Token::Semi))
 }
+macro_rules! comma {
+    () => (TokenTree::Token(_, Token::Comma))
+}
+macro_rules! fat_arrow {
+    () => (TokenTree::Token(_, Token::FatArrow))
+}
 macro_rules! minus {
     () => (TokenTree::Token(_, Token::BinOp(BinOpToken::Minus)))
 }
@@ -165,6 +172,11 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(2);
                 try!(self.for_expr(sp));
             },
+            // Match
+            [at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::Match) => {
+                self.shift(2);
+                try!(self.match_expr(sp));
+            },
             // Call
             [at!(), ident!(sp, name), ..] if name.name.as_str() == "call" => {
                 self.shift(2);
@@ -300,6 +312,100 @@ impl<'cx, 'i> Parser<'cx, 'i> {
         Ok(())
     }
 
+    /// Parses and renders a `@match` expression.
+    ///
+    /// The leading `@match` should already be consumed.
+    fn match_expr(&mut self, sp: Span) -> PResult<()> {
+        // Parse the initial match
+        let mut match_var = vec![];
+        let match_bodies;
+        loop { match self.input {
+            [TokenTree::Delimited(sp, ref d), ..] if d.delim == DelimToken::Brace => {
+                self.shift(1);
+                match_bodies = try!(Parser {
+                    in_attr: self.in_attr,
+                    input: &d.tts,
+                    span: sp,
+                    render: self.render.fork(),
+                }.match_bodies());
+                break;
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                match_var.push(tt.clone());
+            },
+            [] => parse_error!(self, sp, "expected body for this #match"),
+        }}
+        let match_var = try!(self.with_rust_parser(match_var, RustParser::parse_expr));
+        self.render.emit_match(match_var, match_bodies);
+        Ok(())
+    }
+
+    fn match_bodies(&mut self) -> PResult<Vec<TokenTree>> {
+        let mut bodies = Vec::new();
+        loop {
+            match self.input {
+                [] => break,
+                [ref tt @ comma!(), ..] => {
+                    self.shift(1);
+                    bodies.push(tt.clone());
+                },
+                [TokenTree::Token(sp, _), ..] | [TokenTree::Delimited(sp, _), ..] | [TokenTree::Sequence(sp, _), ..] => {
+                    bodies.append(&mut try!(self.match_body(sp)));
+                },
+            }
+        }
+        Ok(bodies)
+    }
+
+    fn match_body(&mut self, sp: Span) -> PResult<Vec<TokenTree>> {
+        let mut body = vec![];
+        loop { match self.input {
+            [ref tt @ fat_arrow!(), ..] => {
+                self.shift(1);
+                body.push(tt.clone());
+                break;
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                body.push(tt.clone());
+            },
+            _ => parse_error!(self, sp, "invalid #match pattern"),
+        }}
+        let mut expr = Vec::new();
+        loop { match self.input {
+            [TokenTree::Delimited(sp, ref d), ..] if d.delim == DelimToken::Brace => {
+                if expr.is_empty() {
+                    self.shift(1);
+                    expr = try!(self.block(sp, &d.tts)).to_tokens(self.render.cx);
+                    break;
+                } else {
+                    self.shift(1);
+                    expr.push(TokenTree::Delimited(sp, d.clone()));
+                }
+            },
+            [comma!(), ..] | [] => {
+                if expr.is_empty() {
+                    parse_error!(self, sp, "expected body for this #match arm");
+                } else {
+                    expr = try!(self.block(sp, &expr)).to_tokens(self.render.cx);
+                    break;
+                }
+            },
+            [ref tt, ..] => {
+                self.shift(1);
+                expr.push(tt.clone());
+            },
+        }}
+        body.push(TokenTree::Delimited(sp, Rc::new(Delimited {
+          delim: DelimToken::Brace,
+          open_span: sp,
+          tts: expr,
+          close_span: sp,
+        })));
+        Ok(body)
+    }
+
     /// Parses and renders a `^splice`.
     ///
     /// The leading `^` should already be consumed.
diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs
index 0b2bdf5..af9e7fa 100644
--- a/maud_macros/src/render.rs
+++ b/maud_macros/src/render.rs
@@ -172,6 +172,11 @@ impl<'cx> Renderer<'cx> {
         self.push(stmt);
     }
 
+    pub fn emit_match(&mut self, match_var: P<Expr>, match_body: Vec<TokenTree>) {
+        let stmt = quote_stmt!(self.cx, match $match_var { $match_body }).unwrap();
+        self.push(stmt);
+    }
+
     pub fn emit_call(&mut self, func: P<Expr>) {
         let w = self.writer;
         let expr = quote_expr!(self.cx, ($func)(&mut *$w));
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 83d7888..7c55ca0 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -250,6 +250,54 @@ mod control {
                 "<li>Sweetie Belle</li>",
                 "</ul>"));
     }
+
+    #[test]
+    fn match_expr() {
+        for &(input, output) in [(Some("yay"), "<div>yay</div>"), (None, "oh noes")].iter() {
+            let mut s = String::new();
+            html!(s, {
+                @match input {
+                  Some(value) => {
+                    div { ^value }
+                  },
+                  None => {
+                    "oh noes"
+                  },
+                }
+            }).unwrap();
+            assert_eq!(s, output);
+        }
+    }
+
+    #[test]
+    fn match_expr_without_delims() {
+        for &(input, output) in [(Some("yay"), "yay"), (None, "<span>oh noes</span>")].iter() {
+            let mut s = String::new();
+            html!(s, {
+                @match input {
+                  Some(value) => ^value,
+                  None => span { "oh noes" },
+                }
+            }).unwrap();
+            assert_eq!(s, output);
+        }
+    }
+
+
+    #[test]
+    fn match_expr_with_guards() {
+        for &(input, output) in [(Some(1), "one"), (None, "none"), (Some(2), "2")].iter() {
+            let mut s = String::new();
+            html!(s, {
+                @match input {
+                  Some(value) if value == 1 => "one",
+                  Some(value) => ^value,
+                  None => "none",
+                }
+            }).unwrap();
+            assert_eq!(s, output);
+        }
+    }
 }
 
 #[test]

From f35707cce012a8a3afea462f4e01071cfa806b6e Mon Sep 17 00:00:00 2001
From: Wim Looman <wim@nemo157.com>
Date: Mon, 1 Feb 2016 23:23:31 +0100
Subject: [PATCH 2/3] Add test for match inside an attribute

---
 maud_macros/tests/tests.rs | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 7c55ca0..b287372 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -283,7 +283,6 @@ mod control {
         }
     }
 
-
     #[test]
     fn match_expr_with_guards() {
         for &(input, output) in [(Some(1), "one"), (None, "none"), (Some(2), "2")].iter() {
@@ -298,6 +297,21 @@ mod control {
             assert_eq!(s, output);
         }
     }
+
+    #[test]
+    fn match_in_attribute() {
+        for &(input, output) in [(1, "<span class=\"one\">1</span>"), (2, "<span class=\"two\">2</span>"), (3, "<span class=\"many\">3</span>")].iter() {
+            let mut s = String::new();
+            html!(s, {
+                span class=@match input {
+                    1 => "one",
+                    2 => "two",
+                    _ => "many",
+                } { ^input }
+            }).unwrap();
+            assert_eq!(s, output);
+        }
+    }
 }
 
 #[test]

From 1d23d5f4c1f22f0e23c1b8b661c6777566e93f57 Mon Sep 17 00:00:00 2001
From: Wim Looman <wim@nemo157.com>
Date: Wed, 10 Feb 2016 11:47:22 +0100
Subject: [PATCH 3/3] #match -> @match for errors

---
 maud_macros/src/parse.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index dda7e99..6a74305 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -334,7 +334,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(1);
                 match_var.push(tt.clone());
             },
-            [] => parse_error!(self, sp, "expected body for this #match"),
+            [] => parse_error!(self, sp, "expected body for this @match"),
         }}
         let match_var = try!(self.with_rust_parser(match_var, RustParser::parse_expr));
         self.render.emit_match(match_var, match_bodies);
@@ -370,7 +370,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
                 self.shift(1);
                 body.push(tt.clone());
             },
-            _ => parse_error!(self, sp, "invalid #match pattern"),
+            _ => parse_error!(self, sp, "invalid @match pattern"),
         }}
         let mut expr = Vec::new();
         loop { match self.input {
@@ -386,7 +386,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
             },
             [comma!(), ..] | [] => {
                 if expr.is_empty() {
-                    parse_error!(self, sp, "expected body for this #match arm");
+                    parse_error!(self, sp, "expected body for this @match arm");
                 } else {
                     expr = try!(self.block(sp, &expr)).to_tokens(self.render.cx);
                     break;