add rhai client macros
This commit is contained in:
parent
cdbfc80ee4
commit
bd4770b99b
14
rhai_client_macros/Cargo.toml
Normal file
14
rhai_client_macros/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rhai_client_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Procedural macros for generating Rhai client functions from Rust functions"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||||
|
rhai = "1.21.0"
|
348
rhai_client_macros/src/lib.rs
Normal file
348
rhai_client_macros/src/lib.rs
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
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::<String>(&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::<i64>(&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::<Vec<_>>();
|
||||||
|
|
||||||
|
// 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::<i64>(&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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user