use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
use xxhash_rust::xxh3::xxh3_128;
#[proc_macro_attribute]
pub fn macro_pub(attr: TokenStream, item: TokenStream) -> TokenStream {
let has_simple_decl_macro = cfg!(has_simple_decl_macro);
let hash = xxh3_128(item.to_string().as_bytes());
let error_output = {
let mut output = item.clone();
output.extend(
r#"compile_error! { "`#[macro_pub]` must be used on a `macro_rules!` macro" }"#
.parse::<TokenStream>()
.unwrap(),
);
output
};
let mut attrs = TokenStream::new();
let mut tokens = item.into_iter();
let macro_rules = loop {
match tokens.next() {
Some(TokenTree::Ident(ident)) if ident.to_string() == "macro_rules" => {
break TokenTree::Ident(ident);
}
Some(TokenTree::Punct(punct)) if punct.as_char() == '#' => match tokens.next() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Bracket => {
attrs.extend([TokenTree::Punct(punct), TokenTree::Group(group)])
}
_ => return error_output,
},
_ => return error_output,
}
};
let bang = match tokens.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '!' => TokenTree::Punct(punct),
_ => return error_output,
};
let macro_name = match tokens.next() {
Some(TokenTree::Ident(ident)) => ident,
_ => return error_output,
};
let macro_arms = match tokens.next() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group.stream(),
_ => return error_output,
};
let (vis, need_macro_export) = if attr.is_empty() {
(
[TokenTree::Ident(Ident::new("pub", Span::call_site()))]
.into_iter()
.collect::<TokenStream>(),
true,
)
} else {
(
[
TokenTree::Ident(Ident::new("pub", Span::call_site())),
TokenTree::Group(Group::new(Delimiter::Parenthesis, attr)),
]
.into_iter()
.collect(),
false,
)
};
let macro_rules_name = TokenTree::Ident(Ident::new(
&format!("macro_impl_{}_{}", hash, macro_name),
macro_name.span(),
));
let mut output = attrs.clone();
if has_simple_decl_macro && need_macro_export {
output.extend(
r##"#[cfg(doc)] #[rustc_macro_transparency = "semitransparent"]"##
.parse::<TokenStream>()
.unwrap(),
);
output.extend(vis.clone());
output.extend([
TokenTree::Ident(Ident::new("macro", Span::mixed_site())),
TokenTree::Ident(macro_name.clone()),
TokenTree::Group(Group::new(
Delimiter::Brace,
macro_arms
.clone()
.into_iter()
.map(|tt| match tt {
TokenTree::Punct(punct) if punct.as_char() == ';' => {
TokenTree::Punct(Punct::new(',', punct.spacing()))
}
tt => tt,
})
.collect(),
)),
]);
output.extend(attrs);
output.extend(r##"#[cfg(not(doc))]"##.parse::<TokenStream>().unwrap());
}
if need_macro_export {
output.extend(
"#[macro_export] #[doc(hidden)]"
.parse::<TokenStream>()
.unwrap(),
);
}
output.extend([
macro_rules,
bang,
if need_macro_export {
macro_rules_name.clone()
} else {
TokenTree::Ident(macro_name.clone())
},
TokenTree::Group(Group::new(Delimiter::Brace, macro_arms)),
]);
if has_simple_decl_macro && need_macro_export {
output.extend(r##"#[cfg(not(doc))]"##.parse::<TokenStream>().unwrap());
}
output.extend(vis);
output.extend([
TokenTree::Ident(Ident::new("use", Span::mixed_site())),
if need_macro_export {
macro_rules_name
} else {
TokenTree::Ident(macro_name.clone())
},
TokenTree::Ident(Ident::new("as", Span::mixed_site())),
TokenTree::Ident(macro_name),
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
]);
output.extend(tokens);
output
}