Rewrite parser again

Now it's some one-pass monstrosity oh noes
This commit is contained in:
Chris Wong 2015-01-10 21:29:58 +13:00
parent 4a03e09cbb
commit 905edd0ae0
5 changed files with 245 additions and 197 deletions
maud/src
maud_macros

View file

@ -1,6 +1,9 @@
//! Super fast HTML template engine. //! Super fast HTML template engine.
use std::io::IoResult; use std::fmt;
use std::fmt::Writer as FmtWriter;
pub type FmtResult<T> = Result<T, fmt::Error>;
/// Utilities for escaping HTML5 markup. /// Utilities for escaping HTML5 markup.
/// ///
@ -9,27 +12,19 @@ use std::io::IoResult;
/// ///
/// [1]: http://www.w3.org/TR/html51/syntax.html#escapingString /// [1]: http://www.w3.org/TR/html51/syntax.html#escapingString
pub mod escape { pub mod escape {
use std::fmt::Writer as FmtWriter;
use super::render; use super::render;
use super::rt; use super::rt;
/// Escape a double-quoted attribute value, as per HTML5 rules. /// Escape a double-quoted attribute value, as per HTML5 rules.
pub fn attribute(s: &str) -> String { pub fn attribute(s: &str) -> String {
render(|w| { render(|w| rt::escape_attribute(w, |w| w.write_str(s)))
for c in s.chars() {
try!(rt::escape_attribute(c, w));
}
Ok(())
})
} }
/// Escape non-attribute text content, as per HTML5 rules. /// Escape non-attribute text content, as per HTML5 rules.
pub fn non_attribute(s: &str) -> String { pub fn non_attribute(s: &str) -> String {
render(|w| { render(|w| rt::escape_non_attribute(w, |w| w.write_str(s)))
for c in s.chars() {
try!(rt::escape_non_attribute(c, w));
}
Ok(())
})
} }
} }
@ -38,33 +33,66 @@ pub mod escape {
#[experimental = "These functions should not be called directly. #[experimental = "These functions should not be called directly.
Use the macros in `maud_macros` instead."] Use the macros in `maud_macros` instead."]
pub mod rt { pub mod rt {
use std::io::IoResult; use std::fmt::Writer as FmtWriter;
use super::FmtResult;
#[inline] struct AttrEscaper<'a, 'b: 'a> {
pub fn escape_attribute(c: char, w: &mut Writer) -> IoResult<()> { inner: &'a mut (FmtWriter + 'b),
match c { }
'&' => w.write_str("&amp;"),
'\u{A0}' => w.write_str("&nbsp;"), impl<'a, 'b> FmtWriter for AttrEscaper<'a, 'b> {
'"' => w.write_str("&quot;"), fn write_str(&mut self, s: &str) -> FmtResult<()> {
_ => w.write_char(c), for c in s.chars() {
try!(match c {
'&' => self.inner.write_str("&amp;"),
'\u{A0}' => self.inner.write_str("&nbsp;"),
'"' => self.inner.write_str("&quot;"),
_ => write!(self.inner, "{}", c),
});
}
Ok(())
}
}
struct NonAttrEscaper<'a, 'b: 'a> {
inner: &'a mut (FmtWriter + 'b),
}
impl<'a, 'b> FmtWriter for NonAttrEscaper<'a, 'b> {
fn write_str(&mut self, s: &str) -> FmtResult<()> {
for c in s.chars() {
try!(match c {
'&' => self.inner.write_str("&amp;"),
'\u{A0}' => self.inner.write_str("&nbsp;"),
'<' => self.inner.write_str("&lt;"),
'>' => self.inner.write_str("&gt;"),
_ => write!(self.inner, "{}", c),
});
}
Ok(())
} }
} }
#[inline] #[inline]
pub fn escape_non_attribute(c: char, w: &mut Writer) -> IoResult<()> { pub fn escape_attribute<F>(w: &mut FmtWriter, f: F) -> FmtResult<()> where
match c { F: FnOnce(&mut FmtWriter) -> FmtResult<()>
'&' => w.write_str("&amp;"), {
'\u{A0}' => w.write_str("&nbsp;"), f(&mut AttrEscaper { inner: w })
'<' => w.write_str("&lt;"), }
'>' => w.write_str("&gt;"),
_ => w.write_char(c), #[inline]
} pub fn escape_non_attribute<F>(w: &mut FmtWriter, f: F) -> FmtResult<()> where
F: FnOnce(&mut FmtWriter) -> FmtResult<()>
{
f(&mut NonAttrEscaper { inner: w })
} }
} }
/// Render a template into a `String`. /// Render a template into a `String`.
pub fn render<F: FnOnce(&mut Writer) -> IoResult<()>>(template: F) -> String { pub fn render<F>(template: F) -> String where
let mut buf = vec![]; F: FnOnce(&mut FmtWriter) -> FmtResult<()>
{
let mut buf = String::new();
template(&mut buf).unwrap(); template(&mut buf).unwrap();
String::from_utf8(buf).unwrap() buf
} }

