#![forbid(unsafe_code)]
extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree as Tt, Punct, Group, Spacing,
Delimiter};
#[proc_macro]
pub fn postfix_macros(stream :TokenStream) -> TokenStream {
let mut vis = Visitor;
let res = vis.visit_stream(stream);
res
}
struct Visitor;
impl Visitor {
fn visit_stream(&mut self, stream :TokenStream) -> TokenStream {
let mut res = Vec::new();
let mut stream_iter = stream.into_iter();
while let Some(tt) = stream_iter.next() {
match tt {
Tt::Group(group) => {
let mut postfix_macro = false;
{
let last_three = res.rchunks(3).next();
if let Some(&[Tt::Punct(ref p1), Tt::Ident(_), Tt::Punct(ref p2)]) = last_three {
if (p1.as_char(), p1.spacing(), p2.as_char(), p2.spacing()) == ('.', Spacing::Alone, '!', Spacing::Alone) {
postfix_macro = true;
}
}
}
let group = if postfix_macro {
let mac_bang = res.pop().unwrap();
let mac = res.pop().unwrap();
res.pop().unwrap();
let expr_len = expression_length(&res);
if expr_len == 0 {
panic!("expected something before the postfix macro invocation");
}
let gr = self.visit_group(group);
let arg_tokens = &res[(res.len() - expr_len)..];
let gr = prepend_macro_arg_to_group(arg_tokens, gr);
res.truncate(res.len() - expr_len);
res.push(mac);
res.push(mac_bang);
gr
} else {
group
};
let tt = Tt::Group(self.visit_group(group));
res.push(tt);
},
Tt::Ident(id) => {
res.push(Tt::Ident(id));
},
Tt::Punct(p) => {
res.push(Tt::Punct(p));
},
Tt::Literal(lit) => {
res.push(Tt::Literal(lit));
},
}
}
res.into_iter().collect()
}
fn visit_group(&mut self, group :Group) -> Group {
let delim = group.delimiter();
let span = group.span();
let stream = self.visit_stream(group.stream());
let mut gr = Group::new(delim, stream);
gr.set_span(span);
gr
}
}
fn expression_length(tts :&[Tt]) -> usize {
let mut expr_len = 0;
let mut last_was_punctuation = true;
let mut last_was_group = true;
'outer: while expr_len < tts.len() {
let tt = &tts[tts.len() - 1 - expr_len];
let mut is_punctuation = false;
let mut is_group = false;
match tt {
Tt::Group(group) => {
is_group = true;
if !last_was_punctuation {
break;
}
if group.delimiter() == Delimiter::Brace {
loop {
if expr_len + 1 >= tts.len() {
break;
}
let tt_before = &tts[tts.len() - 2 - expr_len];
match tt_before {
Tt::Group(_group) => {
},
Tt::Ident(id) => {
let id_str = id.to_string();
if id_str == "else" {
expr_len += 3;
continue;
} else {
}
},
Tt::Punct(p) => match p.as_char() {
';' | ',' => {
expr_len += 1;
break 'outer;
},
'!' => break,
'|' => panic!("Closures not supported yet"),
p => panic!("Group expr search encountered unsupported punctuation {}", p),
},
Tt::Literal(_lit) => {
},
}
let sub_expr_len = expression_length(&tts[..tts.len() - 1 - expr_len]);
expr_len += sub_expr_len;
let tt_before = if tts.len() < 2 + expr_len {
None
} else {
tts.get(tts.len() - 2 - expr_len)
};
let tt_before_that = if tts.len() < 3 + expr_len {
None
} else {
tts.get(tts.len() - 3 - expr_len)
};
match (tt_before_that, tt_before) {
(Some(Tt::Ident(id_t)), Some(Tt::Ident(id))) => {
let id_t = id_t.to_string();
let id = id.to_string();
if id_t == "else" && id == "if" {
expr_len += 3;
} else if id == "match" {
is_group = false;
expr_len += 1;
break;
}
},
(_, Some(Tt::Ident(id))) => {
let id = id.to_string();
if id == "if" || id == "match" {
is_group = false;
expr_len += 1;
break;
} else {
}
},
(_, Some(Tt::Punct(p))) => {
match p.as_char() {
'=' => {
if let Some(Tt::Punct(p_t)) = tt_before_that {
if p_t.as_char() == '=' {
panic!("== in if clause not supported yet");
}
}
panic!("if let not supported");
},
_ => panic!("{} in if not supported yet", p),
}
},
(None, None) => {
break;
},
_ => {
panic!("Hit unsupported case: {:?} {:?}", tt_before_that.map(|v| v.to_string()),
tt_before.map(|v| v.to_string()));
},
}
}
}
},
Tt::Ident(id) => {
if !last_was_punctuation && !last_was_group {
let id_str = id.to_string();
if id_str != "mut" {
break;
}
}
},
Tt::Punct(p) => {
is_punctuation = true;
match p.as_char() {
'.' if p.spacing() == Spacing::Alone => (),
':' | '?' | '!' => (),
'.' if p.spacing() == Spacing::Joint => break,
',' | ';' | '+' | '/' | '%' | '=' | '<' | '>' | '|' | '^' => break,
'&' | '*' | '-' => {
let mut offs_until_binop_partner = 0;
for tt in tts[.. tts.len() - expr_len - 1].iter().rev() {
match tt {
Tt::Group(gr) => {
match gr.delimiter() {
Delimiter::Brace => {
expr_len += offs_until_binop_partner + 1;
break 'outer;
}
Delimiter::Parenthesis | Delimiter::Bracket => {
break;
},
Delimiter::None => {
panic!("We don't support groups delimitered by none yet: {}", gr);
},
}
},
Tt::Ident(id) => {
let id_str = id.to_string();
match id_str.as_str() {
"if" | "match" => {
expr_len += offs_until_binop_partner + 1;
break 'outer;
},
"mut" => (),
_ => break,
}
},
Tt::Punct(p) => {
match p.as_char() {
';' |
',' |
'=' => {
expr_len += offs_until_binop_partner + 1;
break 'outer;
},
'!' |
'&' | '*' | '-' => (),
_ => panic!("Binop partner search encountered punct '{}'", p),
}
},
Tt::Literal(_lit) => {
break;
},
}
offs_until_binop_partner += 1;
}
if offs_until_binop_partner == tts.len() - expr_len - 1 {
expr_len += offs_until_binop_partner + 1;
break;
}
let first = &tts[tts.len() - (expr_len + 1) - offs_until_binop_partner];
let second = &tts[tts.len() - (expr_len + 1) - offs_until_binop_partner + 1];
let mut binop_tts = 1;
match first {
Tt::Group(_gr) => unreachable!(),
Tt::Ident(id) => panic!("Can't start a binop chain with ident '{}'", id),
Tt::Punct(p1) => {
if let Tt::Punct(p2) = second {
let is_binop_and_and = p1.spacing() == Spacing::Joint &&
p1.as_char() == '&' && p2.as_char() == '&';
if is_binop_and_and {
binop_tts = 2;
}
}
},
Tt::Literal(_lit) => unreachable!(),
}
expr_len += 1 + offs_until_binop_partner - binop_tts;
break;
},
c => panic!("Encountered unsupported punctuation {}", c),
}
},
Tt::Literal(_lit) => {
},
}
expr_len += 1;
last_was_punctuation = is_punctuation;
last_was_group = is_group;
}
expr_len
}
fn prepend_macro_arg_to_group(tokens :&[Tt], gr :Group) -> Group {
let expr = match &tokens {
&[tt] if matches!(tt, Tt::Literal(_) | Tt::Ident(_)) => {
tt.clone()
},
_ => {
let expr_stream = tokens.iter().cloned().collect();
let expr_gr = Group::new(Delimiter::Brace, expr_stream);
Tt::Group(expr_gr)
},
};
let stream = gr.stream();
let delim = gr.delimiter();
let mut res_stream = TokenStream::from(expr);
if !stream.is_empty() {
res_stream.extend(std::iter::once(Tt::Punct(Punct::new(',', Spacing::Alone))));
res_stream.extend(stream);
}
Group::new(delim, res_stream)
}