use ast::{self, TokenTree};
use codemap::{Span, DUMMY_SP};
use ext::base::{DummyResult, ExtCtxt, MacResult, SyntaxExtension};
use ext::base::{NormalTT, TTMacroExpander};
use ext::tt::macro_parser::{Success, Error, Failure};
use ext::tt::macro_parser::{MatchedSeq, MatchedNonterminal};
use ext::tt::macro_parser::parse;
use parse::lexer::new_tt_reader;
use parse::parser::{Parser, Restrictions};
use parse::token::{self, special_idents, gensym_ident, NtTT, Token};
use parse::token::Token::*;
use print;
use ptr::P;
use util::small_vector::SmallVector;
use std::cell::RefCell;
use std::collections::{HashMap};
use std::collections::hash_map::{Entry};
use std::rc::Rc;
struct ParserAnyMacro<'a> {
parser: RefCell<Parser<'a>>,
site_span: Span,
macro_ident: ast::Ident
}
impl<'a> ParserAnyMacro<'a> {
fn ensure_complete_parse(&self, allow_semi: bool, context: &str) {
let mut parser = self.parser.borrow_mut();
if allow_semi && parser.token == token::Semi {
parser.bump();
}
if parser.token != token::Eof {
let token_str = parser.this_token_to_string();
let msg = format!("macro expansion ignores token `{}` and any \
following",
token_str);
let span = parser.span;
let mut err = parser.diagnostic().struct_span_err(span, &msg[..]);
let msg = format!("caused by the macro expansion here; the usage \
of `{}!` is likely invalid in {} context",
self.macro_ident, context);
err.span_note(self.site_span, &msg[..])
.emit();
}
}
}
impl<'a> MacResult for ParserAnyMacro<'a> {
fn make_expr(self: Box<ParserAnyMacro<'a>>) -> Option<P<ast::Expr>> {
let ret = panictry!(self.parser.borrow_mut().parse_expr());
self.ensure_complete_parse(true, "expression");
Some(ret)
}
fn make_pat(self: Box<ParserAnyMacro<'a>>) -> Option<P<ast::Pat>> {
let ret = panictry!(self.parser.borrow_mut().parse_pat());
self.ensure_complete_parse(false, "pattern");
Some(ret)
}
fn make_items(self: Box<ParserAnyMacro<'a>>) -> Option<SmallVector<P<ast::Item>>> {
let mut ret = SmallVector::zero();
while let Some(item) = panictry!(self.parser.borrow_mut().parse_item()) {
ret.push(item);
}
self.ensure_complete_parse(false, "item");
Some(ret)
}
fn make_impl_items(self: Box<ParserAnyMacro<'a>>)
-> Option<SmallVector<ast::ImplItem>> {
let mut ret = SmallVector::zero();
loop {
let mut parser = self.parser.borrow_mut();
match parser.token {
token::Eof => break,
_ => ret.push(panictry!(parser.parse_impl_item()))
}
}
self.ensure_complete_parse(false, "item");
Some(ret)
}
fn make_stmts(self: Box<ParserAnyMacro<'a>>)
-> Option<SmallVector<ast::Stmt>> {
let mut ret = SmallVector::zero();
loop {
let mut parser = self.parser.borrow_mut();
match parser.token {
token::Eof => break,
_ => match parser.parse_stmt() {
Ok(maybe_stmt) => match maybe_stmt {
Some(stmt) => ret.push(stmt),
None => (),
},
Err(mut e) => {
e.emit();
break;
}
}
}
}
self.ensure_complete_parse(false, "statement");
Some(ret)
}
fn make_ty(self: Box<ParserAnyMacro<'a>>) -> Option<P<ast::Ty>> {
let ret = panictry!(self.parser.borrow_mut().parse_ty());
self.ensure_complete_parse(false, "type");
Some(ret)
}
}
struct MacroRulesMacroExpander {
name: ast::Ident,
imported_from: Option<ast::Ident>,
lhses: Vec<TokenTree>,
rhses: Vec<TokenTree>,
valid: bool,
}
impl TTMacroExpander for MacroRulesMacroExpander {
fn expand<'cx>(&self,
cx: &'cx mut ExtCtxt,
sp: Span,
arg: &[TokenTree])
-> Box<MacResult+'cx> {
if !self.valid {
return DummyResult::any(sp);
}
generic_extension(cx,
sp,
self.name,
self.imported_from,
arg,
&self.lhses,
&self.rhses)
}
}
fn generic_extension<'cx>(cx: &'cx ExtCtxt,
sp: Span,
name: ast::Ident,
imported_from: Option<ast::Ident>,
arg: &[TokenTree],
lhses: &[TokenTree],
rhses: &[TokenTree])
-> Box<MacResult+'cx> {
if cx.trace_macros() {
println!("{}! {{ {} }}",
name,
print::pprust::tts_to_string(arg));
}
let mut best_fail_spot = DUMMY_SP;
let mut best_fail_msg = "internal error: ran no matchers".to_string();
for (i, lhs) in lhses.iter().enumerate() { let lhs_tt = match *lhs {
TokenTree::Delimited(_, ref delim) => &delim.tts[..],
_ => cx.span_fatal(sp, "malformed macro lhs")
};
match TokenTree::parse(cx, lhs_tt, arg) {
Success(named_matches) => {
let rhs = match rhses[i] {
TokenTree::Delimited(_, ref delimed) => delimed.tts.clone(),
_ => cx.span_fatal(sp, "malformed macro rhs"),
};
let trncbr = new_tt_reader(&cx.parse_sess().span_diagnostic,
Some(named_matches),
imported_from,
rhs);
let mut p = Parser::new(cx.parse_sess(), cx.cfg(), Box::new(trncbr));
p.filename = cx.filename.clone();
p.mod_path_stack = cx.mod_path_stack.clone();
p.restrictions = match cx.in_block {
true => Restrictions::NO_NONINLINE_MOD,
false => Restrictions::empty(),
};
p.check_unknown_macro_variable();
return Box::new(ParserAnyMacro {
parser: RefCell::new(p),
site_span: sp,
macro_ident: name
})
}
Failure(sp, ref msg) => if sp.lo >= best_fail_spot.lo {
best_fail_spot = sp;
best_fail_msg = (*msg).clone();
},
Error(err_sp, ref msg) => {
cx.span_fatal(err_sp.substitute_dummy(sp), &msg[..])
}
}
}
cx.span_fatal(best_fail_spot.substitute_dummy(sp), &best_fail_msg[..]);
}
pub fn compile<'cx>(cx: &'cx mut ExtCtxt,
def: &ast::MacroDef) -> SyntaxExtension {
let lhs_nm = gensym_ident("lhs");
let rhs_nm = gensym_ident("rhs");
let match_lhs_tok = MatchNt(lhs_nm, special_idents::tt, token::Plain, token::Plain);
let match_rhs_tok = MatchNt(rhs_nm, special_idents::tt, token::Plain, token::Plain);
let argument_gram = vec!(
TokenTree::Sequence(DUMMY_SP,
Rc::new(ast::SequenceRepetition {
tts: vec![
TokenTree::Token(DUMMY_SP, match_lhs_tok),
TokenTree::Token(DUMMY_SP, token::FatArrow),
TokenTree::Token(DUMMY_SP, match_rhs_tok)],
separator: Some(token::Semi),
op: ast::KleeneOp::OneOrMore,
num_captures: 2
})),
TokenTree::Sequence(DUMMY_SP,
Rc::new(ast::SequenceRepetition {
tts: vec![TokenTree::Token(DUMMY_SP, token::Semi)],
separator: None,
op: ast::KleeneOp::ZeroOrMore,
num_captures: 0
})));
let arg_reader = new_tt_reader(&cx.parse_sess().span_diagnostic,
None,
None,
def.body.clone());
let argument_map = match parse(cx.parse_sess(),
cx.cfg(),
arg_reader,
&argument_gram) {
Success(m) => m,
Failure(sp, str) | Error(sp, str) => {
panic!(cx.parse_sess().span_diagnostic
.span_fatal(sp.substitute_dummy(def.span), &str[..]));
}
};
let mut valid = true;
let lhses = match **argument_map.get(&lhs_nm.name).unwrap() {
MatchedSeq(ref s, _) => {
s.iter().map(|m| match **m {
MatchedNonterminal(NtTT(ref tt)) => (**tt).clone(),
_ => cx.span_bug(def.span, "wrong-structured lhs")
}).collect()
}
_ => cx.span_bug(def.span, "wrong-structured lhs")
};
for lhs in &lhses {
check_lhs_nt_follows(cx, lhs, def.span);
}
let rhses = match **argument_map.get(&rhs_nm.name).unwrap() {
MatchedSeq(ref s, _) => {
s.iter().map(|m| match **m {
MatchedNonterminal(NtTT(ref tt)) => (**tt).clone(),
_ => cx.span_bug(def.span, "wrong-structured rhs")
}).collect()
}
_ => cx.span_bug(def.span, "wrong-structured rhs")
};
for rhs in &rhses {
valid &= check_rhs(cx, rhs);
}
let exp: Box<_> = Box::new(MacroRulesMacroExpander {
name: def.ident,
imported_from: def.imported_from,
lhses: lhses,
rhses: rhses,
valid: valid,
});
NormalTT(exp, Some(def.span), def.allow_internal_unstable)
}
fn ref_slice<A>(s: &A) -> &[A] { use std::slice::from_raw_parts; unsafe { from_raw_parts(s, 1) } }
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &TokenTree, sp: Span) {
match lhs {
&TokenTree::Delimited(_, ref tts) => {
check_matcher(cx, &tts.tts);
},
tt @ &TokenTree::Sequence(..) => {
check_matcher(cx, ref_slice(tt));
},
_ => cx.span_err(sp, "invalid macro matcher; matchers must be contained \
in balanced delimiters or a repetition indicator")
};
}
fn check_rhs(cx: &mut ExtCtxt, rhs: &TokenTree) -> bool {
match *rhs {
TokenTree::Delimited(..) => return true,
_ => cx.span_err(rhs.get_span(), "macro rhs must be delimited")
}
false
}
#[derive(Debug)]
struct OnFail {
saw_failure: bool,
action: OnFailAction,
}
#[derive(Copy, Clone, Debug)]
enum OnFailAction { Warn, Error, DoNothing }
impl OnFail {
fn warn() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Warn } }
fn error() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::Error } }
fn do_nothing() -> OnFail { OnFail { saw_failure: false, action: OnFailAction::DoNothing } }
fn react(&mut self, cx: &mut ExtCtxt, sp: Span, msg: &str) {
match self.action {
OnFailAction::DoNothing => {}
OnFailAction::Error => cx.span_err(sp, msg),
OnFailAction::Warn => {
cx.struct_span_warn(sp, msg)
.span_note(sp, "The above warning will be a hard error in the next release.")
.emit();
}
};
self.saw_failure = true;
}
}
fn check_matcher(cx: &mut ExtCtxt, matcher: &[TokenTree]) {
let mut on_fail = OnFail::do_nothing();
check_matcher_old(cx, matcher.iter(), &Eof, &mut on_fail);
let mut on_fail = if on_fail.saw_failure {
OnFail::error()
} else {
OnFail::warn()
};
check_matcher_new(cx, matcher, &mut on_fail);
}
fn check_matcher_old<'a, I>(cx: &mut ExtCtxt, matcher: I, follow: &Token, on_fail: &mut OnFail)
-> Option<(Span, Token)> where I: Iterator<Item=&'a TokenTree> {
use print::pprust::token_to_string;
use std::iter::once;
let mut last = None;
let mut tokens = matcher.peekable();
while let Some(token) = tokens.next() {
last = match *token {
TokenTree::Token(sp, MatchNt(ref name, ref frag_spec, _, _)) => {
if can_be_followed_by_any(&frag_spec.name.as_str()) {
continue
} else {
let next_token = match tokens.peek() {
Some(&&TokenTree::Token(_, CloseDelim(_))) => follow.clone(),
Some(&&TokenTree::Token(_, ref tok)) => tok.clone(),
Some(&&TokenTree::Sequence(sp, _)) => {
on_fail.react(cx, sp,
&format!("`${0}:{1}` is followed by a \
sequence repetition, which is not \
allowed for `{1}` fragments",
name, frag_spec)
);
Eof
},
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
None => follow.clone()
};
let tok = if let TokenTree::Token(_, ref tok) = *token {
tok
} else {
unreachable!()
};
match (&next_token, is_in_follow(cx, &next_token, &frag_spec.name.as_str())) {
(_, Err(msg)) => {
on_fail.react(cx, sp, &msg);
continue
}
(&Eof, _) => return Some((sp, tok.clone())),
(_, Ok(true)) => continue,
(next, Ok(false)) => {
on_fail.react(cx, sp, &format!("`${0}:{1}` is followed by `{2}`, which \
is not allowed for `{1}` fragments",
name, frag_spec,
token_to_string(next)));
continue
},
}
}
},
TokenTree::Sequence(sp, ref seq) => {
match seq.separator {
Some(ref u) => {
let last = check_matcher_old(cx, seq.tts.iter(), u, on_fail);
match last {
Some((span, tok)) => {
let fol = match tokens.peek() {
Some(&&TokenTree::Token(_, ref tok)) => tok.clone(),
Some(&&TokenTree::Delimited(_, ref delim)) =>
delim.close_token(),
Some(_) => {
on_fail.react(cx, sp, "sequence repetition followed by \
another sequence repetition, which is not allowed");
Eof
},
None => Eof
};
check_matcher_old(cx, once(&TokenTree::Token(span, tok.clone())),
&fol, on_fail)
},
None => last,
}
},
None => {
let fol = match tokens.peek() {
Some(&&TokenTree::Token(_, ref tok)) => tok.clone(),
Some(&&TokenTree::Delimited(_, ref delim)) => delim.close_token(),
Some(_) => {
on_fail.react(cx, sp, "sequence repetition followed by another \
sequence repetition, which is not allowed");
Eof
},
None => Eof
};
check_matcher_old(cx, seq.tts.iter(), &fol, on_fail)
}
}
},
TokenTree::Token(..) => {
continue
},
TokenTree::Delimited(_, ref tts) => {
check_matcher_old(cx, tts.tts.iter(), &tts.close_token(), on_fail)
}
}
}
last
}
fn check_matcher_new(cx: &mut ExtCtxt, matcher: &[TokenTree], on_fail: &mut OnFail) {
let first_sets = FirstSets::new(matcher);
let empty_suffix = TokenSet::empty();
check_matcher_core(cx, &first_sets, matcher, &empty_suffix, on_fail);
}
struct FirstSets {
first: HashMap<Span, Option<TokenSet>>,
}
impl FirstSets {
fn new(tts: &[TokenTree]) -> FirstSets {
let mut sets = FirstSets { first: HashMap::new() };
build_recur(&mut sets, tts);
return sets;
fn build_recur(sets: &mut FirstSets, tts: &[TokenTree]) -> TokenSet {
let mut first = TokenSet::empty();
for tt in tts.iter().rev() {
match *tt {
TokenTree::Token(sp, ref tok) => {
first.replace_with((sp, tok.clone()));
}
TokenTree::Delimited(_, ref delimited) => {
build_recur(sets, &delimited.tts[..]);
first.replace_with((delimited.open_span,
Token::OpenDelim(delimited.delim)));
}
TokenTree::Sequence(sp, ref seq_rep) => {
let subfirst = build_recur(sets, &seq_rep.tts[..]);
match sets.first.entry(sp) {
Entry::Vacant(vac) => {
vac.insert(Some(subfirst.clone()));
}
Entry::Occupied(mut occ) => {
occ.insert(None);
}
}
if let (Some(ref sep), true) = (seq_rep.separator.clone(),
subfirst.maybe_empty) {
first.add_one_maybe((sp, sep.clone()));
}
if subfirst.maybe_empty || seq_rep.op == ast::KleeneOp::ZeroOrMore {
first.add_all(&TokenSet { maybe_empty: true, ..subfirst });
} else {
first = subfirst;
}
}
}
}
return first;
}
}
fn first(&self, tts: &[TokenTree]) -> TokenSet {
let mut first = TokenSet::empty();
for tt in tts.iter() {
assert!(first.maybe_empty);
match *tt {
TokenTree::Token(sp, ref tok) => {
first.add_one((sp, tok.clone()));
return first;
}
TokenTree::Delimited(_, ref delimited) => {
first.add_one((delimited.open_span,
Token::OpenDelim(delimited.delim)));
return first;
}
TokenTree::Sequence(sp, ref seq_rep) => {
match self.first.get(&sp) {
Some(&Some(ref subfirst)) => {
if let (Some(ref sep), true) = (seq_rep.separator.clone(),
subfirst.maybe_empty) {
first.add_one_maybe((sp, sep.clone()));
}
assert!(first.maybe_empty);
first.add_all(subfirst);
if subfirst.maybe_empty || seq_rep.op == ast::KleeneOp::ZeroOrMore {
first.maybe_empty = true;
continue;
} else {
return first;
}
}
Some(&None) => {
panic!("assume all sequences have (unique) spans for now");
}
None => {
panic!("We missed a sequence during FirstSets construction");
}
}
}
}
}
assert!(first.maybe_empty);
return first;
}
}
#[derive(Clone, Debug)]
struct TokenSet {
tokens: Vec<(Span, Token)>,
maybe_empty: bool,
}
impl TokenSet {
fn empty() -> Self { TokenSet { tokens: Vec::new(), maybe_empty: true } }
fn singleton(tok: (Span, Token)) -> Self {
TokenSet { tokens: vec![tok], maybe_empty: false }
}
fn replace_with(&mut self, tok: (Span, Token)) {
self.tokens.clear();
self.tokens.push(tok);
self.maybe_empty = false;
}
fn replace_with_irrelevant(&mut self) {
self.tokens.clear();
self.maybe_empty = false;
}
fn add_one(&mut self, tok: (Span, Token)) {
if !self.tokens.contains(&tok) {
self.tokens.push(tok);
}
self.maybe_empty = false;
}
fn add_one_maybe(&mut self, tok: (Span, Token)) {
if !self.tokens.contains(&tok) {
self.tokens.push(tok);
}
}
fn add_all(&mut self, other: &Self) {
for tok in &other.tokens {
if !self.tokens.contains(tok) {
self.tokens.push(tok.clone());
}
}
if !other.maybe_empty {
self.maybe_empty = false;
}
}
}
fn check_matcher_core(cx: &mut ExtCtxt,
first_sets: &FirstSets,
matcher: &[TokenTree],
follow: &TokenSet,
on_fail: &mut OnFail) -> TokenSet {
use print::pprust::token_to_string;
let mut last = TokenSet::empty();
'each_token: for i in 0..matcher.len() {
let token = &matcher[i];
let suffix = &matcher[i+1..];
let build_suffix_first = || {
let mut s = first_sets.first(suffix);
if s.maybe_empty { s.add_all(follow); }
return s;
};
let suffix_first;
match *token {
TokenTree::Token(sp, ref tok) => {
let can_be_followed_by_any;
if let Err(bad_frag) = has_legal_fragment_specifier(tok) {
on_fail.react(cx, sp, &format!("invalid fragment specifier `{}`", bad_frag));
can_be_followed_by_any = true;
} else {
can_be_followed_by_any = token_can_be_followed_by_any(tok);
}
if can_be_followed_by_any {
last.replace_with_irrelevant();
continue 'each_token;
} else {
last.replace_with((sp, tok.clone()));
suffix_first = build_suffix_first();
}
}
TokenTree::Delimited(_, ref d) => {
let my_suffix = TokenSet::singleton((d.close_span, Token::CloseDelim(d.delim)));
check_matcher_core(cx, first_sets, &d.tts, &my_suffix, on_fail);
last.replace_with_irrelevant();
continue 'each_token;
}
TokenTree::Sequence(sp, ref seq_rep) => {
suffix_first = build_suffix_first();
let mut new;
let my_suffix = if let Some(ref u) = seq_rep.separator {
new = suffix_first.clone();
new.add_one_maybe((sp, u.clone()));
&new
} else {
&suffix_first
};
let next = check_matcher_core(cx, first_sets, &seq_rep.tts, my_suffix, on_fail);
if next.maybe_empty {
last.add_all(&next);
} else {
last = next;
}
continue 'each_token;
}
}
'each_last: for &(_sp, ref t) in &last.tokens {
if let MatchNt(ref name, ref frag_spec, _, _) = *t {
for &(sp, ref next_token) in &suffix_first.tokens {
match is_in_follow(cx, next_token, &frag_spec.name.as_str()) {
Err(msg) => {
on_fail.react(cx, sp, &msg);
continue 'each_last;
}
Ok(true) => {}
Ok(false) => {
let may_be = if last.tokens.len() == 1 &&
suffix_first.tokens.len() == 1
{
"is"
} else {
"may be"
};
on_fail.react(
cx, sp,
&format!("`${name}:{frag}` {may_be} followed by `{next}`, which \
is not allowed for `{frag}` fragments",
name=name,
frag=frag_spec,
next=token_to_string(next_token),
may_be=may_be));
}
}
}
}
}
}
last
}
fn token_can_be_followed_by_any(tok: &Token) -> bool {
if let &MatchNt(_, ref frag_spec, _, _) = tok {
frag_can_be_followed_by_any(&frag_spec.name.as_str())
} else {
true
}
}
fn frag_can_be_followed_by_any(frag: &str) -> bool {
match frag {
"item" | "block" | "ident" | "meta" | "tt" => true,
_ =>
false,
}
}
fn can_be_followed_by_any(frag: &str) -> bool {
match frag {
"item" | "block" | "ident" | "meta" | "tt" => true,
_ =>
false,
}
}
fn is_in_follow(_: &ExtCtxt, tok: &Token, frag: &str) -> Result<bool, String> {
if let &CloseDelim(_) = tok {
Ok(true)
} else {
match frag {
"item" => {
Ok(true)
},
"block" => {
Ok(true)
},
"stmt" | "expr" => {
match *tok {
FatArrow | Comma | Semi => Ok(true),
_ => Ok(false)
}
},
"pat" => {
match *tok {
FatArrow | Comma | Eq | BinOp(token::Or) => Ok(true),
Ident(i, _) if (i.name.as_str() == "if" ||
i.name.as_str() == "in") => Ok(true),
_ => Ok(false)
}
},
"path" | "ty" => {
match *tok {
OpenDelim(token::DelimToken::Brace) | OpenDelim(token::DelimToken::Bracket) |
Comma | FatArrow | Colon | Eq | Gt | Semi | BinOp(token::Or) => Ok(true),
MatchNt(_, ref frag, _, _) if frag.name.as_str() == "block" => Ok(true),
Ident(i, _) if (i.name.as_str() == "as" ||
i.name.as_str() == "where") => Ok(true),
_ => Ok(false)
}
},
"ident" => {
Ok(true)
},
"meta" | "tt" => {
Ok(true)
},
_ => Err(format!("invalid fragment specifier `{}`", frag))
}
}
}
fn has_legal_fragment_specifier(tok: &Token) -> Result<(), String> {
debug!("has_legal_fragment_specifier({:?})", tok);
if let &MatchNt(_, ref frag_spec, _, _) = tok {
let s = &frag_spec.name.as_str();
if !is_legal_fragment_specifier(s) {
return Err(s.to_string());
}
}
Ok(())
}
fn is_legal_fragment_specifier(frag: &str) -> bool {
match frag {
"item" | "block" | "stmt" | "expr" | "pat" |
"path" | "ty" | "ident" | "meta" | "tt" => true,
_ => false,
}
}