more efforts to automate rhai bindings
This commit is contained in:
54
rhai_autobind_macros/Cargo.lock
generated
Normal file
54
rhai_autobind_macros/Cargo.lock
generated
Normal file
@@ -0,0 +1,54 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
13
rhai_autobind_macros/Cargo.toml
Normal file
13
rhai_autobind_macros/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.4"
|
@@ -0,0 +1,33 @@
|
||||
// calculator.rhai
|
||||
// This script demonstrates using the Calculator struct from Rust in Rhai
|
||||
|
||||
// Create a new calculator
|
||||
let calc = new_calculator();
|
||||
println("Created a new calculator with value: " + calc.value);
|
||||
|
||||
// Perform some calculations
|
||||
calc.add(5);
|
||||
println("After adding 5: " + calc.value);
|
||||
|
||||
calc.multiply(2);
|
||||
println("After multiplying by 2: " + calc.value);
|
||||
|
||||
calc.subtract(3);
|
||||
println("After subtracting 3: " + calc.value);
|
||||
|
||||
calc.divide(2);
|
||||
println("After dividing by 2: " + calc.value);
|
||||
|
||||
// Set value directly
|
||||
calc.value = 100;
|
||||
println("After setting value to 100: " + calc.value);
|
||||
|
||||
// Clear the calculator
|
||||
calc.clear();
|
||||
println("After clearing: " + calc.value);
|
||||
|
||||
// Chain operations
|
||||
let result = calc.add(10).multiply(2).subtract(5).divide(3);
|
||||
println("Result of chained operations: " + result);
|
||||
|
||||
println("Final calculator value: " + calc.value);
|
90
rhai_autobind_macros/examples/rhai_autobind_example/main.rs
Normal file
90
rhai_autobind_macros/examples/rhai_autobind_example/main.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai_autobind_macros::rhai_autobind;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
// Define a simple Calculator struct with the rhai_autobind attribute
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, rhai::CustomType)]
|
||||
#[rhai_autobind]
|
||||
pub struct Calculator {
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
// Constructor
|
||||
pub fn new() -> Self {
|
||||
Calculator { value: 0.0 }
|
||||
}
|
||||
|
||||
// Add method
|
||||
pub fn add(&mut self, x: f64) -> f64 {
|
||||
self.value += x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Subtract method
|
||||
pub fn subtract(&mut self, x: f64) -> f64 {
|
||||
self.value -= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Multiply method
|
||||
pub fn multiply(&mut self, x: f64) -> f64 {
|
||||
self.value *= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Divide method
|
||||
pub fn divide(&mut self, x: f64) -> f64 {
|
||||
if x == 0.0 {
|
||||
println!("Error: Division by zero!");
|
||||
return self.value;
|
||||
}
|
||||
self.value /= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Clear method
|
||||
pub fn clear(&mut self) -> f64 {
|
||||
self.value = 0.0;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Get value method
|
||||
pub fn get_value(&self) -> f64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Rhai Calculator Example");
|
||||
println!("======================");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the Calculator type with the engine using the generated function
|
||||
Calculator::register_rhai_bindings_for_calculator(&mut engine);
|
||||
|
||||
// Register print function for output
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
|
||||
// Create a calculator instance to demonstrate it works
|
||||
let calc = Calculator::new();
|
||||
println!("Initial value: {}", calc.value);
|
||||
|
||||
// Load and run the Rhai script
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("rhai_autobind_example")
|
||||
.join("calculator.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
187
rhai_autobind_macros/src/lib.rs
Normal file
187
rhai_autobind_macros/src/lib.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, format_ident};
|
||||
use syn::{parse_macro_input, DeriveInput, Ident, Type, Fields, Visibility, ItemStruct, LitStr};
|
||||
|
||||
/// A procedural macro to automatically generate Rhai bindings for a struct.
|
||||
///
|
||||
/// This macro will generate an `impl` block for the annotated struct containing
|
||||
/// a static method, typically named `register_rhai_bindings_for_<struct_name_lowercase>`,
|
||||
/// which registers the struct, its fields (as getters), its methods, and common
|
||||
/// database operations with a Rhai `Engine`.
|
||||
///
|
||||
/// # Example Usage
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Assuming MyDb is your database connection type
|
||||
/// // and it's wrapped in an Arc for sharing with Rhai.
|
||||
/// use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
/// use rhai_autobind_macros::rhai_model_export;
|
||||
///
|
||||
/// // Define your database type (placeholder)
|
||||
/// struct MyDb;
|
||||
/// impl MyDb {
|
||||
/// fn new() -> Self { MyDb }
|
||||
/// }
|
||||
///
|
||||
/// #[rhai_model_export(db_type = "Arc<MyDb>")]
|
||||
/// pub struct User {
|
||||
/// pub id: i64,
|
||||
/// pub name: String,
|
||||
/// }
|
||||
///
|
||||
/// impl User {
|
||||
/// // A constructor or builder Rhai can use
|
||||
/// pub fn new(id: i64, name: String) -> Self {
|
||||
/// Self { id, name }
|
||||
/// }
|
||||
///
|
||||
/// // An example method
|
||||
/// pub fn greet(&self) -> String {
|
||||
/// format!("Hello, {}!", self.name)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let db_instance = Arc::new(MyDb::new());
|
||||
///
|
||||
/// // The generated function is called here
|
||||
/// User::register_rhai_bindings_for_user(&mut engine, db_instance);
|
||||
///
|
||||
/// // Now you can use User in Rhai scripts
|
||||
/// // let user = User::new(1, "Test User");
|
||||
/// // print(user.name);
|
||||
/// // print(user.greet());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Macro Attributes
|
||||
///
|
||||
/// - `db_type = "path::to::YourDbType"`: **Required**. Specifies the fully qualified
|
||||
/// Rust type of your database connection/handler that will be passed to the
|
||||
/// generated registration function and used for database operations.
|
||||
/// The type should be enclosed in string quotes, e.g., `"Arc<crate::db::MyDatabase>"`.
|
||||
///
|
||||
/// - `collection_name = "your_collection_name"`: **Optional**. Specifies the name of the
|
||||
/// database collection associated with this model. If not provided, it defaults
|
||||
/// to the struct name converted to snake_case and pluralized (e.g., `User` -> `"users"`).
|
||||
/// Enclose in string quotes, e.g., `"user_profiles"`.
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroArgs {
|
||||
db_type: syn::Type,
|
||||
collection_name: Option<String>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for MacroArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut db_type_str: Option<LitStr> = None;
|
||||
let mut collection_name_str: Option<LitStr> = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: Ident = input.parse()?;
|
||||
let _eq_token: syn::token::Eq = input.parse()?;
|
||||
if ident == "db_type" {
|
||||
db_type_str = Some(input.parse()?);
|
||||
}
|
||||
else if ident == "collection_name" {
|
||||
collection_name_str = Some(input.parse()?);
|
||||
}
|
||||
else {
|
||||
return Err(syn::Error::new(ident.span(), "Unknown attribute argument"));
|
||||
}
|
||||
if !input.is_empty() {
|
||||
let _comma: syn::token::Comma = input.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
let db_type_str = db_type_str.ok_or_else(|| syn::Error::new(input.span(), "Missing required attribute `db_type`"))?;
|
||||
let db_type: syn::Type = syn::parse_str(&db_type_str.value())?;
|
||||
let collection_name = collection_name_str.map(|s| s.value());
|
||||
|
||||
Ok(MacroArgs { db_type, collection_name })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn rhai_model_export(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let macro_args = parse_macro_input!(attr as MacroArgs);
|
||||
let input_struct = parse_macro_input!(item as ItemStruct);
|
||||
|
||||
let struct_name = &input_struct.ident;
|
||||
let struct_name_str = struct_name.to_string();
|
||||
let struct_name_lowercase = struct_name_str.to_lowercase();
|
||||
|
||||
let db_type = macro_args.db_type;
|
||||
let collection_name = macro_args.collection_name.unwrap_or_else(|| {
|
||||
// Basic pluralization, could use `heck` or `inflector` for better results
|
||||
if struct_name_lowercase.ends_with('y') {
|
||||
format!("{}ies", &struct_name_lowercase[..struct_name_lowercase.len()-1])
|
||||
} else if struct_name_lowercase.ends_with('s') {
|
||||
format!("{}es", struct_name_lowercase)
|
||||
} else {
|
||||
format!("{}s", struct_name_lowercase)
|
||||
}
|
||||
});
|
||||
|
||||
let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_lowercase);
|
||||
|
||||
// Placeholder for generated getters and method registrations
|
||||
let mut field_getters = Vec::new();
|
||||
if let Fields::Named(fields) = &input_struct.fields {
|
||||
for field in fields.named.iter() {
|
||||
if let (Some(field_name), Visibility::Public(_)) = (&field.ident, &field.vis) {
|
||||
let field_name_str = field_name.to_string();
|
||||
let getter_fn = quote! {
|
||||
engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
|
||||
// TODO: Handle non-cloneable types or provide options
|
||||
};
|
||||
field_getters.push(getter_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Parse methods from the struct's impl blocks (this is complex)
|
||||
// TODO: Generate wrappers for DB operations using rhai_wrapper macros
|
||||
|
||||
let output = quote! {
|
||||
#input_struct // Re-emit the original struct definition
|
||||
|
||||
impl #struct_name {
|
||||
pub fn #generated_registration_fn_name(
|
||||
engine: &mut rhai::Engine,
|
||||
db: #db_type
|
||||
) {
|
||||
// Bring rhai_wrapper into scope for generated DB functions later
|
||||
// Users might need to ensure rhai_wrapper is a dependency of their crate.
|
||||
// use rhai_wrapper::*;
|
||||
|
||||
engine.build_type::<#struct_name>();
|
||||
|
||||
// Register getters
|
||||
#(#field_getters)*
|
||||
|
||||
// Placeholder for constructor registration
|
||||
// Example: engine.register_fn("new_user", User::new);
|
||||
|
||||
// Placeholder for method registration
|
||||
// Example: engine.register_fn("greet", User::greet);
|
||||
|
||||
// Placeholder for DB function registrations
|
||||
// e.g., get_by_id, get_all, save, delete
|
||||
// let get_by_id_fn_name = format!("get_{}_by_id", #struct_name_lowercase);
|
||||
// engine.register_fn(&get_by_id_fn_name, ...wrap_option_method_result!(...));
|
||||
|
||||
println!("Registered {} with Rhai (collection: '{}') using DB type {}",
|
||||
#struct_name_str,
|
||||
#collection_name,
|
||||
stringify!(#db_type)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
Reference in New Issue
Block a user