Merge pull request from Nemo157/match

Add support for a match keyword
This commit is contained in:
Chris Wong 2016-02-10 22:45:50 +11:00
commit b738d9d533
3 changed files with 174 additions and 1 deletions
maud_macros

View file

@ -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.

View file

@ -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));

View file

@ -250,6 +250,68 @@ 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]
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]