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;