use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, ItemFn, Meta, NestedMeta};
pub fn builtin_function_impl(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
let input_fn = parse_macro_input!(input as ItemFn);
let mut name = None;
let mut category = None;
let mut description = None;
for arg in args {
match arg {
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("name") => {
if let syn::Lit::Str(lit_str) = nv.lit {
name = Some(lit_str.value());
}
}
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("category") => {
if let syn::Lit::Str(lit_str) = nv.lit {
category = Some(lit_str.value());
}
}
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("description") => {
if let syn::Lit::Str(lit_str) = nv.lit {
description = Some(lit_str.value());
}
}
_ => {}
}
}
let name = name.expect("builtin_function macro requires 'name' attribute");
let category = category.unwrap_or_else(|| "User".to_string());
let description = description.unwrap_or_else(|| "User-defined function".to_string());
let fn_name = &input_fn.sig.ident;
let wrapper_ident = quote::format_ident!("__rt_builtin_wrap_{}", fn_name);
let is_async = input_fn.sig.asyncness.is_some();
let call_expr = if is_async {
quote! { #fn_name(args).await? }
} else {
quote! { #fn_name(args)? }
};
let fn_vis = &input_fn.vis;
let fn_block = &input_fn.block;
let fn_inputs = &input_fn.sig.inputs;
let fn_output = &input_fn.sig.output;
let category_variant = match category.as_str() {
"Mathematics" => quote! { FunctionCategory::Mathematics },
"LinearAlgebra" => quote! { FunctionCategory::LinearAlgebra },
"Statistics" => quote! { FunctionCategory::Statistics },
"SignalProcessing" => quote! { FunctionCategory::SignalProcessing },
"ImageProcessing" => quote! { FunctionCategory::ImageProcessing },
"ControlSystems" => quote! { FunctionCategory::ControlSystems },
"Optimization" => quote! { FunctionCategory::Optimization },
"Plotting" => quote! { FunctionCategory::Plotting },
"FileIO" => quote! { FunctionCategory::FileIO },
"Strings" => quote! { FunctionCategory::Strings },
"DataAnalysis" => quote! { FunctionCategory::DataAnalysis },
"Numerical" => quote! { FunctionCategory::Numerical },
"System" => quote! { FunctionCategory::System },
_ => quote! { FunctionCategory::User },
};
let _static_name = format!("__{}_BUILTIN", name.to_uppercase().replace("-", "_"));
let param_info = extract_parameter_info(&parsed.sig);
let return_type_info = extract_return_type_info(&parsed.sig);
let expanded = quote! {
#fn_vis fn #fn_name(#fn_inputs) #fn_output #fn_block
fn #wrapper_ident(args: &[runmat_builtins::Value]) -> runmat_builtins::BuiltinFuture {
let args = args.to_vec();
Box::pin(async move { #call_expr })
}
runmat_builtins::inventory::submit! {
#![crate = runmat_builtins]
runmat_builtins::BuiltinFunction::new(
#name,
#description,
stringify!(#category_variant),
"",
"",
#param_info,
#return_type_info,
None,
#wrapper_ident,
&[],
false,
false,
)
}
};
TokenStream::from(expanded)
}
fn extract_parameter_info(sig: &syn::Signature) -> proc_macro2::TokenStream {
let mut params = Vec::new();
for input in &sig.inputs {
match input {
syn::FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
let param_name = pat_ident.ident.to_string();
let param_type = infer_parameter_type(&pat_type.ty);
params.push(quote! {
runmat_builtins::ParameterInfo {
name: #param_name.to_string(),
param_type: #param_type,
description: String::new(),
required: true,
}
});
}
}
syn::FnArg::Receiver(_) => {
}
}
}
quote! { vec![#(#params),*] }
}
fn extract_return_type_info(sig: &syn::Signature) -> proc_macro2::TokenStream {
match &sig.output {
syn::ReturnType::Default => quote! { runmat_builtins::ParameterType::Any },
syn::ReturnType::Type(_, ty) => infer_parameter_type(ty),
}
}
fn infer_parameter_type(ty: &syn::Type) -> proc_macro2::TokenStream {
let type_str = quote! { #ty }.to_string();
if type_str.contains("f64") || type_str.contains("f32") {
quote! { runmat_builtins::ParameterType::Number }
} else if type_str.contains("i64") || type_str.contains("i32") || type_str.contains("usize") {
quote! { runmat_builtins::ParameterType::Integer }
} else if type_str.contains("Matrix") {
quote! { runmat_builtins::ParameterType::Matrix }
} else if type_str.contains("Value") {
quote! { runmat_builtins::ParameterType::Any }
} else if type_str.contains("String") || type_str.contains("str") {
quote! { runmat_builtins::ParameterType::String }
} else if type_str.contains("bool") {
quote! { runmat_builtins::ParameterType::Boolean }
} else {
quote! { runmat_builtins::ParameterType::Any }
}
}