From 37419d9781f3257a35025868b9c8ad23d950f9a8 Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Sat, 29 Jul 2017 14:30:52 +1200
Subject: [PATCH] Implement match expressions and remove debugging stuff

---
 maud_macros/src/parse.rs | 97 ++++++++++++++++++++++++++++++++++------
 1 file changed, 84 insertions(+), 13 deletions(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 1f7066a..023de7e 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -1,4 +1,4 @@
-use proc_macro::{Delimiter, Literal, TokenNode, TokenStream, TokenTree, TokenTreeIter};
+use proc_macro::{Delimiter, Literal, Spacing, TokenNode, TokenStream, TokenTree, TokenTreeIter, quote};
 use std::mem;
 
 use literalext::LiteralExt;
@@ -11,13 +11,7 @@ pub fn parse(input: TokenStream) -> ParseResult<TokenStream> {
     let _ = Parser {
         in_attr: false,
         input: input.clone().into_iter(),
-    }.markups(&mut render);
-    /*
-    Parser {
-        in_attr: false,
-        input: input.clone().into_iter(),
     }.markups(&mut render)?;
-    */
     // Heuristic: the size of the resulting markup tends to correlate with the
     // code size of the template itself
     let size_hint = input.to_string().len();
@@ -57,7 +51,7 @@ impl Parser {
         *self = attempt;
     }
 
-    /// Attaches an error message to the span and returns `Err`.
+    /// Returns an `Err` with the given message.
     fn error<T, E: Into<String>>(&self, message: E) -> ParseResult<T> {
         Err(message.into())
     }
@@ -231,15 +225,92 @@ impl Parser {
     ///
     /// The leading `@match` should already be consumed.
     fn match_expr(&mut self, render: &mut Renderer) -> ParseResult<()> {
-        self.error("unimplemented")
+        let mut head = Vec::new();
+        let body = loop {
+            match self.next() {
+                Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, body), .. }) => {
+                    let mut parse = Parser {
+                        in_attr: self.in_attr,
+                        input: body.into_iter(),
+                    };
+                    break parse.match_arms(render)?;
+                },
+                Some(token) => head.push(token),
+                None => return self.error("unexpected end of @match expression"),
+            }
+        };
+        render.emit_match(head.into_iter().collect(), body);
+        Ok(())
     }
 
-    fn match_bodies(&mut self, render: &mut Renderer) -> ParseResult<Vec<TokenTree>> {
-        self.error("unimplemented")
+    fn match_arms(&mut self, render: &mut Renderer) -> ParseResult<TokenStream> {
+        let mut arms = Vec::new();
+        while let Some(arm) = self.match_arm(render)? {
+            arms.push(arm);
+        }
+        Ok(arms.into_iter().collect())
     }
 
-    fn match_body(&mut self, render: &mut Renderer) -> ParseResult<Vec<TokenTree>> {
-        self.error("unimplemented")
+    fn match_arm(&mut self, render: &mut Renderer) -> ParseResult<Option<TokenStream>> {
+        let mut pat = Vec::new();
+        loop {
+            match self.peek2() {
+                Some((
+                    eq @ TokenTree { kind: TokenNode::Op('=', Spacing::Joint), .. },
+                    Some(gt @ TokenTree { kind: TokenNode::Op('>', _), .. }),
+                )) => {
+                    self.advance2();
+                    pat.push(eq);
+                    pat.push(gt);
+                    break;
+                },
+                Some((token, _)) => {
+                    self.advance();
+                    pat.push(token);
+                },
+                None =>
+                    if pat.is_empty() {
+                        return Ok(None);
+                    } else {
+                        return self.error("unexpected end of @match pattern");
+                    },
+            }
+        }
+        let body = match self.next() {
+            // $pat => { $stmts }
+            Some(TokenTree { kind: TokenNode::Group(Delimiter::Brace, body), span }) => {
+                let body = self.block(body, render)?;
+                // Trailing commas are optional if the match arm is a braced block
+                if let Some(TokenTree { kind: TokenNode::Op(',', _), .. }) = self.peek() {
+                    self.advance();
+                }
+                // Re-use the span from the original block
+                TokenTree {
+                    kind: TokenNode::Group(Delimiter::Brace, body),
+                    span,
+                }.into()
+            },
+            // $pat => $expr
+            Some(first_token) => {
+                let mut body = vec![first_token];
+                loop {
+                    match self.next() {
+                        Some(TokenTree { kind: TokenNode::Op(',', _), .. }) => break,
+                        Some(token) => {
+                            body.push(token);
+                        },
+                        None => return self.error("unexpected end of @match arm"),
+                    }
+                }
+                let body = self.block(body.into_iter().collect(), render)?;
+                // The generated code may have multiple statements, unlike the
+                // original expression. So wrap the whole thing in a block just
+                // in case.
+                quote!({ $body })
+            },
+            None => return self.error("unexpected end of @match arm"),
+        };
+        Ok(Some(pat.into_iter().chain(body.into_iter()).collect()))
     }
 
     /// Parses and renders a `@let` expression.