From 6c8fbb5badd04c3a7c3edf9b450d3c51bf72b672 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Tue, 2 Feb 2016 15:22:45 +0100 Subject: [PATCH 1/6] Change splice operator and add in render by-move --- maud/src/lib.rs | 11 ++++++ maud_macros/src/parse.rs | 28 +++++--------- maud_macros/src/render.rs | 2 +- maud_macros/tests/tests.rs | 75 +++++++++++++++++++++++++++++++------- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index b012cf42..d194135b 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -23,6 +23,17 @@ impl Render for T { } } +/// Represents a type that can be rendered as HTML just once. +pub trait RenderOnce { + fn render_once(self, &mut fmt::Write) -> fmt::Result; +} + +impl<'a, T: Render + ?Sized> RenderOnce for &'a T { + fn render_once(self, w: &mut fmt::Write) -> fmt::Result { + Render::render(self, w) + } +} + /// A wrapper that renders the inner value without escaping. #[derive(Debug)] pub struct PreEscaped(pub T); diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index 713eafc3..8371927b 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -23,9 +23,6 @@ macro_rules! parse_error { ($self_:expr, $sp:expr, $msg:expr) => (error!($self_.render.cx, $sp, $msg)) } -macro_rules! dollar { - () => (TokenTree::Token(_, Token::Dollar)) -} macro_rules! pound { () => (TokenTree::Token(_, Token::Pound)) } @@ -53,6 +50,9 @@ macro_rules! minus { macro_rules! slash { () => (TokenTree::Token(_, Token::BinOp(BinOpToken::Slash))) } +macro_rules! caret { + () => (TokenTree::Token(_, Token::BinOp(BinOpToken::Caret))) +} macro_rules! literal { () => (TokenTree::Token(_, Token::Literal(..))) } @@ -172,19 +172,11 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.render.emit_call(func); }, // Splice - [ref tt @ dollar!(), ..] => { + [ref tt @ caret!(), ..] => { self.shift(1); let expr = try!(self.splice(tt.get_span())); self.render.splice(expr); }, - [substnt!(sp, ident), ..] => { - self.shift(1); - // Parse `SubstNt` as `[Dollar, Ident]` - // See - let prefix = TokenTree::Token(sp, Token::Ident(ident, IdentStyle::Plain)); - let expr = try!(self.splice_with_prefix(prefix)); - self.render.splice(expr); - }, // Element [ident!(sp, _), ..] => { let name = try!(self.name()); @@ -308,9 +300,9 @@ impl<'cx, 'i> Parser<'cx, 'i> { Ok(()) } - /// Parses and renders a `$splice`. + /// Parses and renders a `^splice`. /// - /// The leading `$` should already be consumed. + /// The leading `^` should already be consumed. fn splice(&mut self, sp: Span) -> PResult> { // First, munch a single token tree let prefix = match self.input { @@ -323,24 +315,24 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.splice_with_prefix(prefix) } - /// Parses and renders a `$splice`, given a prefix that we've already + /// Parses and renders a `^splice`, given a prefix that we've already /// consumed. fn splice_with_prefix(&mut self, prefix: TokenTree) -> PResult> { let mut tts = vec![prefix]; loop { match self.input { - // Munch attribute lookups e.g. `$person.address.street` + // Munch attribute lookups e.g. `^person.address.street` [ref dot @ dot!(), ref ident @ ident!(_, _), ..] => { self.shift(2); tts.push(dot.clone()); tts.push(ident.clone()); }, - // Munch tuple attribute lookups e.g. `$person.1.2` + // Munch tuple attribute lookups e.g. `^person.1.2` [ref dot @ dot!(), ref num @ integer!(), ..] => { self.shift(2); tts.push(dot.clone()); tts.push(num.clone()); }, - // Munch path lookups e.g. `$some_mod::Struct` + // Munch path lookups e.g. `^some_mod::Struct` [ref sep @ modsep!(), ref ident @ ident!(_, _), ..] => { self.shift(2); tts.push(sep.clone()); diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs index 8c52caea..0b2bdf57 100644 --- a/maud_macros/src/render.rs +++ b/maud_macros/src/render.rs @@ -118,7 +118,7 @@ impl<'cx> Renderer<'cx> { /// Appends the result of an expression, with the specified escaping method. pub fn splice(&mut self, expr: P) { let w = self.writer; - let expr = quote_expr!(self.cx, ::maud::Render::render(&$expr, &mut *$w)); + let expr = quote_expr!(self.cx, { use ::maud::RenderOnce; $expr.render_once(&mut *$w) }); let stmt = self.wrap_try(expr); self.push(stmt); } diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index 265cc004..be02f407 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -93,7 +93,7 @@ mod splices { #[test] fn literals() { let mut s = String::new(); - html!(s, $"").unwrap(); + html!(s, ^"").unwrap(); assert_eq!(s, "<pinkie>"); } @@ -101,7 +101,7 @@ mod splices { fn raw_literals() { use maud::PreEscaped; let mut s = String::new(); - html!(s, $PreEscaped("")).unwrap(); + html!(s, ^PreEscaped("")).unwrap(); assert_eq!(s, ""); } @@ -109,7 +109,7 @@ mod splices { fn blocks() { let mut s = String::new(); html!(s, { - ${ + ^{ let mut result = 1i32; for i in 2..11 { result *= i; @@ -142,7 +142,7 @@ mod splices { #[test] fn statics() { let mut s = String::new(); - html!(s, $BEST_PONY).unwrap(); + html!(s, ^BEST_PONY).unwrap(); assert_eq!(s, "Pinkie Pie"); } @@ -150,7 +150,7 @@ mod splices { fn closures() { let best_pony = "Pinkie Pie"; let mut s = String::new(); - html!(s, $best_pony).unwrap(); + html!(s, ^best_pony).unwrap(); assert_eq!(s, "Pinkie Pie"); } @@ -177,7 +177,7 @@ mod splices { }; let mut s = String::new(); html!(s, { - "Name: " $pinkie.name ". Rating: " $pinkie.repugnance() + "Name: " ^pinkie.name ". Rating: " ^pinkie.repugnance() }).unwrap(); assert_eq!(s, "Name: Pinkie Pie. Rating: 1"); } @@ -186,7 +186,7 @@ mod splices { fn nested_macro_invocation() { let best_pony = "Pinkie Pie"; let mut s = String::new(); - html!(s, $(format!("{}", best_pony))).unwrap(); + html!(s, ^(format!("{}", best_pony))).unwrap(); assert_eq!(s, "Pinkie Pie"); } } @@ -195,7 +195,7 @@ mod splices { fn issue_13() { let owned = String::from("yay"); let mut s = String::new(); - html!(s, $owned).unwrap(); + html!(s, ^owned).unwrap(); let _ = owned; } @@ -225,7 +225,7 @@ mod control { let mut s = String::new(); html!(s, { #if let Some(value) = input { - $value + ^value } #else { "oh noes" } @@ -240,7 +240,7 @@ mod control { let mut s = String::new(); html!(s, { ul #for pony in &ponies { - li $pony + li ^pony } }).unwrap(); assert_eq!(s, concat!( @@ -306,7 +306,7 @@ fn issue_23() { } let name = "Lyra"; - let s = to_string!(p { "Hi, " $name "!" }); + let s = to_string!(p { "Hi, " ^name "!" }); assert_eq!(s, "

