use syn::{parse::Nothing, spanned::Spanned, ImplItem::{Method, Const}, Type::Path, ItemFn, ItemImpl,
DeriveInput, Ident, Attribute};
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
#[proc_macro_attribute]
pub fn override_final(attr: TokenStream, input: TokenStream)-> TokenStream {
syn::parse_macro_input!(attr as Nothing);
if let Ok(impl_block) = syn::parse::<ItemImpl>(input.clone()) {
let self_type = match impl_block.self_ty.as_ref() {
Path(path) => path,
_ => return quick_error(format!("Could not get Path for impl (should never see this)")),
}.path.segments[0].ident.to_string();
match impl_block.items.into_iter().fold(None, |acc, item| {
let new_error = match item {
Method(method) => {
let priority_lesser =
std::env::var(format!("__override_final_method_{}_{}", self_type, &method.sig.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
syn::Error::new(
method.sig.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Method requested final. \
Replace #[override_final] with #[default] or higher \
on a (seperate if required) impl block to make top level."),
"1" =>
format!("Method requested final. \
Replace #[override_final] with #[override_default] or higher \
on a (seperate if required) impl block to make top level."),
priority_lesser =>
format!("Method requested final. \
Replace #[override_final] with #[override_default(priority = {})] \
or higher on a (seperate if required) impl block to make top level.",
priority_lesser),
}
)
},
Const(constant) => {
let priority_lesser =
std::env::var(format!("__override_final_implconst_{}_{}", self_type, &constant.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
syn::Error::new(
constant.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Impl constant requested final. \
Replace #[override_final] with #[default] or higher \
on a (seperate if required) impl block to make top level."),
"1" =>
format!("Impl constant requested final. \
Replace #[override_final] with #[override_default] or higher \
on a (seperate if required) impl block to make top level."),
priority_lesser =>
format!("Impl constant requested final. \
Replace #[override_final] with #[override_default(priority = {})] \
or higher on a (seperate if required) impl block to make top level.",
priority_lesser),
}
)
}
item => syn::Error::new(item.span(),
format!("I can't finalize this yet")),
};
match acc {
None => Some(new_error),
Some(mut errors) => {
errors.combine(new_error);
Some(errors)
},
}
}) {
Some(errors) => errors.to_compile_error().into(),
None => input,
}
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
let priority_lesser =
std::env::var(format!("__override_final_func_{}", &item.sig.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
return syn::Error::new(
item.sig.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Function requested final. \
Replace #[override_final] with #[default] or higher to make top level."),
"1" =>
format!("Function requested final. \
Replace #[override_final] with #[override_default] \
or higher to make top level."),
priority_lesser =>
format!("Function requested final. \
Replace #[override_final] with #[override_default(priority = {})] \
or higher to make top level.",
priority_lesser),
}
).to_compile_error().into();
} else {
quick_error(format!("I can't finalize whatever this is attached to yet"))
}
}
#[proc_macro_attribute]
pub fn default(attr: TokenStream, input: TokenStream) -> TokenStream {
syn::parse_macro_input!(attr as Nothing);
attach(input, 0)
}
#[proc_macro_attribute]
pub fn override_default(attr: TokenStream, input: TokenStream) -> TokenStream {
let priority = {
if let Ok(_) = syn::parse::<Nothing>(attr.clone()) {
1
} else if let Ok(syn::Expr::Assign(assign)) = syn::parse::<syn::Expr>(attr.clone()) {
if let (syn::Expr::Path(left), syn::Expr::Lit(right)) = (*assign.left, *assign.right) {
if left.path.segments[0].ident.to_string() == "priority" {
if let syn::Lit::Int(lit) = right.lit {
if let Ok(i) = lit.base10_parse::<u32>() {
i
} else {
return quick_error(format!("Could not parse literal"));
}
} else {
return quick_error(format!("Expected integer literal"));
}
} else {
return quick_error(format!("Unexpected arguement name"));
}
} else {
return quick_error(format!("Incorrect arguement format / expected positive integer"));
}
} else {
return quick_error(format!("Unexpected arguement {} (expected priority)", attr.to_string()));
}
};
attach(input, priority)
}
fn quick_error(message: String) -> TokenStream {
syn::Error::new(
Span::call_site(),
message
).to_compile_error().into()
}
fn attach(input: TokenStream, priority: u32) -> TokenStream {
if let Ok(item) = syn::parse::<ItemImpl>(input.clone()) {
attach_impl(item, priority)
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
attach_function(item, priority)
} else {
quick_error(format!("I can't parse this yet"))
}
}
fn attach_function(mut input: ItemFn, priority: u32) -> TokenStream {
attr_add(&mut input.attrs,
format!("__override_priority_{}_func_{}", priority, &input.sig.ident));
match std::env::var(format!("__override_acceptflags_func_{}", &input.sig.ident)) {
Err(_) => {
TokenStream::from(quote! {
#input
})
},
Ok(flags) => {
let old_attrs = input.attrs.clone();
let old_ident = &input.sig.ident;
let old_sig = input.sig.clone();
let mut args = Vec::new();
for input in &input.sig.inputs {
match input {
syn::FnArg::Typed(t) => {
match t.pat.as_ref() {
syn::Pat::Ident(p) => args.push(&p.ident),
_ => return syn::Error::new(
t.span(),
format!("I do not know what this is and \
it can't be overriden"))
.to_compile_error().into(),
}
},
arg => return syn::Error::new(
arg.span(),
format!("I can only override typed arguments"))
.to_compile_error().into(),
}
};
let if_branches = flags.split(" ").map(|flagstr| {
let flagext = Ident::new(&format!("__override_flagext_{}_{}",
flagstr, old_ident),
Span::call_site());
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) > 0 {
#flagext (#(#args),*);
}
}
}).collect::<Vec<proc_macro2::TokenStream>>();
input.sig.ident = Ident::new(&format!("__override_flagentry_{}",
old_ident),
Span::call_site());
let sigentry = &input.sig.ident;
attr_inline(&mut input.attrs);
TokenStream::from(quote! {
#(#old_attrs)*
#old_sig {
#(#if_branches else )* {
#sigentry (#(#args),*);
}
}
#input
})
}
}
}
fn attach_impl(mut input: ItemImpl, priority: u32) -> TokenStream {
let self_type = match input.self_ty.as_ref() {
Path(path) => path,
item => return syn::Error::new(
item.span(),
format!("Could not get Path for impl (should never see this)"))
.to_compile_error().into(),
}.path.segments[0].ident.to_string();
let mut additional_items: Vec::<syn::ImplItem> = Vec::new();
for item in &mut input.items {
match item {
Method(method) => {
attr_add(&mut method.attrs, format!("__override_priority_{}_method_{}_{}",
priority,
self_type,
&method.sig.ident));
if let Ok(flags) = std::env::var(format!("__override_acceptflags_method_{}_{}", self_type, &method.sig.ident)) {
let old_attrs = method.attrs.clone();
let old_ident = &method.sig.ident;
let old_sig = method.sig.clone();
let mut args = Vec::new();
for input in &method.sig.inputs {
match input {
syn::FnArg::Typed(t) => {
match t.pat.as_ref() {
syn::Pat::Ident(p) => args.push(&p.ident),
_ => return syn::Error::new(
t.span(),
format!("I do not know what this is and \
it can't be overriden"))
.to_compile_error().into(),
}
},
arg => return syn::Error::new(
arg.span(),
format!("I can only override typed arguments"))
.to_compile_error().into(),
}
};
let if_branches = flags.split(" ").map(|flagstr| {
let flagext = Ident::new(&format!("__override_flagext_{}_{}",
flagstr, old_ident),
Span::call_site());
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) > 0 {
Self::#flagext (#(#args),*);
}
}
}).collect::<Vec<proc_macro2::TokenStream>>();
method.sig.ident = Ident::new(&format!("__override_flagentry_{}",
old_ident),
Span::call_site());
let sigentry = &method.sig.ident;
attr_inline(&mut method.attrs);
additional_items.push(syn::parse2::<syn::ImplItem>(quote! {
#(#old_attrs)*
#old_sig {
#(#if_branches else )* {
Self::#sigentry (#(#args),*);
}
}
}).unwrap());
}
},
Const(constant) =>
match std::env::var(format!("__override_acceptflags_method_{}", self_type)) {
Err(_) =>
attr_add(&mut constant.attrs, format!("__override_priority_{}_implconst_{}_{}",
priority,
self_type,
&constant.ident)),
Ok(_) => return syn::Error::new(
item.span(),
format!("Laying flags on const currently envokes undefined behavior"))
.to_compile_error().into(),
},
item => return syn::Error::new(
item.span(),
format!("I can't overload anything other than methods/consts in an impl block yet"))
.to_compile_error().into(),
}
}
input.items.append(&mut additional_items);
TokenStream::from(quote! {
#input
})
}
fn attr_add(attrs: &mut Vec<Attribute>, flag: String) {
let override_flag = Ident::new(&flag, Span::call_site());
attrs.push(
syn::parse2::<DeriveInput>(
quote! {
#[cfg(not(#override_flag))]
struct Dummy;
}
).unwrap().attrs.swap_remove(0));
}
fn attr_inline(attrs: &mut Vec<Attribute>) {
attrs.push(
syn::parse2::<DeriveInput>(
quote! {
#[inline(always)]
struct Dummy;
}
).unwrap().attrs.swap_remove(0));
}
#[proc_macro_attribute]
pub fn override_flag(attr: TokenStream, input: TokenStream) -> TokenStream {
let (flag, priority) =
if let Ok(expr) = syn::parse::<syn::Expr>(attr.clone()) {
if let syn::Expr::Assign(assign) = expr {
if let (syn::Expr::Path(left), syn::Expr::Path(right)) = (*assign.left, *assign.right) {
if left.path.segments[0].ident.to_string() == "flag" {
(right.path.segments[0].ident.to_string(), 1)
} else {
return quick_error(format!("mandatory arguement 'flag' ommited"));
}
} else {
return quick_error(format!("Bad arguement form: left side must be \
'flag', right side must be an identifier"));
}
} else {
return quick_error(format!("Malformed arguement"));
}
} else {
let attrstr = attr.to_string();
if attrstr.matches(",").count() == 1 {
let args = attrstr.split(",").collect::<Vec<&str>>();
if let (Ok(syn::Expr::Assign(arg1)), Ok(syn::Expr::Assign(arg2))) = (syn::parse_str::<syn::Expr>(args[0]), syn::parse_str::<syn::Expr>(args[1])) {
if let (syn::Expr::Path(left1), syn::Expr::Path(left2)) = (*arg1.left, *arg2.left) {
let (flagarg, parg) =
if left1.path.segments[0].ident.to_string() == "flag" && left2.path.segments[0].ident.to_string() == "priority" {
((left1, *arg1.right), (left2, *arg2.right))
} else if left1.path.segments[0].ident.to_string() == "priority" && left2.path.segments[0].ident.to_string() == "flag" {
((left2, *arg2.right), (left1, *arg1.right))
} else {
return quick_error(format!("Invalid arguement list"));
};
if let (syn::Expr::Path(right1), syn::Expr::Lit(right2)) = (flagarg.1, parg.1) {
if let syn::Lit::Int(right2int) = right2.lit {
if let Ok(right2val) = right2int.base10_parse::<u32>() {
(right1.path.segments[0].ident.to_string(), right2val)
} else {
return quick_error(format!("Priority must be a positive \
integer"));
}
} else {
return quick_error(format!("Priority must be a positive \
integer"));
}
} else {
return quick_error(format!("Flag must be an identifier"));
}
} else {
return quick_error(format!("Flag requires an identifier"));
}
} else {
return quick_error(format!("Malformed arguement"));
}
} else {
return quick_error(format!("Incomplete/badly malformed arguement \
or too many arguements"));
}
};
if let Ok(item) = syn::parse::<ItemImpl>(input.clone()) {
flag_impl(item, priority, flag)
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
flag_function(item, priority, flag)
} else {
quick_error(format!("I can't parse this yet"))
}
}
fn flag_function(mut item: ItemFn, priority: u32, flag: String) -> TokenStream {
attr_add(&mut item.attrs,
format!("__override_priority_{}_flag_{}_func_{}",
priority, flag, item.sig.ident));
attr_inline(&mut item.attrs);
item.sig.ident = Ident::new(&format!("__override_flagext_{}_{}",
flag, item.sig.ident),
Span::call_site());
return TokenStream::from(quote! {
#item
});
}
fn flag_impl(mut impl_block: ItemImpl, priority: u32, flag: String) -> TokenStream {
let self_type = match impl_block.self_ty.as_ref() {
Path(path) => path,
_ => return quick_error(format!("Could not get Path for impl (should never see this)")),
}.path.segments[0].ident.to_string();
for item in &mut impl_block.items {
match item {
Method(method) => {
attr_add(&mut method.attrs,
format!("__override_priority_{}_flag_{}_method_{}_{}",
priority, flag, self_type, method.sig.ident));
attr_inline(&mut method.attrs);
method.sig.ident = Ident::new(&format!("__override_flagext_{}_{}",
flag, method.sig.ident),
Span::call_site());
},
Const(_constant) => {
return quick_error(format!("flagging a constant currently envokes undefined \
behavior"));
},
item => return syn::Error::new(
item.span(),
format!("I can't overload anything other than methods/consts in an impl block yet"))
.to_compile_error().into(),
}
}
TokenStream::from(quote! {
#impl_block
})
}