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(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)] pub enum Escape { NoEscape, Escape, } 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); } /// 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!(), ..] => { self.shift(2); (tt, true) }, [ref tt @ literal!(), ..] => { 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, } } } /// 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(); 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) }