Rewrite the parser yay
This commit is contained in:
parent
8a8ae3a5a3
commit
05c68067dd
4 changed files with 246 additions and 66 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue