use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{ItemFn, FnArg, punctuated::Punctuated, token::Comma, Attribute};
#[derive(Clone, Copy)]
enum When {
Call,
Return,
}
#[proc_macro_attribute]
pub fn param(args: TokenStream, func: TokenStream) -> TokenStream {
parse(args, func, When::Call)
}
#[proc_macro_attribute]
pub fn debug(args: TokenStream, func: TokenStream) -> TokenStream {
parse(args, func, When::Return)
}
fn parse(_: TokenStream, func: TokenStream, when: When) -> TokenStream {
let func = syn::parse_macro_input!(func as ItemFn);
let func_attrs = func.attrs; let attrs = parse_attrs(func_attrs);
let func_vis = &func.vis; let func_block = &func.block; let sig = &func.sig;
let func_constness = &sig.constness; let func_async = &sig.asyncness; let func_abi = &sig.abi; let func_name = &sig.ident; let func_generics = &sig.generics; let func_where_clause = &func_generics.where_clause; let func_inputs = &sig.inputs; let func_output = &sig.output; let params = parse_params(func_inputs);
let (format, values) = get_log_format_values(&func_name.to_string(), params);
let format = match when {
When::Call => format!("call {format}"),
When::Return => format!("called {format}"),
};
let log = match (when, func_output) {
(When::Call, _) => quote! {
macro_log::d!(#format, #values);
},
(_, syn::ReturnType::Default) => quote! {
macro_log::d!("{}", call);
},
(_, syn::ReturnType::Type(_, _)) => quote! {
macro_log::d!("{} => {:?}", call, return_value);
},
};
let caller = match when {
When::Call => quote! {
#attrs
#func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
#log
#func_block
}
},
When::Return => quote! {
#attrs
#func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
let call = format!(#format, #values);
let return_value = #func_block;
#log
return_value
}
},
};
caller.into()
}
fn parse_attrs(attrs: Vec<Attribute>) -> proc_macro2::TokenStream {
attrs
.iter()
.map(|attr| attr.to_token_stream().to_string())
.collect::<Vec<String>>().join(" ")
.parse().unwrap()
}
fn parse_params(func_inputs: &Punctuated<FnArg, Comma>) -> Vec<String> {
let mut args = vec![];
for arg in func_inputs.into_iter() {
match arg {
FnArg::Receiver(arg) => {
let tokens = quote!(#arg).to_string();
let (name, _vartype) = tokens.split_once(" : ").unwrap();
args.push(name.into());
}
FnArg::Typed(_) => { let tokens = quote!(#arg).to_string();
let (name, _vartype) = tokens.split_once(" : ").unwrap();
args.push(name.into());
}
}
}
args
}
fn get_log_format_values(func_name: &str, args: Vec<String>) -> (String, proc_macro2::TokenStream) {
let format_args = args.iter()
.map(|it| if it.as_str() != "_" {
format!("{it} = {{:?}}")
} else {
"_ = ?".to_string()
})
.collect::<Vec<String>>().join(", ");
let format = format!("fn {func_name}({format_args})");
let values = args.iter()
.filter(|it| it.as_str() != "_")
.map(|it| format!("{it}")).collect::<Vec<String>>().join(",");
let values = values.parse::<proc_macro2::TokenStream>().unwrap();
(format, values)
}