236 lines
6.4 KiB
Rust
236 lines
6.4 KiB
Rust
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)
|
|
}
|