use proc_macro::TokenStream; use quote::{quote, format_ident}; use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quote}; /// Procedural macro that generates a Rhai client function for a Rust function. /// /// When applied to a Rust function, it generates a corresponding function with a '_rhai_client' suffix /// that calls the original function through the Rhai engine. /// /// # Example /// /// ```rust /// #[rhai] /// fn hello(name: String) -> String { /// format!("Hello, {}!", name) /// } /// ``` /// /// This will generate: /// /// ```rust /// fn hello_rhai_client(engine: &rhai::Engine, name: String) -> String { /// let script = format!("hello(\"{}\")", name.replace("\"", "\\\"")); /// engine.eval::(&script).unwrap_or_else(|err| { /// eprintln!("Rhai script error: {}", err); /// String::new() /// }) /// } /// ``` /// /// Note: The macro handles type conversions between Rust and Rhai types, /// particularly for integer types (Rhai uses i64 internally). #[proc_macro_attribute] pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the input function let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { if let Pat::Ident(pat_ident) = &**pat { let param_name = &pat_ident.ident; param_names.push(param_name.clone()); param_types.push(ty.clone()); param_declarations.push(quote! { #param_name: #ty }); } } _ => { // Skip self parameters continue; } } } // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; // Generate parameter formatting for the Rhai script let param_format_strings = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { let type_str = quote! { #ty }.to_string(); // Handle different parameter types if type_str.contains("String") { quote! { format!("\"{}\"" , #name.replace("\"", "\\\"")) } } else if type_str.contains("bool") { quote! { format!("{}", #name) } } else if type_str.contains("i32") || type_str.contains("u32") { // Convert smaller integer types to i64 for Rhai quote! { format!("{}", #name as i64) } } else if type_str.contains("i64") || type_str.contains("u64") || type_str.contains("f32") || type_str.contains("f64") { // Other numeric types quote! { format!("{}", #name) } } else { // For complex types, just pass the variable name // The Rhai engine will handle the conversion quote! { #name.to_string() } } }); // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); // Generate the client function with appropriate type conversions let client_fn = if return_type_str.contains("i32") || return_type_str.contains("u32") { // For integer return types that need conversion quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_strings),*].join(", ") ); match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { eprintln!("Rhai script error: {}", err); 0 as #return_type // Use 0 as default for numeric types } } } } } else if return_type_str.contains("String") { // For String return type quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_strings),*].join(", ") ); match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { eprintln!("Rhai script error: {}", err); String::new() // Empty string as default } } } } } else if return_type_str.contains("bool") { // For boolean return type quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_strings),*].join(", ") ); match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { eprintln!("Rhai script error: {}", err); false // False as default } } } } } else { // For complex types or other types quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_strings),*].join(", ") ); match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { eprintln!("Rhai script error: {}", err); panic!("Failed to evaluate Rhai script: {}", err) // Panic for complex types } } } } }; // Combine the original function and the generated client function let output = quote! { #input_fn #client_fn }; output.into() } /// A more advanced version of the rhai macro that handles different parameter types better. /// /// This version properly escapes strings and handles different parameter types more accurately. /// It's recommended to use this version for more complex functions. #[proc_macro_attribute] pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the input function let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { if let Pat::Ident(pat_ident) = &**pat { let param_name = &pat_ident.ident; param_names.push(param_name.clone()); param_types.push(ty.clone()); param_declarations.push(quote! { #param_name: #ty }); } } _ => { // Skip self parameters continue; } } } // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; // Generate parameter formatting for the Rhai script let param_format_expressions = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { let type_str = quote! { #ty }.to_string(); // Handle different parameter types if type_str.contains("String") { quote! { format!("\"{}\"", #name.replace("\"", "\\\"")) } } else if type_str.contains("bool") { quote! { format!("{}", #name) } } else if type_str.contains("i32") || type_str.contains("u32") { // Convert smaller integer types to i64 for Rhai quote! { format!("{}", #name as i64) } } else if type_str.contains("i") || type_str.contains("u") || type_str.contains("f") { // Other numeric types quote! { format!("{}", #name) } } else { // Default for other types quote! { format!("{:?}", #name) } } }).collect::>(); // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); let needs_return_conversion = return_type_str.contains("i32") || return_type_str.contains("u32"); // Generate the client function with appropriate type conversions let client_fn = if needs_return_conversion { quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { eprintln!("Rhai script error: {}", err); #return_type::default() } } } } } else { quote! { fn #client_fn_name(engine: &rhai::Engine, #(#param_declarations),*) -> #return_type { let script = format!( "{}({})", #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { eprintln!("Rhai script error: {}", err); #return_type::default() } } } } }; // Combine the original function and the generated client function let output = quote! { #input_fn #client_fn }; output.into() } /// Macro that generates a module with Rhai client functions for all functions in scope. /// /// This macro should be used at the module level to generate Rhai client functions for all /// functions marked with the #[rhai] attribute. #[proc_macro] pub fn generate_rhai_module(_item: TokenStream) -> TokenStream { // This would be a more complex implementation that would need to // scan the module for functions marked with #[rhai] and generate // client functions for all of them. // // For simplicity, we'll just return a placeholder implementation let output = quote! { /// Register all functions marked with #[rhai] in this module with the Rhai engine. /// /// This function handles type conversions between Rust and Rhai types automatically. /// For example, it converts between Rust's i32 and Rhai's i64 types. pub fn register_rhai_functions(engine: &mut rhai::Engine) { // This would be generated based on the functions in the module println!("Registering Rhai functions..."); // In a real implementation, this would iterate through all functions // marked with #[rhai] and register them with the engine. } }; output.into() }