use crate::args::{AxinArg, FunctionSpec};
use proc_macro2::Span;
use quote::quote;
use syn::{parse_quote, FnArg, Ident, ItemFn, Pat, Stmt, Token};
pub fn generate_enhanced_function(
input_fn: ItemFn,
prologue_stmts: Vec<Stmt>,
decorator_fn: Option<FunctionSpec>,
on_enter_fn: Option<FunctionSpec>,
on_exit_fn: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let original_fn = input_fn.clone();
let fn_vis = &original_fn.vis;
let fn_sig = &original_fn.sig;
let fn_inputs = &fn_sig.inputs;
let fn_output = &fn_sig.output;
let original_block = original_fn.block;
let args: Vec<_> = fn_inputs
.iter()
.filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
Some(&pat_ident.ident)
} else {
None
}
} else {
None
}
})
.collect();
let mut inner_stmts = Vec::new();
inner_stmts.extend(prologue_stmts);
inner_stmts.extend(original_block.stmts);
let mut final_stmts = Vec::new();
if let Some(on_enter) = &on_enter_fn {
let call_expr = generate_function_call(on_enter);
final_stmts.push(parse_quote! { #call_expr; });
}
final_stmts.push(parse_quote! {
let original_fn = |#fn_inputs| #fn_output {
#(#inner_stmts)*
};
});
if let Some(decorator) = &decorator_fn {
let decorator_call = generate_decorator_call(decorator, &args);
final_stmts.push(parse_quote! {
let __result = #decorator_call;
});
} else {
final_stmts.push(parse_quote! {
let __result = original_fn(#(#args),*);
});
}
if let Some(on_exit) = &on_exit_fn {
let call_expr = generate_function_call(on_exit);
final_stmts.push(parse_quote! { #call_expr; });
}
final_stmts.push(parse_quote! {
return __result;
});
let final_block = syn::Block {
brace_token: original_block.brace_token,
stmts: final_stmts,
};
quote! {
#fn_vis #fn_sig #final_block
}
}
fn generate_function_call(func_spec: &FunctionSpec) -> proc_macro2::TokenStream {
match func_spec {
FunctionSpec::Simple(path) => {
quote! { #path() }
}
FunctionSpec::WithArgs(path, args) => {
quote! { #path(#args) }
}
}
}
fn generate_decorator_call(
func_spec: &FunctionSpec,
orig_args: &[&Ident],
) -> proc_macro2::TokenStream {
match func_spec {
FunctionSpec::Simple(path) => {
if orig_args.is_empty() {
quote! { #path(original_fn) }
} else {
quote! { #path(original_fn, #(#orig_args),*) }
}
}
FunctionSpec::WithArgs(path, args) => {
if orig_args.is_empty() {
quote! { (#path(#args))(original_fn) }
} else {
quote! { (#path(#args))(original_fn, #(#orig_args),*) }
}
}
}
}
pub fn process_attribute_args(
attribute_args: crate::args::AxinArgs,
) -> (
Vec<Stmt>,
Option<FunctionSpec>,
Option<FunctionSpec>,
Option<FunctionSpec>,
) {
let mut prologue_stmts: Vec<Stmt> = Vec::new();
let mut decorator_fn: Option<FunctionSpec> = None;
let mut on_enter_fn: Option<FunctionSpec> = None;
let mut on_exit_fn: Option<FunctionSpec> = None;
for arg in attribute_args.args.into_iter() {
match arg {
AxinArg::Prologue { stmts } => {
for stmt in stmts {
if let syn::Stmt::Expr(expr, None) = stmt {
prologue_stmts
.push(syn::Stmt::Expr(expr, Some(Token))));
} else {
prologue_stmts.push(stmt);
}
}
}
AxinArg::OnEnter { func } => {
on_enter_fn = Some(func);
}
AxinArg::OnExit { func } => {
on_exit_fn = Some(func);
}
AxinArg::Decorator { func } => {
decorator_fn = Some(func);
}
}
}
(prologue_stmts, decorator_fn, on_enter_fn, on_exit_fn)
}