Scaffold syntax extension thing

This commit is contained in:
Chris Wong 2014-12-18 18:57:55 +13:00
parent 34031b2f95
commit 6a4709315b
4 changed files with 153 additions and 16 deletions
htmlthing_macros

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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!(),
}
}

View file

@ -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");
}