View file

@ -1,5 +1,6 @@
#![crate_type = "dylib"] #![crate_type = "dylib"]
#![feature(plugin_registrar, quote, slicing_syntax)] #![feature(plugin_registrar, quote)]
#![allow(unstable)]
extern crate syntax; extern crate syntax;
extern crate rustc; extern crate rustc;
@ -15,10 +16,7 @@ mod render;
fn expand_html<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'cx> { 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) => { Some(expr) => MacExpr::new(expr),
let expr = render::render(cx, markups[]);
MacExpr::new(expr)
},
None => DummyResult::any(sp), None => DummyResult::any(sp),
} }
} }

View file

@ -5,56 +5,34 @@ use syntax::parse::parser::Parser as RustParser;
use syntax::parse::token; use syntax::parse::token;
use syntax::ptr::P; use syntax::ptr::P;
#[derive(Show)] use super::render::Renderer;
pub enum Markup {
Element(String, Vec<(String, Value)>, Option<Box<Markup>>),
Block(Vec<Markup>),
Value(Value),
}
#[derive(Show)]
pub struct Value {
pub value: Value_,
pub escape: Escape,
}
#[derive(Show)]
pub enum Value_ {
Literal(String),
Splice(P<Expr>),
}
#[derive(Copy, PartialEq, Show)] #[derive(Copy, PartialEq, Show)]
pub enum Escape { pub enum Escape {
NoEscape, None,
Escape, Attr,
Body,
} }
macro_rules! some { macro_rules! guard {
($e:expr) => ( ($e:expr) => (if !$e { return false; })
match $e {
Some(x) => x,
None => return None,
}
)
} }
macro_rules! any { macro_rules! branch {
($self_:expr;) => (None); ($self_:expr;) => (return false);
($self_:expr; $e:expr) => (any!($self_; $e,)); ($self_:expr; $e:expr) => (branch!($self_; $e,));
($self_:expr; $e:expr, $($es:expr),*) => ({ ($self_:expr; $e:expr, $($es:expr),*) => ({
let start_ptr = $self_.input.as_ptr(); let start_ptr = $self_.input.as_ptr();
match $e { if $e {
Some(x) => Some(x), true
None => { } else {
if $self_.input.as_ptr() == start_ptr { if $self_.input.as_ptr() == start_ptr {
// Parsing failed, but did not consume input. // Parsing failed, but did not consume input.
// Keep going. // Keep going.
any!($self_; $($es),*) branch!($self_; $($es),*)
} else { } else {
return None; return false;
} }
},
} }
}) })
} }
@ -78,59 +56,72 @@ macro_rules! ident {
($x:pat) => (TtToken(_, token::Ident($x, token::IdentStyle::Plain))) ($x:pat) => (TtToken(_, token::Ident($x, token::IdentStyle::Plain)))
} }
pub fn parse(cx: &mut ExtCtxt, input: &[TokenTree]) -> Option<Vec<Markup>> { pub fn parse(cx: &mut ExtCtxt, input: &[TokenTree]) -> Option<P<Expr>> {
Parser { cx: cx, input: input }.markups() let mut success = true;
let expr = Renderer::with(cx, |render| {
let mut parser = Parser {
in_attr: false,
input: input,
render: render,
};
success = parser.markups();
});
if success {
Some(expr)
} else {
None
}
} }
struct Parser<'cx, 's: 'cx, 'i> { struct Parser<'cx: 'r, 's: 'cx, 'i, 'r, 'o: 'r> {
cx: &'cx mut ExtCtxt<'s>, in_attr: bool,
input: &'i [TokenTree], input: &'i [TokenTree],
render: &'r mut Renderer<'cx, 's, 'o>,
} }
impl<'cx, 's, 'i> Parser<'cx, 's, 'i> { impl<'cx: 'r, 's: 'cx, 'i, 'r, 'o: 'r> Parser<'cx, 's, 'i, 'r, 'o> {
/// Consume `n` items from the input. /// Consume `n` items from the input.
fn shift(&mut self, n: uint) { fn shift(&mut self, n: usize) {
self.input = self.input.slice_from(n); self.input = self.input.slice_from(n);
} }
fn choose_escape(&self) -> Escape {
if self.in_attr {
Escape::Attr
} else {
Escape::Body
}
}
/// Construct a Rust AST parser from the given token tree. /// Construct a Rust AST parser from the given token tree.
fn new_rust_parser(&self, tts: Vec<TokenTree>) -> RustParser<'s> { fn new_rust_parser(&self, tts: Vec<TokenTree>) -> RustParser<'s> {
parse::tts_to_parser(self.cx.parse_sess, tts, self.cx.cfg.clone()) parse::tts_to_parser(self.render.cx.parse_sess, tts, self.render.cx.cfg.clone())
} }
fn markups(&mut self) -> Option<Vec<Markup>> { fn markups(&mut self) -> bool {
let mut result = vec![];
loop { loop {
match self.input { match self.input {
[] => return Some(result), [] => return true,
[semi!(), ..] => self.shift(1), [semi!(), ..] => self.shift(1),
[ref tt, ..] => { [ref tt, ..] => {
match self.markup() { if !self.markup() {
Some(markup) => result.push(markup), self.render.cx.span_err(tt.get_span(), "invalid syntax");
None => { return false;
self.cx.span_err(tt.get_span(), "invalid syntax");
return None;
},
} }
} }
} }
} }
} }
fn markup(&mut self) -> Option<Markup> { fn markup(&mut self) -> bool {
any!(self; branch!(self;
self.value().map(Markup::Value),
self.block(),
self.element())
}
fn value(&mut self) -> Option<Value> {
any!(self;
self.literal(), self.literal(),
self.splice()) self.splice(),
self.block(),
!self.in_attr && self.element())
} }
fn literal(&mut self) -> Option<Value> { fn literal(&mut self) -> bool {
let (tt, minus) = match self.input { let (tt, minus) = match self.input {
[minus!(), ref tt @ literal!(), ..] => { [minus!(), ref tt @ literal!(), ..] => {
self.shift(2); self.shift(2);
@ -140,27 +131,30 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
self.shift(1); self.shift(1);
(tt, false) (tt, false)
}, },
_ => return None, _ => return false,
}; };
let lit = self.new_rust_parser(vec![tt.clone()]).parse_lit(); let lit = self.new_rust_parser(vec![tt.clone()]).parse_lit();
lit_to_string(self.cx, lit, minus) match lit_to_string(self.render.cx, lit, minus) {
.map(|s| Value { Some(s) => {
value: Value_::Literal(s), let escape = self.choose_escape();
escape: Escape::Escape, self.render.string(s.as_slice(), escape);
}) },
None => return false,
}
true
} }
fn splice(&mut self) -> Option<Value> { fn splice(&mut self) -> bool {
let (escape, sp) = match self.input { let (escape, sp) = match self.input {
[ref tt @ dollar!(), dollar!(), ..] => { [ref tt @ dollar!(), dollar!(), ..] => {
self.shift(2); self.shift(2);
(Escape::NoEscape, tt.get_span()) (Escape::None, tt.get_span())
}, },
[ref tt @ dollar!(), ..] => { [ref tt @ dollar!(), ..] => {
self.shift(1); self.shift(1);
(Escape::Escape, tt.get_span()) (self.choose_escape(), tt.get_span())
}, },
_ => return None, _ => return false,
}; };
let tt = match self.input { let tt = match self.input {
[ref tt, ..] => { [ref tt, ..] => {
@ -168,48 +162,57 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
self.new_rust_parser(vec![tt.clone()]).parse_expr() self.new_rust_parser(vec![tt.clone()]).parse_expr()
}, },
_ => { _ => {
self.cx.span_err(sp, "expected expression for this splice"); self.render.cx.span_err(sp, "expected expression for this splice");
return None; return false;
}, },
}; };
Some(Value { self.render.splice(tt, escape);
value: Value_::Splice(tt), true
escape: escape,
})
} }
fn element(&mut self) -> Option<Markup> { fn element(&mut self) -> bool {
let name = match self.input { let name = match self.input {
[ident!(name), ..] => { [ident!(name), ..] => {
self.shift(1); self.shift(1);
name.as_str().to_string() name.as_str().to_string()
}, },
_ => return None, _ => return false,
}; };
let attrs = some!(self.attrs()); let name = name.as_slice();
let body = any!(self; self.markup()); self.render.element_open_start(name);
Some(Markup::Element(name, attrs, body.map(|body| box body))) guard!(self.attrs());
self.render.element_open_end();
guard!(self.markup());
self.render.element_close(name);
true
} }
fn attrs(&mut self) -> Option<Vec<(String, Value)>> { fn attrs(&mut self) -> bool {
let mut attrs = vec![];
while let [ident!(name), eq!(), ..] = self.input { while let [ident!(name), eq!(), ..] = self.input {
self.shift(2); self.shift(2);
let name = name.as_str().to_string(); self.render.attribute_start(name.as_str());
let value = some!(self.value()); {
attrs.push((name, value)); let old_in_attr = self.in_attr;
self.in_attr = true;
guard!(self.markup());
self.in_attr = old_in_attr;
}
self.render.attribute_end();
} }
Some(attrs) true
} }
fn block(&mut self) -> Option<Markup> { fn block(&mut self) -> bool {
match self.input { match self.input {
[TtDelimited(_, ref d), ..] if d.delim == token::DelimToken::Brace => { [TtDelimited(_, ref d), ..] if d.delim == token::DelimToken::Brace => {
self.shift(1); self.shift(1);
Parser { cx: self.cx, input: d.tts[] }.markups() Parser {
.map(Markup::Block) in_attr: self.in_attr,
input: d.tts.as_slice(),
render: self.render,
}.markups()
}, },
_ => None, _ => false,
} }
} }
} }
@ -228,7 +231,7 @@ fn lit_to_string(cx: &mut ExtCtxt, lit: Lit, minus: bool) -> Option<String> {
return None; return None;
}, },
LitChar(c) => result.push(c), LitChar(c) => result.push(c),
LitInt(x, _) => result.push_str(x.to_string()[]), LitInt(x, _) => result.push_str(x.to_string().as_slice()),
LitFloat(s, _) | LitFloatUnsuffixed(s) => result.push_str(s.get()), LitFloat(s, _) | LitFloatUnsuffixed(s) => result.push_str(s.get()),
LitBool(b) => result.push_str(if b { "true" } else { "false" }), LitBool(b) => result.push_str(if b { "true" } else { "false" }),
}; };

