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] 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]