From 05c68067ddcf0ab0d0602a04dcf577cf8673da4a Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Wed, 7 Jan 2015 17:43:37 +1300 Subject: [PATCH] Rewrite the parser yay --- maud_macros/src/lib.rs | 2 +- maud_macros/src/parse.rs | 243 +++++++++++++++++++++++++++---------- maud_macros/src/render.rs | 5 + maud_macros/tests/tests.rs | 62 ++++++++++ 4 files changed, 246 insertions(+), 66 deletions(-) diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index e7ba5f4..0aa8a8f 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -14,7 +14,7 @@ mod parse; mod render; fn expand_html<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'cx> { - match parse::parse(cx, &*args) { + match parse::parse(cx, args) { Some(markups) => { let expr = render::render(cx, &*markups); MacExpr::new(expr) diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index b38e1ba..6cf16cd 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -1,12 +1,14 @@ -use syntax::ast::{Expr, Lit, TokenTree, TtToken}; +use syntax::ast::{Expr, Lit, TokenTree, TtDelimited, TtToken}; use syntax::ext::base::ExtCtxt; use syntax::parse; +use syntax::parse::parser::Parser as RustParser; use syntax::parse::token; use syntax::ptr::P; #[derive(Show)] pub enum Markup { - Element(Vec<(String, Value)>, Vec<Markup>), + Element(String, Vec<(String, Value)>, Option<Box<Markup>>), + Block(Vec<Markup>), Value(Value), } @@ -16,22 +18,6 @@ pub struct Value { pub escape: Escape, } -impl Value { - pub fn escape(value: Value_) -> Value { - Value { - value: value, - escape: Escape::Escape, - } - } - - pub fn no_escape(value: Value_) -> Value { - Value { - value: value, - escape: Escape::NoEscape, - } - } -} - #[derive(Show)] pub enum Value_ { Literal(String), @@ -44,54 +30,191 @@ pub enum Escape { Escape, } -pub fn parse(cx: &mut ExtCtxt, mut args: &[TokenTree]) -> Option<Vec<Markup>> { - macro_rules! semi { - () => (TtToken(_, token::Semi)) - } - macro_rules! minus { - () => (TtToken(_, token::BinOp(token::Minus))) - } - macro_rules! literal { - () => (TtToken(_, token::Literal(..))) +macro_rules! some { + ($e:expr) => ( + match $e { + Some(x) => x, + None => return None, + } + ) +} + +macro_rules! any { + ($self_:expr;) => (None); + ($self_:expr; $e:expr) => (any!($self_; $e,)); + ($self_:expr; $e:expr, $($es:expr),*) => ({ + let start_ptr = $self_.input.as_ptr(); + match $e { + Some(x) => Some(x), + None => { + if $self_.input.as_ptr() == start_ptr { + // Parsing failed, but did not consume input. + // Keep going. + any!($self_; $($es),*) + } else { + return None; + } + }, + } + }) +} + +macro_rules! dollar { + () => (TtToken(_, token::Dollar)) +} +macro_rules! eq { + () => (TtToken(_, token::Eq)) +} +macro_rules! semi { + () => (TtToken(_, token::Semi)) +} +macro_rules! minus { + () => (TtToken(_, token::BinOp(token::Minus))) +} +macro_rules! literal { + () => (TtToken(_, token::Literal(..))) +} +macro_rules! ident { + ($x:pat) => (TtToken(_, token::Ident($x, token::IdentStyle::Plain))) +} + +pub fn parse(cx: &mut ExtCtxt, input: &[TokenTree]) -> Option<Vec<Markup>> { + Parser { cx: cx, input: input }.markups() +} + +struct Parser<'cx, 's: 'cx, 'i> { + cx: &'cx mut ExtCtxt<'s>, + input: &'i [TokenTree], +} + +impl<'cx, 's, 'i> Parser<'cx, 's, 'i> { + /// Consume `n` items from the input. + fn shift(&mut self, n: uint) { + self.input = self.input.slice_from(n); } - let mut result = vec![]; - loop { - match match args { - [semi!(), ..] => { - args.shift(1); - continue - }, + /// Construct a Rust AST parser from the given token tree. + fn new_rust_parser(&self, tts: Vec<TokenTree>) -> RustParser<'s> { + parse::tts_to_parser(self.cx.parse_sess, tts, self.cx.cfg.clone()) + } + + fn markups(&mut self) -> Option<Vec<Markup>> { + let mut result = vec![]; + loop { + match self.input { + [] => return Some(result), + [semi!(), ..] => self.shift(1), + [ref tt, ..] => { + match self.markup() { + Some(markup) => result.push(markup), + None => { + self.cx.span_err(tt.get_span(), "invalid syntax"); + return None; + }, + } + } + } + } + } + + fn markup(&mut self) -> Option<Markup> { + any!(self; + self.value().map(Markup::Value), + self.block(), + self.element()) + } + + fn value(&mut self) -> Option<Value> { + any!(self; + self.literal(), + self.splice()) + } + + fn literal(&mut self) -> Option<Value> { + let (tt, minus) = match self.input { [minus!(), ref tt @ literal!(), ..] => { - args.shift(2); - parse_literal(cx, tt, true) + self.shift(2); + (tt, true) }, [ref tt @ literal!(), ..] => { - args.shift(1); - parse_literal(cx, tt, false) + self.shift(1); + (tt, false) + }, + _ => return None, + }; + let lit = self.new_rust_parser(vec![tt.clone()]).parse_lit(); + lit_to_string(self.cx, lit, minus) + .map(|s| Value { + value: Value_::Literal(s), + escape: Escape::Escape, + }) + } + + fn splice(&mut self) -> Option<Value> { + let (escape, sp) = match self.input { + [ref tt @ dollar!(), dollar!(), ..] => { + self.shift(2); + (Escape::NoEscape, tt.get_span()) + }, + [ref tt @ dollar!(), ..] => { + self.shift(1); + (Escape::Escape, tt.get_span()) + }, + _ => return None, + }; + let tt = match self.input { + [ref tt, ..] => { + self.shift(1); + self.new_rust_parser(vec![tt.clone()]).parse_expr() + }, + _ => { + self.cx.span_err(sp, "expected expression for this splice"); + return None; + }, + }; + Some(Value { + value: Value_::Splice(tt), + escape: escape, + }) + } + + fn element(&mut self) -> Option<Markup> { + let name = match self.input { + [ident!(name), ..] => { + self.shift(1); + name.as_str().to_string() + }, + _ => return None, + }; + let attrs = some!(self.attrs()); + let body = any!(self; self.markup()); + Some(Markup::Element(name, attrs, body.map(|body| box body))) + } + + fn attrs(&mut self) -> Option<Vec<(String, Value)>> { + let mut attrs = vec![]; + while let [ident!(name), eq!(), ..] = self.input { + self.shift(2); + let name = name.as_str().to_string(); + let value = some!(self.value()); + attrs.push((name, value)); + } + Some(attrs) + } + + fn block(&mut self) -> Option<Markup> { + match self.input { + [TtDelimited(_, ref d), ..] if d.delim == token::DelimToken::Brace => { + self.shift(1); + Parser { cx: self.cx, input: &*d.tts }.markups() + .map(Markup::Block) }, _ => None, - } { - Some(x) => result.push(x), - None => break, - } - } - match args { - [] => Some(result), - [ref tt, ..] => { - cx.span_err(tt.get_span(), "invalid syntax"); - None } } } -fn parse_literal(cx: &mut ExtCtxt, tt: &TokenTree, minus: bool) -> Option<Markup> { - let mut parser = parse::tts_to_parser(cx.parse_sess, vec![tt.clone()], cx.cfg.clone()); - let lit = parser.parse_lit(); - lit_to_string(cx, lit, minus) - .map(|s| Markup::Value(Value::escape(Value_::Literal(s)))) -} - +/// Convert a literal to a string. fn lit_to_string(cx: &mut ExtCtxt, lit: Lit, minus: bool) -> Option<String> { use syntax::ast::Lit_::*; let mut result = String::new(); @@ -111,13 +234,3 @@ fn lit_to_string(cx: &mut ExtCtxt, lit: Lit, minus: bool) -> Option<String> { }; Some(result) } - -trait Shift { - fn shift(&mut self, n: uint); -} - -impl<'a, T> Shift for &'a [T] { - fn shift(&mut self, n: uint) { - *self = self.slice_from(n); - } -} diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs index 4d9d444..7d4fceb 100644 --- a/maud_macros/src/render.rs +++ b/maud_macros/src/render.rs @@ -23,6 +23,11 @@ fn render_markup(cx: &mut ExtCtxt, markup: &Markup, w: Ident, out: &mut Vec<P<St use super::parse::Markup::*; match *markup { Element(..) => unimplemented!(), + Block(ref markups) => { + for markup in markups.iter() { + render_markup(cx, markup, w, out); + } + } Value(ref value) => { let expr = render_value(cx, value, w, false); out.push(quote_stmt!(cx, $expr)); diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index 6ffede0..cd8b576 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -16,3 +16,65 @@ fn escaping() { let s = maud::render(template); assert_eq!(&*s, "<flim&flam>"); } + +#[test] +fn blocks() { + let s = maud::render(html! { + "hello" + { + " ducks"; + " geese"; + } + " swans" + }); + assert_eq!(&*s, "hello ducks geese swans"); +} + +mod splice { + use super::maud; // lol + + #[test] + fn literal() { + let s = maud::render(html! { $"<pinkie>" }); + assert_eq!(&*s, "<pinkie>"); + } + + #[test] + fn raw_literal() { + let s = maud::render(html! { $$"<pinkie>" }); + assert_eq!(&*s, "<pinkie>"); + } + + #[test] + fn block() { + let s = maud::render(html! { + ${ + let mut result = 1i32; + for i in range(2, 11) { + result *= i; + } + result + } + }); + assert_eq!(&*s, "3628800"); + } + + static BEST_PONY: &'static str = "Pinkie Pie"; + + #[test] + fn statics() { + let s = maud::render(html! { $BEST_PONY }); + assert_eq!(&*s, "Pinkie Pie"); + } + + // FIXME: See <https://github.com/rust-lang/rust/issues/15962> + // for why this is commented out + /* + #[test] + fn closure() { + let best_pony = "Pinkie Pie"; + let s = maud::render(html! { $best_pony }); + assert_eq!(&*s, "Pinkie Pie"); + } + */ +}