View file

@ -4,70 +4,88 @@ use syntax::ext::base::ExtCtxt;
use syntax::parse::token; use syntax::parse::token;
use syntax::ptr::P; use syntax::ptr::P;
use super::parse::{Markup, Value}; use super::parse::Escape;
use maud::escape; use maud::escape;
pub fn render(cx: &mut ExtCtxt, markups: &[Markup]) -> P<Expr> { pub struct Renderer<'cx, 's: 'cx, 'o> {
let w = Ident::new(token::intern("w")); pub cx: &'cx mut ExtCtxt<'s>,
let mut stmts = vec![]; stmts: &'o mut Vec<P<Stmt>>,
for markup in markups.iter() { w: Ident,
render_markup(cx, markup, w, &mut stmts);
}
quote_expr!(cx, |&: $w: &mut ::std::io::Writer| -> ::std::io::IoResult<()> {
$stmts
Ok(())
})
} }
fn render_markup(cx: &mut ExtCtxt, markup: &Markup, w: Ident, out: &mut Vec<P<Stmt>>) { impl<'cx, 's: 'cx, 'o> Renderer<'cx, 's, 'o> {
use super::parse::Markup::*; pub fn with<F>(cx: &'cx mut ExtCtxt<'s>, f: F) -> P<Expr> where
match *markup { F: for<'o_> FnOnce(&mut Renderer<'cx, 's, 'o_>)
Element(..) => unimplemented!(), {
Block(ref markups) => { let mut stmts = vec![];
for markup in markups.iter() { let w = Ident::new(token::intern("w"));
render_markup(cx, markup, w, out); let cx = {
} let mut render = Renderer {
}, cx: cx,
Value(ref value) => { stmts: &mut stmts,
let expr = render_value(cx, value, w, false); w: w,
out.push(quote_stmt!(cx, $expr));
},
}
}
fn render_value(cx: &mut ExtCtxt, value: &Value, w: Ident, is_attr: bool) -> P<Expr> {
use super::parse::Escape::*;
use super::parse::Value_::*;
let &Value { ref value, escape } = value;
match *value {
Literal(ref s) => {
let s = match escape {
NoEscape => s[].into_cow(),
Escape => if is_attr {
escape::attribute(s[]).into_cow()
} else {
escape::non_attribute(s[]).into_cow()
},
}; };
let s = s[]; f(&mut render);
quote_expr!(cx, { render.cx
try!($w.write_str($s)) };
}) quote_expr!(cx, |&: $w: &mut ::std::fmt::Writer| -> ::std::result::Result<(), ::std::fmt::Error> {
}, $stmts
Splice(ref expr) => match escape { Ok(())
NoEscape => quote_expr!(cx, { })
try!(write!($w, "{}", $expr)); }
}),
Escape => quote_expr!(cx, { /// Append a literal pre-escaped string.
let s = $expr.to_string(); pub fn write(&mut self, s: &str) {
for c in s.chars() { let w = self.w;
try!(if $is_attr { self.stmts.push(quote_stmt!(self.cx, try!($w.write_str($s))));
::maud::rt::escape_attribute(c, $w) }
} else {
::maud::rt::escape_non_attribute(c, $w) /// Append a literal string, with the specified escaping method.
}); pub fn string(&mut self, s: &str, escape: Escape) {
} let s = match escape {
}), Escape::None => s.into_cow(),
}, Escape::Attr => escape::attribute(s).into_cow(),
Escape::Body => escape::non_attribute(s).into_cow(),
};
self.write(s.as_slice());
}
/// Append the result of an expression, with the specified escaping method.
pub fn splice(&mut self, expr: P<Expr>, escape: Escape) {
let w = self.w;
self.stmts.push(match escape {
Escape::None => quote_stmt!(self.cx, try!(write!($w, "{}", $expr))),
Escape::Attr =>
quote_stmt!(self.cx,
try!(::maud::rt::escape_attribute($w, |w| write!(w, "{}", $expr)))),
Escape::Body =>
quote_stmt!(self.cx,
try!(::maud::rt::escape_non_attribute($w, |w| write!(w, "{}", $expr)))),
});
}
pub fn element_open_start(&mut self, name: &str) {
self.write("<");
self.write(name);
}
pub fn attribute_start(&mut self, name: &str) {
self.write(" ");
self.write(name);
self.write("=\"");
}
pub fn attribute_end(&mut self) {
self.write("\"");
}
pub fn element_open_end(&mut self) {
self.write(">");
}
pub fn element_close(&mut self, name: &str) {
self.write("</");
self.write(name);
self.write(">");
} }
} }

View file

@ -1,4 +1,5 @@
#![feature(plugin)] #![feature(plugin)]
#![allow(unstable)]
extern crate maud; extern crate maud;
#[plugin] #[no_link] extern crate maud_macros; #[plugin] #[no_link] extern crate maud_macros;