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

View file

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

View file

@ -5,56 +5,34 @@ use syntax::parse::parser::Parser as RustParser;
use syntax::parse::token;
use syntax::ptr::P;
#[derive(Show)]
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>),
}
use super::render::Renderer;
#[derive(Copy, PartialEq, Show)]
pub enum Escape {
NoEscape,
Escape,
None,
Attr,
Body,
}
macro_rules! some {
($e:expr) => (
match $e {
Some(x) => x,
None => return None,
}
)
macro_rules! guard {
($e:expr) => (if !$e { return false; })
}
macro_rules! any {
($self_:expr;) => (None);
($self_:expr; $e:expr) => (any!($self_; $e,));
macro_rules! branch {
($self_:expr;) => (return false);
($self_:expr; $e:expr) => (branch!($self_; $e,));
($self_:expr; $e:expr, $($es:expr),*) => ({
let start_ptr = $self_.input.as_ptr();
match $e {
Some(x) => Some(x),
None => {
if $e {
true
} else {
if $self_.input.as_ptr() == start_ptr {
// Parsing failed, but did not consume input.
// Keep going.
any!($self_; $($es),*)
branch!($self_; $($es),*)
} else {
return None;
return false;
}
},
}
})
}
@ -78,59 +56,72 @@ 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()
pub fn parse(cx: &mut ExtCtxt, input: &[TokenTree]) -> Option<P<Expr>> {
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> {
cx: &'cx mut ExtCtxt<'s>,
struct Parser<'cx: 'r, 's: 'cx, 'i, 'r, 'o: 'r> {
in_attr: bool,
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.
fn shift(&mut self, n: uint) {
fn shift(&mut self, n: usize) {
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.
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>> {
let mut result = vec![];
fn markups(&mut self) -> bool {
loop {
match self.input {
[] => return Some(result),
[] => return true,
[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;
},
if !self.markup() {
self.render.cx.span_err(tt.get_span(), "invalid syntax");
return false;
}
}
}
}
}
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;
fn markup(&mut self) -> bool {
branch!(self;
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 {
[minus!(), ref tt @ literal!(), ..] => {
self.shift(2);
@ -140,27 +131,30 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
self.shift(1);
(tt, false)
},
_ => return None,
_ => return false,
};
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,
})
match lit_to_string(self.render.cx, lit, minus) {
Some(s) => {
let escape = self.choose_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 {
[ref tt @ dollar!(), dollar!(), ..] => {
self.shift(2);
(Escape::NoEscape, tt.get_span())
(Escape::None, tt.get_span())
},
[ref tt @ dollar!(), ..] => {
self.shift(1);
(Escape::Escape, tt.get_span())
(self.choose_escape(), tt.get_span())
},
_ => return None,
_ => return false,
};
let tt = match self.input {
[ref tt, ..] => {
@ -168,48 +162,57 @@ impl<'cx, 's, 'i> Parser<'cx, 's, 'i> {
self.new_rust_parser(vec![tt.clone()]).parse_expr()
},
_ => {
self.cx.span_err(sp, "expected expression for this splice");
return None;
self.render.cx.span_err(sp, "expected expression for this splice");
return false;
},
};
Some(Value {
value: Value_::Splice(tt),
escape: escape,
})
self.render.splice(tt, escape);
true
}
fn element(&mut self) -> Option<Markup> {
fn element(&mut self) -> bool {
let name = match self.input {
[ident!(name), ..] => {
self.shift(1);
name.as_str().to_string()
},
_ => return None,
_ => return false,
};
let attrs = some!(self.attrs());
let body = any!(self; self.markup());
Some(Markup::Element(name, attrs, body.map(|body| box body)))
let name = name.as_slice();
self.render.element_open_start(name);
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)>> {
let mut attrs = vec![];
fn attrs(&mut self) -> bool {
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));
self.render.attribute_start(name.as_str());
{
let old_in_attr = self.in_attr;
self.in_attr = true;
guard!(self.markup());
self.in_attr = old_in_attr;
}
Some(attrs)
self.render.attribute_end();
}
true
}
fn block(&mut self) -> Option<Markup> {
fn block(&mut self) -> bool {
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)
Parser {
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;
},
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()),
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::ptr::P;
use super::parse::{Markup, Value};
use super::parse::Escape;
use maud::escape;
pub fn render(cx: &mut ExtCtxt, markups: &[Markup]) -> P<Expr> {
let w = Ident::new(token::intern("w"));
pub struct Renderer<'cx, 's: 'cx, 'o> {
pub cx: &'cx mut ExtCtxt<'s>,
stmts: &'o mut Vec<P<Stmt>>,
w: Ident,
}
impl<'cx, 's: 'cx, 'o> Renderer<'cx, 's, 'o> {
pub fn with<F>(cx: &'cx mut ExtCtxt<'s>, f: F) -> P<Expr> where
F: for<'o_> FnOnce(&mut Renderer<'cx, 's, 'o_>)
{
let mut stmts = vec![];
for markup in markups.iter() {
render_markup(cx, markup, w, &mut stmts);
}
quote_expr!(cx, |&: $w: &mut ::std::io::Writer| -> ::std::io::IoResult<()> {
let w = Ident::new(token::intern("w"));
let cx = {
let mut render = Renderer {
cx: cx,
stmts: &mut stmts,
w: w,
};
f(&mut render);
render.cx
};
quote_expr!(cx, |&: $w: &mut ::std::fmt::Writer| -> ::std::result::Result<(), ::std::fmt::Error> {
$stmts
Ok(())
})
}
fn render_markup(cx: &mut ExtCtxt, markup: &Markup, w: Ident, out: &mut Vec<P<Stmt>>) {
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));
},
}
}
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) => {
/// Append a literal pre-escaped string.
pub fn write(&mut self, s: &str) {
let w = self.w;
self.stmts.push(quote_stmt!(self.cx, try!($w.write_str($s))));
}
/// Append a literal string, with the specified escaping method.
pub fn string(&mut self, s: &str, escape: Escape) {
let s = match escape {
NoEscape => s[].into_cow(),
Escape => if is_attr {
escape::attribute(s[]).into_cow()
} else {
escape::non_attribute(s[]).into_cow()
},
Escape::None => s.into_cow(),
Escape::Attr => escape::attribute(s).into_cow(),
Escape::Body => escape::non_attribute(s).into_cow(),
};
let s = s[];
quote_expr!(cx, {
try!($w.write_str($s))
})
},
Splice(ref expr) => match escape {
NoEscape => quote_expr!(cx, {
try!(write!($w, "{}", $expr));
}),
Escape => quote_expr!(cx, {
let s = $expr.to_string();
for c in s.chars() {
try!(if $is_attr {
::maud::rt::escape_attribute(c, $w)
} else {
::maud::rt::escape_non_attribute(c, $w)
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)]
#![allow(unstable)]
extern crate maud;
#[plugin] #[no_link] extern crate maud_macros;