more efforts to automate rhai bindings

This commit is contained in:
timurgordon
2025-05-13 02:00:35 +03:00
parent 16ad4f5743
commit ec4769a6b0
14 changed files with 3174 additions and 52 deletions

View File

@@ -208,3 +208,601 @@ macro_rules! wrap_for_rhai {
$func
};
}
/// Macro to wrap a Rust function that returns Option<T> for Rhai.
/// It converts Some(T) to Dynamic::from(T) and None to Dynamic::UNIT.
#[macro_export]
macro_rules! wrap_option_return {
// Matches fn_name(arg1_type, arg2_type) -> Option<ReturnType>
// Example: get_user_by_id(OurDB, INT) -> Option<User>
// Macro call: wrap_option_return!(get_user_by_id, OurDB, INT => User)
// Generated closure: |db: OurDB, id: INT| -> rhai::Dynamic { ... }
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $ReturnType:ident) => {
|arg1: $Arg1Type, arg2: $Arg2Type| -> rhai::Dynamic {
match $func(arg1, arg2) {
Some(value) => {
// Ensure the value is converted into a Dynamic.
// If $ReturnType is already a CustomType, this should work directly.
rhai::Dynamic::from(value)
}
None => rhai::Dynamic::UNIT,
}
}
};
// Add more arms here if functions with different numbers/types of arguments returning Option<T>
// are needed.
// For example, for a function with one argument:
// ($func:ident, $Arg1Type:ty => $ReturnType:ident) => { ... };
}
/// Macro to wrap a Rust function that returns Vec<T> for Rhai.
/// It converts the Vec into a rhai::Array.
#[macro_export]
macro_rules! wrap_vec_return {
// For functions like fn(Arg1) -> Vec<InnerType>
// Example: get_all_users(db: OurDB) -> Vec<User>
// Macro call: wrap_vec_return!(get_all_users, OurDB => User)
// Generated closure: |db: OurDB| -> rhai::Array { ... }
($func:ident, $Arg1Type:ty => $InnerVecType:ty) => {
|arg1_val: $Arg1Type| -> rhai::Array {
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val);
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
}
};
// For functions like fn(Arg1, Arg2) -> Vec<InnerType>
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
|arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Array {
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val, arg2_val);
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
}
};
// For functions like fn() -> Vec<InnerType>
($func:ident, () => $InnerVecType:ty) => {
|| -> rhai::Array {
let result_vec: std::vec::Vec<$InnerVecType> = $func();
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
}
};
}
#[macro_export]
macro_rules! wrap_option_vec_return {
// Case: fn_name(Arg1Type, Arg2Type) -> Option<Vec<InnerType>>
// Generates a closure: |arg1_val: Arg1Type, arg2_val: Arg2Type| -> rhai::Dynamic
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
move |arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Dynamic {
match $func(arg1_val, arg2_val) { // Call the original Rust function
Some(vec_inner) => { // vec_inner is Vec<$InnerVecType>
let rhai_array = vec_inner.into_iter()
.map(rhai::Dynamic::from) // Each $InnerVecType must be convertible to Dynamic
.collect::<rhai::Array>();
rhai::Dynamic::from(rhai_array)
}
None => rhai::Dynamic::UNIT,
}
}
};
// TODO: Add arms for different numbers of arguments if needed, e.g.:
// ($func:ident, $Arg1Type:ty => $InnerVecType:ty) => { ... }
// ($func:ident => $InnerVecType:ty) => { ... }
}
/// Wraps a Rust function that returns `Result<Option<T>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_option_return_result {
// Case: Function with DB connection and 1 additional argument
(
$func:path, // Path to the actual Rust function
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
$ErrorType:ty // Error type from actual func
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance, arg1_val) { // Call the actual function
Ok(Some(value)) => {
// Assumes ReturnType has a .to_rhai_map() method.
Ok(rhai::Dynamic::from(value.to_rhai_map()))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Function with DB connection and 0 additional arguments
(
$func:path,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance) { // Call the actual function
Ok(Some(value)) => {
Ok(rhai::Dynamic::from(value.to_rhai_map()))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
/// Wraps a Rust function that returns `Result<Vec<T>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_vec_return_result {
// Case: Function with DB connection and 1 additional argument
(
$func:path, // Path to the actual Rust function
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
$ErrorType:ty // Error type from actual func
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance, arg1_val) { // Call the actual function
Ok(vec_of_values) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Function with DB connection and 0 additional arguments (e.g., get_all)
(
$func:path,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance) { // Call the actual function
Ok(vec_of_values) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
/// Wraps a Rust function that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_option_vec_return_result {
// Case: Function with DB connection and 1 additional argument
(
$func:path, // Path to the actual Rust function
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
$ErrorType:ty // Error type from actual func
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance, arg1_val) { // Call the actual function
Ok(Some(vec_of_values)) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Function with DB connection and 0 additional arguments
(
$func:path,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match $func(db_conn_instance) { // Call the actual function
Ok(Some(vec_of_values)) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
// --- Macros for methods returning Result<_, _> for fallible operations --- //
/// Wraps a Rust method that returns `Result<Option<T>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_option_method_result {
// Case: Method with DB connection (self) and 1 additional argument
(
$method_name:ident, // Name of the method on the Collection
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type (e.g. &Collection), Arg types
$ErrorType:ty // Error type from method
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name(arg1_val) { // Call the method
Ok(Some(value)) => {
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Method with DB connection (self) and 0 additional arguments
(
$method_name:ident,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name() { // Call the method
Ok(Some(value)) => {
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
/// Wraps a Rust method that returns `Result<Vec<T>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_vec_method_result {
// Case: Method with DB connection (self) and 1 additional argument
(
$method_name:ident, // Name of the method on the Collection
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
$ErrorType:ty // Error type from method
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name(arg1_val) { // Call the method
Ok(vec_of_values) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Method with DB connection (self) and 0 additional arguments (e.g., get_all)
(
$method_name:ident,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name() { // Call the method
Ok(vec_of_values) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
/// Wraps a Rust method that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
/// Assumes `ErrorType` implements `std::fmt::Display`.
#[macro_export]
macro_rules! wrap_option_vec_method_result {
// Case: Method with DB connection (self) and 1 additional argument
(
$method_name:ident, // Name of the method on the Collection
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
$ErrorType:ty // Error type from method
) => {
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name(arg1_val) { // Call the method
Ok(Some(vec_of_values)) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(), // ErrorType: Display
rhai::Position::NONE,
)))
}
}
}
};
// Case: Method with DB connection (self) and 0 additional arguments
(
$method_name:ident,
$DbConnType:ty => $ReturnType:ty,
$ErrorType:ty
) => {
move |db_conn_instance: $DbConnType|
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
match db_conn_instance.$method_name() { // Call the method
Ok(Some(vec_of_values)) => {
let rhai_array = vec_of_values
.into_iter()
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
.collect::<rhai::Array>();
Ok(rhai::Dynamic::from(rhai_array))
}
Ok(None) => Ok(rhai::Dynamic::UNIT),
Err(err) => {
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Function Error: {}", err).into(),
rhai::Position::NONE,
)))
}
}
}
};
// TODO: Add variants for more arguments as needed
}
// TODO: Consider merging wrap_option_return, wrap_vec_return, and wrap_option_vec_return
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
// For now, separate macros are clear for distinct return type patterns.
/// A macro that creates a Rust function that calls a Rhai engine to execute a Rhai function which wraps an underlying Rust function.
/// This creates a full circle of Rust → Rhai → Rust function calls.
///
/// # Example Usage
/// ```rust,ignore
/// // Define a Rust function
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// // Register it with Rhai (assuming engine is already created)
/// engine.register_fn("add_rhai", add);
///
/// // Create a wrapper function that takes an engine reference and calls the Rhai function
/// rust_rhai_wrapper!(add_via_rhai, "add_rhai", (i32, i32) -> i32);
///
/// // Now you can call add_via_rhai which will call the Rhai function add_rhai which calls the Rust function add
/// let result = add_via_rhai(&mut engine, 5, 3); // result = 8
/// ```
#[macro_export]
macro_rules! rust_rhai_wrapper {
// Basic case: function with no arguments
($func_name:ident, $rhai_func_name:expr, () -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine) -> $return_type {
let result = engine.eval::<$return_type>(
&format!("{}()", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with one argument
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> $return_type {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
let result = engine.eval_with_scope::<$return_type>(
&mut scope,
&format!("{}(arg1)", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with two arguments
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> $return_type {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
scope.push("arg2", arg2);
let result = engine.eval_with_scope::<$return_type>(
&mut scope,
&format!("{}(arg1, arg2)", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with three arguments
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty) -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type) -> $return_type {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
scope.push("arg2", arg2);
scope.push("arg3", arg3);
let result = engine.eval_with_scope::<$return_type>(
&mut scope,
&format!("{}(arg1, arg2, arg3)", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with four arguments
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty) -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type) -> $return_type {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
scope.push("arg2", arg2);
scope.push("arg3", arg3);
scope.push("arg4", arg4);
let result = engine.eval_with_scope::<$return_type>(
&mut scope,
&format!("{}(arg1, arg2, arg3, arg4)", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with five arguments
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty, $arg5_type:ty) -> $return_type:ty) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type, arg5: $arg5_type) -> $return_type {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
scope.push("arg2", arg2);
scope.push("arg3", arg3);
scope.push("arg4", arg4);
scope.push("arg5", arg5);
let result = engine.eval_with_scope::<$return_type>(
&mut scope,
&format!("{}(arg1, arg2, arg3, arg4, arg5)", $rhai_func_name)
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
result
}
};
// Function with a Result return type and no arguments
($func_name:ident, $rhai_func_name:expr, () -> Result<$ok_type:ty, $err_type:ty>) => {
pub fn $func_name(engine: &mut rhai::Engine) -> Result<$ok_type, $err_type> {
match engine.eval::<$ok_type>(&format!("{}()", $rhai_func_name)) {
Ok(result) => Ok(result),
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
}
}
};
// Function with a Result return type and one argument
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> Result<$ok_type, $err_type> {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1)", $rhai_func_name)) {
Ok(result) => Ok(result),
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
}
}
};
// Function with a Result return type and two arguments
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> Result<$ok_type, $err_type> {
// Create a scope to pass arguments
let mut scope = rhai::Scope::new();
scope.push("arg1", arg1);
scope.push("arg2", arg2);
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1, arg2)", $rhai_func_name)) {
Ok(result) => Ok(result),
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
}
}
};
}
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
// For now, separate macros are clear for distinct return type patterns.