diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index 246de31..156025e 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -156,6 +156,11 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> { self.shift(2); self.match_expr(sp)?; }, + // Let + [at!(), keyword!(sp, k), ..] if k.is_keyword(keywords::Let) => { + self.shift(2); + self.let_expr(sp)?; + } // Element [ident!(sp, _), ..] => { let name = self.namespaced_name().unwrap(); @@ -402,6 +407,42 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> { Ok(body) } + /// Parses and renders a `@let` expression. + /// + /// The leading `@let` should already be consumed. + fn let_expr(&mut self, sp: Span) -> PResult<()> { + let mut pattern = vec![]; + loop { match *self.input { + [eq!(), ..] => { + self.shift(1); + break; + }, + [ref tt, ..] => { + self.shift(1); + pattern.push(tt.clone()); + }, + _ => parse_error!(self, sp, "invalid @let"), + }} + let pattern = self.with_rust_parser(pattern, RustParser::parse_pat)?; + let mut rhs = vec![]; + let body; + loop { match *self.input { + [TokenTree::Delimited(sp, ref d), ..] if d.delim == DelimToken::Brace => { + self.shift(1); + body = self.block(sp, &d.tts)?; + break; + }, + [ref tt, ..] => { + self.shift(1); + rhs.push(tt.clone()); + }, + _ => parse_error!(self, sp, "invalid @let"), + }} + let rhs = self.with_rust_parser(rhs, RustParser::parse_expr)?; + self.render.emit_let(pattern, rhs, body); + Ok(()) + } + /// Parses and renders an element node. /// /// The element name should already be consumed. diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs index f1b034f..10222da 100644 --- a/maud_macros/src/render.rs +++ b/maud_macros/src/render.rs @@ -156,6 +156,11 @@ impl<'cx, 'a> Renderer<'cx, 'a> { let stmt = quote_stmt!(self.cx, match $match_var { $match_body }).unwrap(); self.push(stmt); } + + pub fn emit_let(&mut self, pattern: P<Pat>, rhs: P<Expr>, body: Vec<Stmt>) { + let stmt = quote_stmt!(self.cx, { let $pattern = $rhs; $body }).unwrap(); + self.push(stmt); + } } fn html_escape(s: &str) -> String { diff --git a/maud_macros/tests/control_structures.rs b/maud_macros/tests/control_structures.rs index 42c40b3..7d574f5 100644 --- a/maud_macros/tests/control_structures.rs +++ b/maud_macros/tests/control_structures.rs @@ -131,3 +131,27 @@ fn match_in_attribute() { assert_eq!(s, output); } } + +#[test] +fn let_expr() { + let s = html! { + @let x = 42 { + "I have " (x) " cupcakes!" + } + }.into_string(); + assert_eq!(s, "I have 42 cupcakes!"); +} + +#[test] +fn let_lexical_scope() { + let x = 42; + let s = html! { + @let x = 99 { + "Twilight thought I had " (x) " cupcakes, " + } + "but I only had " (x) "." + }.into_string(); + assert_eq!(s, concat!( + "Twilight thought I had 99 cupcakes, ", + "but I only had 42.")); +}