Hi, Lyra!

"); } @@ -314,7 +314,7 @@ fn issue_23() { fn tuple_accessors() { let mut s = String::new(); let a = ("ducks", "geese"); - html!(s, { $a.0 }).unwrap(); + html!(s, { ^a.0 }).unwrap(); assert_eq!(s, "ducks"); } @@ -327,6 +327,55 @@ fn splice_with_path() { } let mut s = String::new(); - html!(s, $inner::name()).unwrap(); + html!(s, ^inner::name()).unwrap(); assert_eq!(s, "Maud"); } + +#[test] +fn multirender() { + struct R<'a>(&'a str); + impl<'a> maud::Render for R<'a> { + fn render(&self, w: &mut std::fmt::Write) -> std::fmt::Result { + w.write_str(self.0) + } + } + + let mut s = String::new(); + let r = R("pinkie "); + html!(s, ^r).unwrap(); + html!(s, ^r).unwrap(); + // R is not-Copyable so this shows that it will auto-ref splice arguments that implement Render. + assert_eq!(s, "pinkie pinkie "); +} + +#[test] +fn render_once_by_move() { + struct Once<'a>(&'a str); + impl<'a> maud::RenderOnce for Once<'a> { + fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result { + w.write_str(self.0) + } + } + + let mut s = String::new(); + let once = Once("pinkie"); + html!(s, ^once).unwrap(); + assert_eq!(s, "pinkie"); +} + +#[test] +fn render_once_by_move_with_copy() { + #[derive(Clone, Copy)] + struct Once<'a>(&'a str); + impl<'a> maud::RenderOnce for Once<'a> { + fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result { + w.write_str(self.0) + } + } + + let mut s = String::new(); + let once = Once("pinkie "); + html!(s, ^once).unwrap(); + html!(s, ^once).unwrap(); + assert_eq!(s, "pinkie pinkie "); +} From 831df66cdbbac92f52bd97947442872f72d4c645 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Tue, 2 Feb 2016 16:00:14 +0100 Subject: [PATCH 2/6] Add tests for issue #26 --- maud_macros/tests/tests.rs | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index be02f407..027e8fac 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -379,3 +379,48 @@ fn render_once_by_move_with_copy() { html!(s, ^once).unwrap(); assert_eq!(s, "pinkie pinkie "); } + +#[test] +fn issue_26() { + macro_rules! to_string { + ($($x:tt)*) => {{ + let mut s = String::new(); + html!(s, $($x)*).unwrap(); + s + }} + } + + let name = "Lyra"; + let s = to_string!(p { "Hi, " ^(name) "!" }); + assert_eq!(s, "

