diff --git a/htmlthing_macros/src/lib.rs b/htmlthing_macros/src/lib.rs index 8aef544..5a570f4 100644 --- a/htmlthing_macros/src/lib.rs +++ b/htmlthing_macros/src/lib.rs @@ -1,29 +1,29 @@ #![crate_type = "dylib"] -#![feature(plugin_registrar)] +#![feature(globs, plugin_registrar, quote, macro_rules)] extern crate syntax; extern crate rustc; +use syntax::ast::{Ident, TokenTree}; use syntax::codemap::Span; +use syntax::ext::base::{DummyResult, ExtCtxt, IdentTT, MacItems, MacResult}; use syntax::parse::token; -use syntax::ast::{TokenTree, TtToken}; -use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacExpr}; -use syntax::ext::build::AstBuilder; use rustc::plugin::Registry; -fn expand_html(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'static> { - let s = match args { - [TtToken(_, token::Ident(s, _))] => token::get_ident(s), - _ => { - cx.span_err(sp, "argument should be a single identifier"); - return DummyResult::any(sp); - }, - }; +mod parse; +mod render; - MacExpr::new(cx.expr_str(sp, s)) +fn expand_html<'cx>(cx: &'cx mut ExtCtxt, sp: Span, ident: Ident, args: Vec<TokenTree>) -> Box<MacResult + 'cx> { + match parse::parse(cx, &*args) { + Some(markups) => { + let item = render::render(cx, ident, &*markups); + MacItems::new(item.into_iter()) + }, + None => DummyResult::any(sp), + } } #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { - reg.register_macro("html", expand_html); + reg.register_syntax_extension(token::intern("html"), IdentTT(box expand_html, None)); } diff --git a/htmlthing_macros/src/parse.rs b/htmlthing_macros/src/parse.rs new file mode 100644 index 0000000..2d06acb --- /dev/null +++ b/htmlthing_macros/src/parse.rs @@ -0,0 +1,101 @@ +use syntax::ast::{Expr, Lit, TokenTree, TtToken}; +use syntax::ext::base::ExtCtxt; +use syntax::parse; +use syntax::parse::token; +use syntax::ptr::P; + +#[deriving(Show)] +pub enum Markup { + Empty, + Element(Vec<(String, Value)>, Vec<Markup>), + Value(Value), +} + +#[deriving(Show)] +pub enum Value { + Literal(String), + Splice(P<Expr>), +} + +pub fn parse(cx: &mut ExtCtxt, mut args: &[TokenTree]) -> Option<Vec<Markup>> { + use self::Markup::*; + let mut result = vec![]; + loop { + match parse_markup(cx, &mut args) { + Empty => break, + markup => result.push(markup), + } + } + // If not all tokens were consumed, then there must have been an + // error somewhere + match args { + [] => Some(result), + _ => None, + } +} + +fn parse_markup(cx: &mut ExtCtxt, args: &mut &[TokenTree]) -> Markup { + use self::Markup::*; + use self::Value::*; + if let Some(s) = parse_literal(cx, args) { + Value(Literal(s)) + } else { + match *args { + [] => Empty, + [ref tt, ..] => { + cx.span_err(tt.get_span(), "invalid syntax"); + Empty + }, + } + } +} + +fn parse_literal(cx: &mut ExtCtxt, args: &mut &[TokenTree]) -> Option<String> { + let minus = match *args { + [TtToken(_, token::BinOp(token::Minus)), ..] => { + args.shift(1); + true + }, + _ => false, + }; + + match *args { + [ref tt @ TtToken(_, token::Literal(..)), ..] => { + args.shift(1); + 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) + }, + _ => None, + } +} + +fn lit_to_string(cx: &mut ExtCtxt, lit: Lit, minus: bool) -> Option<String> { + use syntax::ast::Lit_::*; + let mut result = String::new(); + if minus { + result.push('-'); + } + match lit.node { + LitStr(s, _) => result.push_str(s.get()), + LitBinary(..) | LitByte(..) => { + cx.span_err(lit.span, "cannot splice binary data"); + return None; + }, + LitChar(c) => result.push(c), + LitInt(x, _) => result.push_str(&*x.to_string()), + LitFloat(s, _) | LitFloatUnsuffixed(s) => result.push_str(s.get()), + LitBool(b) => result.push_str(if b { "true" } else { "false" }), + }; + 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/htmlthing_macros/src/render.rs b/htmlthing_macros/src/render.rs new file mode 100644 index 0000000..fb330e0 --- /dev/null +++ b/htmlthing_macros/src/render.rs @@ -0,0 +1,33 @@ +use syntax::ast::{Ident, Item, Stmt}; +use syntax::ext::base::ExtCtxt; +use syntax::parse::token; +use syntax::ptr::P; + +use super::parse::Markup; + +pub fn render(cx: &mut ExtCtxt, ident: Ident, markups: &[Markup]) -> Option<P<Item>> { + let w = Ident::new(token::intern("w")); + let mut stmts = vec![]; + for markup in markups.iter() { + render_markup(cx, markup, w, &mut stmts); + } + quote_item!(cx, + fn $ident<W: ::std::io::Writer>($w: &mut W) -> ::std::io::IoResult<()> { + $stmts; + Ok(()) + } + ) +} + +fn render_markup(cx: &mut ExtCtxt, markup: &Markup, w: Ident, out: &mut Vec<P<Stmt>>) { + use super::parse::Markup::*; + use super::parse::Value::*; + match *markup { + Empty => {}, + Element(..) => unimplemented!(), + Value(Literal(ref s)) => { + out.push(quote_stmt!(cx, try!($w.write_str($s)))); + }, + Value(Splice(_)) => unimplemented!(), + } +} diff --git a/htmlthing_macros/tests/tests.rs b/htmlthing_macros/tests/tests.rs index 14c6071..5c10bac 100644 --- a/htmlthing_macros/tests/tests.rs +++ b/htmlthing_macros/tests/tests.rs @@ -5,6 +5,9 @@ extern crate htmlthing; #[test] fn it_works() { - let s = html!(ducks); - assert_eq!(s, "ducks"); + let mut buf = vec![]; + html! test_template("du\tcks" -23 3.14 '\n' "geese"); + test_template(&mut buf).unwrap(); + let s = String::from_utf8(buf).unwrap(); + assert_eq!(&*s, "du\tcks-233.14\ngeese"); }