#![allow(unused_imports)]
use std::collections::{HashSet, HashMap};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use syntax::ast;
use syntax::codemap::{Span, Spanned, spanned};
use syntax::errors::DiagnosticBuilder;
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
use syntax::parse;
use syntax::parse::{token, parser, classify};
use syntax::parse::parser::{Parser, Restrictions};
use syntax::ptr::P;
use syntax::tokenstream::TokenTree;
use self::TagKind::{StartTag, EndTag};
use self::LHS::{Pat, Tags};
use self::RHS::{Else, Expr};
type Tokens = Vec<TokenTree>;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
enum TagKind {
StartTag,
EndTag,
}
impl TagKind {
fn lift(self, cx: &mut ExtCtxt) -> Tokens {
match self {
StartTag => quote_tokens!(&mut *cx, ::tokenizer::StartTag),
EndTag => quote_tokens!(&mut *cx, ::tokenizer::EndTag),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct Tag {
kind: TagKind,
name: Option<String>,
}
enum LHS {
Pat(P<ast::Pat>),
Tags(Vec<Spanned<Tag>>),
}
enum RHS {
Else,
Expr(P<ast::Expr>),
}
struct Arm {
binding: Option<ast::SpannedIdent>,
lhs: Spanned<LHS>,
rhs: Spanned<RHS>,
}
struct Match {
discriminant: P<ast::Expr>,
arms: Vec<Arm>,
}
fn push_all<T>(lhs: &mut Vec<T>, rhs: Vec<T>) {
lhs.extend(rhs.into_iter());
}
fn parse_spanned_ident<'a>(parser: &mut Parser<'a>) -> Result<ast::SpannedIdent, DiagnosticBuilder<'a>> {
let lo = parser.span.lo;
let ident = try!(parser.parse_ident());
let hi = parser.last_span.hi;
Ok(spanned(lo, hi, ident))
}
fn parse_tag<'a>(parser: &mut Parser<'a>) -> Result<Spanned<Tag>, DiagnosticBuilder<'a>> {
let lo = parser.span.lo;
try!(parser.expect(&token::Lt));
let kind = match parser.eat(&token::BinOp(token::Slash)) {
true => EndTag,
false => StartTag,
};
let name = match parser.eat(&token::Underscore) {
true => None,
false => Some((*try!(parser.parse_ident()).name.as_str()).to_owned()),
};
try!(parser.expect(&token::Gt));
Ok(spanned(lo, parser.last_span.hi, Tag {
kind: kind,
name: name,
}))
}
fn parse<'a>(cx: &'a mut ExtCtxt, toks: &[TokenTree]) -> Result<Match, DiagnosticBuilder<'a>> {
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), toks.to_vec());
let discriminant = try!(parser.parse_expr_res(Restrictions::RESTRICTION_NO_STRUCT_LITERAL, None));
try!(parser.expect(&token::OpenDelim(token::Brace)));
let mut arms: Vec<Arm> = Vec::new();
while parser.token != token::CloseDelim(token::Brace) {
let mut binding = None;
if parser.look_ahead(1, |t| *t == token::At) {
binding = Some(try!(parse_spanned_ident(&mut parser)));
parser.bump(); }
let lhs_lo = parser.span.lo;
let lhs = match parser.token {
token::Underscore | token::Ident(..) => Pat(try!(parser.parse_pat())),
token::Lt => {
let mut tags = Vec::new();
while parser.token != token::FatArrow {
tags.push(try!(parse_tag(&mut parser)));
}
Tags(tags)
}
_ => return Err(parser.fatal("unrecognized pattern")),
};
let lhs_hi = parser.last_span.hi;
try!(parser.expect(&token::FatArrow));
let rhs_lo = parser.span.lo;
let mut rhs_hi = parser.span.hi;
let rhs = if parser.eat_keyword(token::keywords::Else) {
try!(parser.expect(&token::Comma));
Else
} else {
let expr = try!(parser.parse_expr_res(Restrictions::RESTRICTION_STMT_EXPR, None));
rhs_hi = parser.last_span.hi;
let require_comma =
!classify::expr_is_simple_block(&*expr)
&& parser.token != token::CloseDelim(token::Brace);
if require_comma {
try!(parser.expect_one_of(
&[token::Comma], &[token::CloseDelim(token::Brace)]));
} else {
parser.eat(&token::Comma);
}
Expr(expr)
};
arms.push(Arm {
binding: binding,
lhs: spanned(lhs_lo, lhs_hi, lhs),
rhs: spanned(rhs_lo, rhs_hi, rhs),
});
}
parser.bump();
Ok(Match {
discriminant: discriminant,
arms: arms,
})
}
struct WildcardArm {
binding: Tokens,
kind: TagKind,
expr: P<ast::Expr>,
}
fn make_tag_pattern(cx: &mut ExtCtxt, binding: Tokens, tag: Tag) -> Tokens {
let kind = tag.kind.lift(cx);
let mut fields = quote_tokens!(&mut *cx, kind: $kind,);
match tag.name {
None => (),
Some(name) => push_all(&mut fields, quote_tokens!(&mut *cx, name: atom!($name),)),
}
quote_tokens!(&mut *cx,
::tree_builder::types::TagToken($binding ::tokenizer::Tag { $fields ..})
)
}
macro_rules! ext_err {
($span: expr, $message: expr) => { return Err(($span, $message)) }
}
macro_rules! ext_err_if {
($condition: expr, $span: expr, $message: expr) => {
if $condition { return Err(($span, $message)) }
}
}
pub fn expand_to_tokens(cx: &mut ExtCtxt, span: Span, toks: &[TokenTree])
-> Result<Vec<TokenTree>, (Span, &'static str)> {
let Match { discriminant, mut arms } = panictry!(parse(cx, toks));
let last_arm = match arms.pop() {
Some(x) => x,
None => ext_err!(span, "need at least one match arm"),
};
let mut arm_code: Tokens = vec!();
let mut seen_tags: HashSet<Tag> = HashSet::new();
let mut wildcards: Vec<WildcardArm> = vec!();
let mut wild_excluded: HashMap<TagKind, Vec<Tag>> = HashMap::new();
for Arm { binding, lhs, rhs } in arms.into_iter() {
let binding = match binding {
Some(i) => quote_tokens!(&mut *cx, $i @),
None => vec!(),
};
match (lhs.node, rhs.node) {
(Pat(_), Else)
=> ext_err!(rhs.span, "'else' may not appear with an ordinary pattern"),
(Pat(pat), Expr(expr)) => {
ext_err_if!(!wildcards.is_empty(), lhs.span,
"ordinary patterns may not appear after wildcard tags");
push_all(&mut arm_code, quote_tokens!(&mut *cx, $binding $pat => $expr,));
}
(Tags(tags), Else) => {
for Spanned { span, node: tag } in tags.into_iter() {
ext_err_if!(!seen_tags.insert(tag.clone()), span, "duplicate tag");
ext_err_if!(tag.name.is_none(), rhs.span,
"'else' may not appear with a wildcard tag");
match wild_excluded.entry(tag.kind) {
Occupied(e) => { e.into_mut().push(tag.clone()); }
Vacant(e) => { e.insert(vec![tag.clone()]); }
}
}
}
(Tags(tags), Expr(expr)) => {
let mut wildcard = None;
for Spanned { span, node: tag } in tags.into_iter() {
ext_err_if!(!seen_tags.insert(tag.clone()), span, "duplicate tag");
match tag.name {
Some(_) => {
ext_err_if!(!wildcards.is_empty(), lhs.span,
"specific tags may not appear after wildcard tags");
ext_err_if!(wildcard == Some(true), span,
"wildcard tags must appear alone");
if wildcard.is_some() {
push_all(&mut arm_code, quote_tokens!(&mut *cx, |));
}
push_all(&mut arm_code, make_tag_pattern(cx, binding.clone(), tag));
wildcard = Some(false);
}
None => {
ext_err_if!(wildcard.is_some(), span,
"wildcard tags must appear alone");
wildcard = Some(true);
wildcards.push(WildcardArm {
binding: binding.clone(),
kind: tag.kind,
expr: expr.clone(),
});
}
}
}
match wildcard {
None => ext_err!(lhs.span, "[internal macro error] tag arm with no tags"),
Some(false) => {
push_all(&mut arm_code, quote_tokens!(&mut *cx, => $expr,));
}
Some(true) => () }
}
}
}
let Arm { binding, lhs, rhs } = last_arm;
let last_arm_token = token::gensym_ident("last_arm_token");
let enable_wildcards = token::gensym_ident("enable_wildcards");
let (last_pat, last_expr) = match (binding, lhs.node, rhs.node) {
(Some(id), _, _) => ext_err!(id.span, "the last arm cannot have an @-binding"),
(None, Tags(_), _) => ext_err!(lhs.span, "the last arm cannot have tag patterns"),
(None, _, Else) => ext_err!(rhs.span, "the last arm cannot use 'else'"),
(None, Pat(p), Expr(e)) => match p.node {
ast::PatKind::Wild | ast::PatKind::Ident(..) => (p, e),
_ => ext_err!(lhs.span, "the last arm must have a wildcard or ident pattern"),
},
};
let mut enable_wildcards_code = vec!();
for (_, tags) in wild_excluded.into_iter() {
for tag in tags.into_iter() {
push_all(&mut enable_wildcards_code, make_tag_pattern(cx, vec!(), tag));
push_all(&mut enable_wildcards_code, quote_tokens!(&mut *cx, => false,));
}
}
let mut wildcard_code = vec!();
for WildcardArm { binding, kind, expr } in wildcards.into_iter() {
let pat = make_tag_pattern(cx, binding, Tag { kind: kind, name: None });
push_all(&mut wildcard_code, quote_tokens!(&mut *cx,
(true, $pat) => $expr,
));
}
Ok(quote_tokens!(&mut *cx,
match $discriminant {
$arm_code
$last_arm_token => {
let $enable_wildcards = match $last_arm_token {
$enable_wildcards_code
_ => true,
};
match ($enable_wildcards, $last_arm_token) {
$wildcard_code
(_, $last_pat) => $last_expr,
}
},
}
))
}