Hi, Lyra!

"); +} + +#[test] +fn issue_26_2() { + macro_rules! to_string { + ($($x:tt)*) => {{ + let mut s = String::new(); + html!(s, $($x)*).unwrap(); + s + }} + } + + let name = "Lyra"; + let s = to_string!(p { "Hi, " ^("person called ".to_string() + name) "!" }); + assert_eq!(s, "

Hi, person called Lyra!

"); +} + +#[test] +fn issue_26_3() { + macro_rules! to_string { + ($($x:tt)*) => {{ + let mut s = String::new(); + html!(s, $($x)*).unwrap(); + s + }} + } + + let name = "Lyra"; + let s = to_string!(p { "Hi, " ^{"person called ".to_string() + name} "!" }); + assert_eq!(s, "

Hi, person called Lyra!

"); +} From bd0b135a0da30185b00384c602e42c3bb75f6519 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Tue, 2 Feb 2016 16:08:03 +0100 Subject: [PATCH 3/6] Replace # -> @ and remove from else --- maud_macros/src/parse.rs | 14 +++++++------- maud_macros/tests/tests.rs | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index 8371927b..c3d0bc20 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -23,8 +23,8 @@ macro_rules! parse_error { ($self_:expr, $sp:expr, $msg:expr) => (error!($self_.render.cx, $sp, $msg)) } -macro_rules! pound { - () => (TokenTree::Token(_, Token::Pound)) +macro_rules! at { + () => (TokenTree::Token(_, Token::At)) } macro_rules! dot { () => (TokenTree::Token(_, Token::Dot)) @@ -156,17 +156,17 @@ impl<'cx, 'i> Parser<'cx, 'i> { try!(self.literal(tt, false)) }, // If - [pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => { + [at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => { self.shift(2); try!(self.if_expr(sp)); }, // For - [pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => { + [at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => { self.shift(2); try!(self.for_expr(sp)); }, // Call - [pound!(), ident!(sp, name), ..] if name.name.as_str() == "call" => { + [at!(), ident!(sp, name), ..] if name.name.as_str() == "call" => { self.shift(2); let func = try!(self.splice(sp)); self.render.emit_call(func); @@ -235,8 +235,8 @@ impl<'cx, 'i> Parser<'cx, 'i> { }} // Parse the (optional) else let else_body = match self.input { - [pound!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => { - self.shift(2); + [keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => { + self.shift(1); match self.input { [keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => { self.shift(1); diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index 027e8fac..a7ce1560 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -205,13 +205,13 @@ mod control { for (number, &name) in (1..4).zip(["one", "two", "three"].iter()) { let mut s = String::new(); html!(s, { - #if number == 1 { + @if number == 1 { "one" - } #else if number == 2 { + } else if number == 2 { "two" - } #else if number == 3 { + } else if number == 3 { "three" - } #else { + } else { "oh noes" } }).unwrap(); @@ -224,9 +224,9 @@ mod control { for &(input, output) in [(Some("yay"), "yay"), (None, "oh noes")].iter() { let mut s = String::new(); html!(s, { - #if let Some(value) = input { + @if let Some(value) = input { ^value - } #else { + } else { "oh noes" } }).unwrap(); @@ -239,7 +239,7 @@ mod control { let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"]; let mut s = String::new(); html!(s, { - ul #for pony in &ponies { + ul @for pony in &ponies { li ^pony } }).unwrap(); @@ -288,9 +288,9 @@ fn call() { panic!("oh noes") }; html!(s, { - #call ducks - #call (|w: &mut fmt::Write| write!(w, "Geese")) - #call swans(true) + @call ducks + @call (|w: &mut fmt::Write| write!(w, "Geese")) + @call swans(true) }).unwrap(); assert_eq!(s, "DucksGeeseSwans"); } From 0e1bd1c926ff402e600bfdd0403d9cce053f37c9 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Tue, 2 Feb 2016 16:17:10 +0100 Subject: [PATCH 4/6] Add tests for issue #21 --- maud_macros/tests/tests.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index a7ce1560..f49a617f 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -424,3 +424,30 @@ fn issue_26_3() { let s = to_string!(p { "Hi, " ^{"person called ".to_string() + name} "!" }); assert_eq!(s, "

Hi, person called Lyra!

"); } + +#[test] +fn issue_21() { + macro_rules! greet { + () => ({ + let mut result = String::new(); + let name = "Pinkie Pie"; + html!(result, p { "Hello, " ^name "!" }).map(|()| result) + }) + } + + let s = greet!().unwrap(); + assert_eq!(s, "

Hello, Pinkie Pie!

"); +} + +#[test] +fn issue_21_2() { + macro_rules! greet { + ($name:expr) => ({ + let mut result = String::new(); + html!(result, p { "Hello, " ^$name "!" }).map(|()| result) + }) + } + + let s = greet!("Pinkie Pie").unwrap(); + assert_eq!(s, "

Hello, Pinkie Pie!

"); +} From 393873904eee3f08eabbd28a5ed69645e661e24c Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Wed, 3 Feb 2016 11:50:13 +0100 Subject: [PATCH 5/6] Fix up comments and error messages --- maud_macros/src/parse.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index c3d0bc20..a8b33deb 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -214,9 +214,9 @@ impl<'cx, 'i> Parser<'cx, 'i> { Ok(()) } - /// Parses and renders an `#if` expression. + /// Parses and renders an `@if` expression. /// - /// The leading `#if` should already be consumed. + /// The leading `@if` should already be consumed. fn if_expr(&mut self, sp: Span) -> PResult<()> { // Parse the initial if let mut if_cond = vec![]; @@ -231,7 +231,7 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.shift(1); if_cond.push(tt.clone()); }, - [] => parse_error!(self, sp, "expected body for this #if"), + [] => parse_error!(self, sp, "expected body for this @if"), }} // Parse the (optional) else let else_body = match self.input { @@ -255,7 +255,7 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.shift(1); Some(try!(self.block(sp, &d.tts))) }, - _ => parse_error!(self, sp, "expected body for this #else"), + _ => parse_error!(self, sp, "expected body for this @else"), } }, _ => None, @@ -264,9 +264,9 @@ impl<'cx, 'i> Parser<'cx, 'i> { Ok(()) } - /// Parses and renders a `#for` expression. + /// Parses and renders a `@for` expression. /// - /// The leading `#for` should already be consumed. + /// The leading `@for` should already be consumed. fn for_expr(&mut self, sp: Span) -> PResult<()> { let mut pattern = vec![]; loop { match self.input { @@ -278,7 +278,7 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.shift(1); pattern.push(tt.clone()); }, - _ => parse_error!(self, sp, "invalid #for"), + _ => parse_error!(self, sp, "invalid @for"), }} let pattern = try!(self.with_rust_parser(pattern, RustParser::parse_pat)); let mut iterable = vec![]; @@ -293,7 +293,7 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.shift(1); iterable.push(tt.clone()); }, - _ => parse_error!(self, sp, "invalid #for"), + _ => parse_error!(self, sp, "invalid @for"), }} let iterable = try!(self.with_rust_parser(iterable, RustParser::parse_expr)); self.render.emit_for(pattern, iterable, body); From 1b307449f34c7d55b79932bbbd74b43db1db6258 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Sun, 7 Feb 2016 11:02:27 +0100 Subject: [PATCH 6/6] Add @ prefix on `else` back in --- maud_macros/src/parse.rs | 6 +++--- maud_macros/tests/tests.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index a8b33deb..6e44ac26 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -233,10 +233,10 @@ impl<'cx, 'i> Parser<'cx, 'i> { }, [] => parse_error!(self, sp, "expected body for this @if"), }} - // Parse the (optional) else + // Parse the (optional) @else let else_body = match self.input { - [keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => { - self.shift(1); + [at!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => { + self.shift(2); match self.input { [keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => { self.shift(1); diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index f49a617f..f1667dfc 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -207,11 +207,11 @@ mod control { html!(s, { @if number == 1 { "one" - } else if number == 2 { + } @else if number == 2 { "two" - } else if number == 3 { + } @else if number == 3 { "three" - } else { + } @else { "oh noes" } }).unwrap(); @@ -226,7 +226,7 @@ mod control { html!(s, { @if let Some(value) = input { ^value - } else { + } @else { "oh noes" } }).unwrap();