use std::collections::{HashMap};
use std::rc::{Rc};
use syntax::ast::*;
use syntax::codemap::{self, DUMMY_SP, CodeMap, Span, Spanned};
use syntax::errors::{Handler};
use syntax::parse::{ParseSess};
use syntax::parse::common::{SeqSep};
use syntax::parse::parser::{Parser};
use syntax::parse::token::{BinOpToken, Token};
use syntax::ptr::{P};
use super::{Amount, PluginResult, Specifier};
use super::utility::{SaveEmitter, ToError, TransactionParser};
macro_rules! from {
($variant:ident, $ty:ty) => {
impl<'a> From<&'a Match> for $ty {
fn from(match_: &'a Match) -> $ty {
match *match_ {
Match::$variant(ref value) => value.clone(),
_ => panic!("expected `Match::{}`", stringify!($variant)),
}
}
}
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Match {
Attr(Attribute),
BinOp(Spanned<BinOpToken>),
Block(P<Block>),
Delim(Spanned<Rc<Delimited>>),
Expr(P<Expr>),
Ident(Spanned<Ident>),
Item(P<Item>),
Lftm(Spanned<Name>),
Lit(Lit),
Meta(P<MetaItem>),
Pat(P<Pat>),
Path(Path),
Stmt(Stmt),
Ty(P<Ty>),
Tok(Spanned<Token>),
Tt(TokenTree),
Sequence(Vec<Match>),
NamedSequence(Spanned<usize>),
}
impl Match {
#[cfg_attr(feature="clippy", allow(needless_lifetimes))]
pub fn to<'a, T: From<&'a Match>>(&'a self) -> T {
self.into()
}
}
from!(Attr, Attribute);
from!(BinOp, Spanned<BinOpToken>);
from!(Block, P<Block>);
from!(Delim, Spanned<Rc<Delimited>>);
from!(Expr, P<Expr>);
from!(Ident, Spanned<Ident>);
from!(Item, P<Item>);
from!(Lftm, Spanned<Name>);
from!(Lit, Lit);
from!(Meta, P<MetaItem>);
from!(Pat, P<Pat>);
from!(Path, Path);
from!(Stmt, Stmt);
from!(Ty, P<Ty>);
from!(Tok, Spanned<Token>);
from!(Tt, TokenTree);
from!(Sequence, Vec<Match>);
impl<'a> From<&'a Match> for Spanned<usize> {
fn from(match_: &'a Match) -> Spanned<usize> {
match *match_ {
Match::NamedSequence(count) => count,
_ => panic!("expected `Match::NamedSequence`"),
}
}
}
impl<'a> From<&'a Match> for Spanned<bool> {
fn from(match_: &'a Match) -> Spanned<bool> {
match *match_ {
Match::NamedSequence(count) => codemap::respan(count.span, count.node != 0),
_ => panic!("expected `Match::NamedSequence`"),
}
}
}
struct ArgumentParser<'s> {
parser: TransactionParser<'s>,
span: Span,
}
impl<'s> ArgumentParser<'s> {
fn new(session: &'s ParseSess, tts: &'s [TokenTree], span: Span) -> ArgumentParser<'s> {
ArgumentParser { parser: TransactionParser::new(session, tts.into()), span: span }
}
fn expect_token(&mut self) -> PluginResult<Token> {
match self.parser.bump_and_get() {
Token::Eof => self.span.to_error("unexpected end of arguments"),
token => Ok(token),
}
}
fn expect_specific_token(&mut self, token: &Token) -> PluginResult<()> {
let found = try!(self.expect_token());
if found.mtwt_eq(token) {
Ok(())
} else {
let message = format!("expected `{}`", Parser::token_to_string(token));
self.parser.get_last_span().to_error(message)
}
}
fn parse_sequence(
&mut self,
amount: Amount,
separator: Option<&Token>,
specification: &[Specifier],
matches: &mut HashMap<String, Match>,
) -> PluginResult<usize> {
for specifier in specification {
if let Some(name) = specifier.get_name() {
matches.insert(name.clone(), Match::Sequence(vec![]));
}
}
let required = amount == Amount::OneOrMore;
let mut count = 0;
loop {
self.parser.save();
if let Some(ref separator) = separator {
if count != 0 && !self.parser.eat(separator) {
return Ok(count);
}
}
let mut submatches = HashMap::new();
match self.parse_args(specification, &mut submatches) {
Ok(()) => count += 1,
Err(error) => if count == 0 && required {
return Err(error);
} else {
self.parser.rollback();
return Ok(count);
},
}
for (k, v) in submatches {
match *matches.entry(k).or_insert_with(|| Match::Sequence(vec![])) {
Match::Sequence(ref mut sequence) => sequence.push(v),
_ => unreachable!(),
}
}
if amount == Amount::ZeroOrOne {
return Ok(count);
}
}
}
fn parse_delim(&mut self) -> PluginResult<Rc<Delimited>> {
let (delimiter, open) = match try!(self.expect_token()) {
Token::OpenDelim(delimiter) => (delimiter, self.parser.get_last_span()),
invalid => {
let string = Parser::token_to_string(&invalid);
let error = format!("expected opening delimiter, found `{}`", string);
return self.parser.get_last_span().to_error(error);
},
};
let tts = self.parser.parse(|p| {
let sep = SeqSep { sep: None, trailing_sep_allowed: false };
p.parse_seq_to_end(&Token::CloseDelim(delimiter), sep, |p| p.parse_token_tree())
});
let delimited = Delimited {
delim: delimiter,
open_span: open,
tts: try!(tts),
close_span: self.parser.get_last_span(),
};
Ok(Rc::new(delimited))
}
#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))]
fn parse_args(
&mut self, specification: &[Specifier], matches: &mut HashMap<String, Match>
) -> PluginResult<()> {
macro_rules! insert {
($variant:ident, $parse:ident$(.$field:ident)*, $name:expr) => ({
let match_ = try!(self.parser.$parse($name))$(.$field)*;
matches.insert($name.clone(), Match::$variant(match_));
});
}
macro_rules! insert_spanned {
($variant:ident, $parse:ident$(.$field:ident)*, $name:expr) => ({
let open = self.parser.get_span();
let match_ = try!(self.parser.$parse($name))$(.$field)*;
let spanned = codemap::spanned(open.lo, self.parser.get_last_span().hi, match_);
matches.insert($name.clone(), Match::$variant(spanned));
});
}
for specifier in specification {
match *specifier {
Specifier::Attr(ref name) => insert!(Attr, parse_attribute, name),
Specifier::BinOp(ref name) => match try!(self.expect_token()) {
Token::BinOp(binop) | Token::BinOpEq(binop) => {
let spanned = codemap::respan(self.parser.get_last_span(), binop);
matches.insert(name.clone(), Match::BinOp(spanned));
},
_ => {
let error = format!("expected binop: '{}'", name);
return self.parser.get_last_span().to_error(error);
},
},
Specifier::Block(ref name) => insert!(Block, parse_block, name),
Specifier::Delim(ref name) => {
let open = self.parser.get_span();
let delim = try!(self.parse_delim());
let spanned = codemap::spanned(open.lo, delim.close_span.hi, delim);
matches.insert(name.clone(), Match::Delim(spanned));
},
Specifier::Expr(ref name) => insert!(Expr, parse_expr, name),
Specifier::Ident(ref name) => insert_spanned!(Ident, parse_ident, name),
Specifier::Item(ref name) => insert!(Item, parse_item, name),
Specifier::Lftm(ref name) => insert_spanned!(Lftm, parse_lifetime.name, name),
Specifier::Lit(ref name) => insert!(Lit, parse_lit, name),
Specifier::Meta(ref name) => {
let meta = try!(self.parser.parse_meta_item(name)).map(|mut m| {
m.span.hi = self.parser.get_last_span().hi;
m
});
matches.insert(name.clone(), Match::Meta(meta));
},
Specifier::Pat(ref name) => insert!(Pat, parse_pat, name),
Specifier::Path(ref name) => insert!(Path, parse_path, name),
Specifier::Stmt(ref name) => insert!(Stmt, parse_stmt, name),
Specifier::Ty(ref name) => insert!(Ty, parse_ty, name),
Specifier::Tok(ref name) => {
let tok = try!(self.expect_token());
let spanned = codemap::respan(self.parser.get_last_span(), tok);
matches.insert(name.clone(), Match::Tok(spanned));
},
Specifier::Tt(ref name) => insert!(Tt, parse_token_tree, name),
Specifier::Specific(ref expected) => try!(self.expect_specific_token(expected)),
Specifier::Delimited(delimiter, ref specification) => {
try!(self.expect_specific_token(&Token::OpenDelim(delimiter)));
try!(self.parse_args(&specification, matches));
try!(self.expect_specific_token(&Token::CloseDelim(delimiter)));
},
Specifier::Sequence(amount, ref separator, ref specification) => {
try!(self.parse_sequence(amount, separator.as_ref(), specification, matches));
},
Specifier::NamedSequence(ref name, amount, ref separator, ref specification) => {
let open = self.parser.get_last_span();
let separator = separator.as_ref();
let count = self.parse_sequence(amount, separator, specification, matches);
let close = self.parser.get_last_span();
let spanned = codemap::spanned(open.lo, close.hi, try!(count));
matches.insert(name.clone(), Match::NamedSequence(spanned));
},
}
}
Ok(())
}
pub fn parse(&mut self, specification: &[Specifier]) -> PluginResult<HashMap<String, Match>> {
let mut matches = HashMap::new();
try!(self.parse_args(specification, &mut matches));
if self.parser.is_empty() {
Ok(matches)
} else {
let start = self.parser.get_span();
let span = Span { lo: start.lo, hi: self.span.hi, expn_id: self.span.expn_id };
span.to_error("too many arguments")
}
}
}
pub fn parse_args(
session: &ParseSess, tts: &[TokenTree], specification: &[Specifier]
) -> PluginResult<HashMap<String, Match>> {
if tts.is_empty() && specification.is_empty() {
return Ok(HashMap::new());
}
let start = tts.iter().nth(0).map_or(DUMMY_SP, |s| s.get_span());
let end = tts.iter().last().map_or(DUMMY_SP, |s| s.get_span());
let span = Span { lo: start.lo, hi: end.hi, expn_id: start.expn_id };
if !tts.is_empty() && specification.is_empty() {
return span.to_error("too many arguments");
}
let handler = Handler::with_emitter(false, false, Box::new(SaveEmitter));
let mut codemap = CodeMap::new();
codemap.files = session.codemap().files.clone();
let session = ParseSess::with_span_handler(handler, Rc::new(codemap));
ArgumentParser::new(&session, tts, span).parse(specification)
}