#![feature(proc_macro_span)]
#![feature(proc_macro_quote)]
use proc_macro::{Delimiter, Group, Spacing, Span, TokenStream, TokenTree};
#[proc_macro]
pub fn scrub(body: TokenStream) -> TokenStream {
let parent = Span::call_site()
.parent()
.unwrap()
.parent()
.expect("called outside macro");
map_stream(body, |mut t| {
if let Some(p) = t.span().parent() {
t.set_span(p.located_at(parent).resolved_at(t.span()));
}
if let TokenTree::Group(g) = t {
t = map_group(g, scrub)
}
t
})
}
fn map_stream(stream: TokenStream, f: impl FnMut(TokenTree) -> TokenTree) -> TokenStream {
stream.into_iter().map(f).collect()
}
fn map_group(g: Group, f: impl FnOnce(TokenStream) -> TokenStream) -> TokenTree {
let mut g2 = Group::new(g.delimiter(), f(g.stream()));
g2.set_span(g.span());
TokenTree::Group(g2)
}
#[proc_macro_attribute]
pub fn scrubbed(attr: TokenStream, body: TokenStream) -> TokenStream {
assert!(attr.is_empty());
let mut func: fn(TokenStream) -> TokenStream = scrub_macro_body;
map_stream(body, |mut t| {
match t {
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Parenthesis => {
func = add_scrub;
}
TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => {
t = map_group(g, func);
}
_ => {}
}
t
})
}
fn scrub_macro_body(body: TokenStream) -> TokenStream {
let mut state = 0;
map_stream(body, |mut t| {
match t {
TokenTree::Punct(ref p) if p.spacing() == Spacing::Joint => state = 1,
TokenTree::Punct(ref p) if p.as_char() == '>' && state == 1 => state = 2,
TokenTree::Group(g) if g.delimiter() == Delimiter::Brace && state == 2 => {
t = map_group(g, add_scrub);
state = 0;
}
_ => state = 0,
};
t
})
}
fn add_scrub(body: TokenStream) -> TokenStream {
proc_macro::quote! { ::scrub::scrub! { $body } }
}