Compare commits
8 Commits
main
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
|
db5b9a0a42 | ||
|
b20140785e | ||
|
061aee6f1d | ||
|
ec4769a6b0 | ||
|
16ad4f5743 | ||
|
22032f329a | ||
|
c23de6871b | ||
|
372b7a2772 |
@ -1,6 +1,3 @@
|
||||
# rhaj
|
||||
|
||||
```bash
|
||||
#tests, following one shows how we can dynamically load the functions from a set of rhai scripts
|
||||
cargo run --bin test_dynamic_loading
|
||||
```
|
||||
DSL's for our ecosystem
|
515
_archive/lib.rs
Normal file
515
_archive/lib.rs
Normal file
@ -0,0 +1,515 @@
|
||||
// rhai_macros_derive/src/lib.rs
|
||||
|
||||
// We will add our derive macro implementations here.
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, format_ident, quote_spanned};
|
||||
use syn::{parse_macro_input, Ident, Type, ItemFn, spanned::Spanned, PathArguments, GenericArgument, DeriveInput, Data, LitStr};
|
||||
|
||||
// Old ToRhaiMap and FromRhaiMap definitions will be removed from here.
|
||||
// The export_fn macro definition starts after this.
|
||||
|
||||
// Trait definitions removed from here as proc-macro crates cannot export them.
|
||||
// They should be defined in a regular library crate (e.g., rhai_wrapper or a new rhai_traits crate).
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn export_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let func = parse_macro_input!(item as ItemFn);
|
||||
|
||||
let fn_name = &func.sig.ident;
|
||||
let wrapper_fn_name = Ident::new(&format!("rhai_wrapper_{}", fn_name), fn_name.span());
|
||||
let original_fn_generics = &func.sig.generics;
|
||||
let fn_inputs = &func.sig.inputs;
|
||||
// num_expected_args has been removed as Rhai handles arity with typed arguments
|
||||
|
||||
let mut arg_conversions: Vec<TokenStream2> = Vec::new(); // For let converted_arg_0 = ...
|
||||
let mut original_fn_call_args: Vec<TokenStream2> = Vec::new(); // For fn_name(converted_arg_0, ...)
|
||||
|
||||
let mut wrapper_args_dynamic_defs: Vec<TokenStream2> = Vec::new(); // Stores definitions like `wrapper_arg_0: ::rhai::Dynamic`
|
||||
|
||||
for (i, fn_arg) in fn_inputs.iter().enumerate() {
|
||||
if let syn::FnArg::Typed(pat_type) = fn_arg {
|
||||
let arg_ident_original_pat = match *pat_type.pat.clone() {
|
||||
syn::Pat::Ident(pat_ident) => pat_ident.ident,
|
||||
_ => panic!("#[export_fn] only supports simple identifiers as argument patterns, not complex patterns."),
|
||||
};
|
||||
let arg_ident_original_pat_str = arg_ident_original_pat.to_string();
|
||||
let wrapper_arg_name_ident = Ident::new(&format!("wrapper_arg_{}", i), pat_type.pat.span());
|
||||
let wrapper_arg_name_str = wrapper_arg_name_ident.to_string();
|
||||
|
||||
// Populate definitions for the wrapper function signature
|
||||
wrapper_args_dynamic_defs.push(quote! { #wrapper_arg_name_ident: ::rhai::Dynamic });
|
||||
|
||||
// Generate a unique ident for the converted argument variable
|
||||
let converted_rhai_arg_ident = Ident::new(&format!("converted_rhai_arg_{}", i), pat_type.pat.span());
|
||||
|
||||
let arg_rust_type = &*pat_type.ty;
|
||||
let arg_type_str = quote!(#arg_rust_type).to_string().replace(' ', "");
|
||||
|
||||
if let Type::Reference(ref type_ref) = *pat_type.ty {
|
||||
let is_mutable = type_ref.mutability.is_some();
|
||||
let referent_type = &*type_ref.elem;
|
||||
let lock_guard_ident = Ident::new(&format!("lock_guard_{}", i), pat_type.pat.span());
|
||||
|
||||
let conversion_logic = if is_mutable {
|
||||
quote! {
|
||||
let mut #lock_guard_ident = #wrapper_arg_name_ident.write_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be a mutable reference to {}. Ensure it's not already locked.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &mut *#lock_guard_ident;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let #lock_guard_ident = #wrapper_arg_name_ident.read_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be an immutable reference to {}.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &*#lock_guard_ident;
|
||||
}
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "&str" {
|
||||
let str_lock_guard_ident = Ident::new(&format!("str_lock_guard_{}", i), pat_type.pat.span());
|
||||
let conversion_logic = quote! {
|
||||
let #str_lock_guard_ident = #wrapper_arg_name_ident.read_lock::<rhai::ImmutableString>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type &str (requires read_lock on ImmutableString)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = #str_lock_guard_ident.as_str();
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "INT" || arg_type_str == "i64" {
|
||||
let conversion_logic = quote! {
|
||||
let int_option: Option<::rhai::INT> = #wrapper_arg_name_ident.as_int();
|
||||
let #converted_rhai_arg_ident = int_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type i64 (INT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "FLOAT" || arg_type_str == "f64" {
|
||||
let conversion_logic = quote! {
|
||||
let float_option: Option<::rhai::FLOAT> = #wrapper_arg_name_ident.as_float();
|
||||
let #converted_rhai_arg_ident = float_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type f64 (FLOAT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "bool" {
|
||||
let conversion_logic = quote! {
|
||||
let bool_option: Option<bool> = #wrapper_arg_name_ident.as_bool();
|
||||
let #converted_rhai_arg_ident = bool_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type bool",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "String" {
|
||||
let conversion_logic = quote! {
|
||||
let #converted_rhai_arg_ident = #wrapper_arg_name_ident.clone().into_string()
|
||||
.map_err(|_| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type String",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else {
|
||||
let owned_type_path = &*pat_type.ty; // No deref needed here, quote! can handle &Type
|
||||
let arg_type_for_conversion_code = quote!{#owned_type_path};
|
||||
let conversion_logic = quote! {
|
||||
let dyn_val_for_cast = #wrapper_arg_name_ident.clone(); // Clone for try_cast
|
||||
let actual_type_name = #wrapper_arg_name_ident.type_name();
|
||||
let #converted_rhai_arg_ident:#arg_type_for_conversion_code = match <#arg_type_for_conversion_code as std::convert::TryFrom<::rhai::Dynamic>>::try_from(dyn_val_for_cast) {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
let cast_option: Option<#arg_type_for_conversion_code> = #wrapper_arg_name_ident.clone().try_cast::<#arg_type_for_conversion_code>();
|
||||
cast_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be convertible to type {}. TryFrom and try_cast failed.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#owned_type_path)
|
||||
),
|
||||
format!("but found type {}", actual_type_name),
|
||||
::rhai::Position::NONE
|
||||
)))?
|
||||
}
|
||||
};
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
}
|
||||
} else {
|
||||
// Handle receivers like self, &self, &mut self if necessary
|
||||
panic!("#[export_fn] does not support methods with 'self' receivers. Apply to static or free functions.");
|
||||
}
|
||||
}
|
||||
|
||||
let return_type = match &func.sig.output {
|
||||
syn::ReturnType::Default => quote!( () ),
|
||||
syn::ReturnType::Type(_, ty) => quote!( #ty ),
|
||||
};
|
||||
|
||||
// Determine the return type for the wrapper function's Result
|
||||
// If original is (), wrapper returns Dynamic (representing unit). Otherwise, original return type.
|
||||
let return_type_for_wrapper = if quote!(#return_type).to_string().replace(" ", "") == "()" {
|
||||
quote!(::rhai::Dynamic)
|
||||
} else {
|
||||
quote!(#return_type)
|
||||
};
|
||||
|
||||
// How to convert the result of the original function call into the wrapper's Ok Result.
|
||||
let return_conversion = if let syn::ReturnType::Type(_, _ty) = &func.sig.output {
|
||||
// If original function returns non-(), result is already #return_type_for_wrapper (which is #return_type)
|
||||
// So, it can be directly used.
|
||||
quote! { Ok(result) }
|
||||
} else {
|
||||
// If original function returns (), result is (), convert to Dynamic::UNIT for Rhai
|
||||
quote! { Ok(().into()) }
|
||||
};
|
||||
|
||||
let (impl_generics, _ty_generics, where_clause) = original_fn_generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
#func // Re-emit the original function
|
||||
|
||||
// _cx is NativeCallContext. The arguments are now individual ::rhai::Dynamic types.
|
||||
pub fn #wrapper_fn_name #impl_generics (_cx: ::rhai::NativeCallContext, #(#wrapper_args_dynamic_defs),*) -> Result<#return_type_for_wrapper, Box<::rhai::EvalAltResult>> #where_clause {
|
||||
// Arity check is implicitly handled by Rhai's function registration for typed arguments.
|
||||
// If the script calls with wrong number of args, Rhai won't find a matching function signature.
|
||||
|
||||
#(#arg_conversions)*
|
||||
|
||||
// The following allow might be needed if the original function's result is directly usable
|
||||
// without explicit conversion to Dynamic, but the wrapper expects Dynamic for unit returns.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let result = #fn_name(#(#original_fn_call_args),*);
|
||||
#return_conversion
|
||||
}
|
||||
};
|
||||
|
||||
// eprintln!("Generated code for {}:\n{}", stringify!(#fn_name), expanded.to_string());
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_derive(FromRhaiMap, attributes(rhai_map_field))]
|
||||
pub fn derive_from_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
|
||||
// Helper function to check if a type is Option<T> and extract T
|
||||
// Returns (is_option, inner_type_quote_option)
|
||||
fn get_option_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Option" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper function to check if a type is Vec<T> and extract T
|
||||
// Returns (is_vec, inner_type_quote_option)
|
||||
fn get_vec_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper to get the string representation of a type, simplified (e.g., "MyStruct", "String", "i64")
|
||||
fn get_simple_type_str(ty: &Type) -> String {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if let Some(segment) = type_path.path.segments.last() {
|
||||
return segment.ident.to_string();
|
||||
}
|
||||
}
|
||||
quote!(#ty).to_string().replace(' ', "").replace("::", "_") // fallback, might need refinement
|
||||
}
|
||||
|
||||
// Helper to check if a type is a primitive type based on its string representation
|
||||
fn is_primitive_type_str(simple_type_str: &str) -> bool {
|
||||
["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&simple_type_str)
|
||||
}
|
||||
|
||||
let fields_data = match &input.data {
|
||||
Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
|
||||
_ => panic!("FromRhaiMapDerive only supports structs with named fields"),
|
||||
};
|
||||
|
||||
let mut field_value_declarations = Vec::new();
|
||||
let mut struct_field_assignments = Vec::new();
|
||||
|
||||
for field in fields_data.iter() {
|
||||
let field_name_ident = field.ident.as_ref().unwrap(); // Changed variable name for clarity
|
||||
let field_name_str = field_name_ident.to_string();
|
||||
let field_name_str_lit = LitStr::new(&field_name_str, field_name_ident.span()); // Corrected: Use LitStr for string literals
|
||||
let field_ty = &field.ty;
|
||||
let field_value_ident = format_ident!("__field_val_{}", field_name_str);
|
||||
|
||||
let (is_option, option_inner_ty_opt) = get_option_inner_type(field_ty);
|
||||
let type_for_vec_check = if is_option { option_inner_ty_opt.unwrap() } else { field_ty };
|
||||
let (is_vec, vec_inner_ty_opt) = get_vec_inner_type(type_for_vec_check);
|
||||
|
||||
let assignment_code = if is_option {
|
||||
let option_inner_ty = option_inner_ty_opt.expect("Option inner type not found");
|
||||
if is_vec { // Option<Vec<T>>
|
||||
let vec_element_ty = vec_inner_ty_opt.expect("Option<Vec<T>> inner T not found");
|
||||
let vec_element_ty_str = get_simple_type_str(vec_element_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_element_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_element_ty>().ok_or_else(|| format!(
|
||||
"Element in Option<Vec> for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_element_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Option<Vec> for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_element_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|dyn_val_array| {
|
||||
dyn_val_array.into_array().map_err(|e| e.to_string())?.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_element_ty>, String>>()
|
||||
}).transpose()?
|
||||
}
|
||||
} else { // Option<T>
|
||||
let option_inner_ty_str = get_simple_type_str(option_inner_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&option_inner_ty_str) {
|
||||
quote! {
|
||||
let val_for_cast = val.clone();
|
||||
let actual_type_name = val.type_name();
|
||||
val_for_cast.try_cast::<#option_inner_ty>().ok_or_else(|| format!(
|
||||
"Value in Option for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#option_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = val.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for Option key '{}', got {}", #field_name_str_lit, val.type_name()))?;
|
||||
#option_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|val| {
|
||||
#conversion_logic
|
||||
}).transpose()?
|
||||
}
|
||||
}
|
||||
} else if is_vec { // Vec<T>
|
||||
let vec_inner_ty = vec_inner_ty_opt.expect("Vec inner type not found");
|
||||
let vec_inner_ty_str = get_simple_type_str(vec_inner_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_inner_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_inner_ty>().ok_or_else(|| format!(
|
||||
"Element in Vec for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Vec for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {{
|
||||
let dyn_val_array = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found for Vec", #field_name_str_lit))?
|
||||
.into_array().map_err(|e| e.to_string())?;
|
||||
dyn_val_array.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_inner_ty>, String>>()?
|
||||
}}
|
||||
} else { // Direct field T
|
||||
let field_ty_str = get_simple_type_str(field_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&field_ty_str) {
|
||||
quote! {
|
||||
let dyn_val_for_cast = dyn_val_cloned.clone();
|
||||
let actual_type_name = dyn_val_cloned.type_name();
|
||||
dyn_val_for_cast.try_cast::<#field_ty>().ok_or_else(|| format!(
|
||||
"Value in map for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#field_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = dyn_val_cloned.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for key '{}', got {}", #field_name_str_lit, dyn_val_cloned.type_name()))?;
|
||||
#field_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {{
|
||||
let dyn_val_cloned = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found", #field_name_str_lit))?;
|
||||
#conversion_logic?
|
||||
}}
|
||||
};
|
||||
|
||||
field_value_declarations.push(quote! { let #field_value_ident = #assignment_code; });
|
||||
struct_field_assignments.push(quote_spanned!(field_name_ident.span()=> #field_name_ident: #field_value_ident));
|
||||
}
|
||||
|
||||
let gen = quote! {
|
||||
impl #name {
|
||||
pub fn from_rhai_map(map: ::rhai::Map) -> Result<Self, String> {
|
||||
#(#field_value_declarations)*
|
||||
Ok(Self {
|
||||
#(#struct_field_assignments),*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proc_macro::TokenStream::from(gen)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ToRhaiMap)]
|
||||
pub fn derive_to_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let name = &ast.ident;
|
||||
|
||||
let mut field_insertions = Vec::new();
|
||||
|
||||
if let syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) = ast.data {
|
||||
for field in fields.named {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_name_str = field_name.to_string();
|
||||
let ty = &field.ty;
|
||||
|
||||
let mut _is_vec_of_custom_struct = false;
|
||||
let mut is_direct_custom_struct = false;
|
||||
|
||||
let _field_type_name_for_logic = quote!(#ty).to_string().replace(" ", "");
|
||||
|
||||
if let syn::Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let syn::PathArguments::AngleBracketed(angle_bracketed_args) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if let Some(syn::GenericArgument::Type(_inner_ty_syn @ syn::Type::Path(inner_type_path))) = angle_bracketed_args.args.first() {
|
||||
if let Some(inner_segment) = inner_type_path.path.segments.last() {
|
||||
let inner_type_str = inner_segment.ident.to_string();
|
||||
if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&inner_type_str.as_str()) {
|
||||
_is_vec_of_custom_struct = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&type_path.path.segments.first().unwrap().ident.to_string().as_str()) {
|
||||
// It's not a Vec, and not a primitive, so assume it's a direct custom struct
|
||||
is_direct_custom_struct = true;
|
||||
}
|
||||
}
|
||||
|
||||
if _is_vec_of_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &CustomStruct, to_rhai_map() takes &self
|
||||
rhai::Dynamic::from(item.to_rhai_map())
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
// FIX: Logic for direct custom struct field
|
||||
} else if is_direct_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(self.#field_name.to_rhai_map()));
|
||||
});
|
||||
} else {
|
||||
// This handles Vec<Primitive> and direct Primitives
|
||||
let type_str = quote!(#ty).to_string().replace(" ", "");
|
||||
if type_str.starts_with("Vec<") {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &Primitive, clone it for .into()
|
||||
item.clone().into()
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Direct primitive field
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), self.#field_name.clone().into());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics ToRhaiMap for #name #ty_generics #where_clause {
|
||||
fn to_rhai_map(&self) -> rhai::Map {
|
||||
let mut map = rhai::Map::new();
|
||||
#(#field_insertions)*
|
||||
map
|
||||
}
|
||||
}
|
||||
};
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
// Rest of the code remains the same
|
BIN
_archive/listen/.DS_Store
vendored
Normal file
BIN
_archive/listen/.DS_Store
vendored
Normal file
Binary file not shown.
2620
_archive/listen/Cargo.lock
generated
Normal file
2620
_archive/listen/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
_archive/listen/Cargo.toml
Normal file
26
_archive/listen/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
rhai = { version = "1.15.0", features = ["sync"] }
|
||||
rhai_system = { path = "../rhai_system" }
|
||||
bytes = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tera = "1.0"
|
||||
once_cell = "1"
|
||||
rhai_tera = { path = "../rhai_tera" }
|
||||
calendar = { path = "../components/calendar" }
|
||||
|
||||
[[example]]
|
||||
name = "send_rhai_script"
|
||||
path = "examples/send_rhai_script.rs"
|
||||
required-features = []
|
||||
|
||||
[dev-dependencies] # Examples often use dev-dependencies, but reqwest is more like a direct dep for the example's purpose.
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] } # Updated for async example client
|
||||
# tokio is already a main dependency, so the example can use it.
|
38
_archive/listen/examples/send_rhai_script.rs
Normal file
38
_archive/listen/examples/send_rhai_script.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use reqwest::Client;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = Client::new();
|
||||
let server_url = "http://127.0.0.1:8000";
|
||||
|
||||
// Simple Rhai script that creates a map
|
||||
let rhai_script = r#"
|
||||
let message = "Hello from Rhai script!";
|
||||
let number = 40 + 2;
|
||||
#{
|
||||
greeting: message,
|
||||
calculation_result: number
|
||||
}
|
||||
"#;
|
||||
|
||||
println!("Sending Rhai script to server:\n{}", rhai_script);
|
||||
|
||||
let response = client
|
||||
.post(server_url)
|
||||
.header("Content-Type", "text/plain") // Or application/rhai, but plain text is fine
|
||||
.body(rhai_script.to_string())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status();
|
||||
let response_text = response.text().await?;
|
||||
|
||||
println!("\nServer responded with status: {}", status);
|
||||
println!("Response body:\n{}", response_text);
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(format!("Server returned error: {} - {}", status, response_text).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
3
_archive/listen/scripts/init.rhai
Normal file
3
_archive/listen/scripts/init.rhai
Normal file
@ -0,0 +1,3 @@
|
||||
// This script is used to initialize the Rhai system.
|
||||
// It can be left empty or used to define globally available functions/variables.
|
||||
// print("init.rhai loaded!");
|
111
_archive/listen/src/main.rs
Normal file
111
_archive/listen/src/main.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body,
|
||||
Request,
|
||||
Response,
|
||||
Server,
|
||||
StatusCode,
|
||||
Method,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::path::Path;
|
||||
|
||||
use rhai_system::{create_hot_reloadable_system, System};
|
||||
use rhai::Dynamic;
|
||||
use hyper::body::to_bytes;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8000));
|
||||
|
||||
let init_script_path = Path::new("scripts/init.rhai");
|
||||
|
||||
let rhai_sys = match create_hot_reloadable_system(&[init_script_path], Some(0)) {
|
||||
Ok(system) => Arc::new(system),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create Rhai system: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let rhai_sys_clone = Arc::clone(&rhai_sys);
|
||||
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
let rhai_system_for_service = Arc::clone(&rhai_sys_clone);
|
||||
async {
|
||||
Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
|
||||
let rhai_system_for_request = Arc::clone(&rhai_system_for_service);
|
||||
handle_request(rhai_system_for_request, req)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
println!("Rhai script server running at http://{}", addr);
|
||||
println!("Send POST requests with Rhai script in the body to execute.");
|
||||
|
||||
Server::bind(&addr).serve(make_svc).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_request(rhai_sys: Arc<System>, req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
match *req.method() {
|
||||
Method::POST => {
|
||||
let body_bytes = match to_bytes(req.into_body()).await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading request body: {}", e);
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Body::from(format!("Error reading request body: {}", e)))
|
||||
.unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
let script_string = match String::from_utf8(body_bytes.to_vec()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Request body is not valid UTF-8: {}", e);
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from(format!("Request body is not valid UTF-8: {}", e)))
|
||||
.unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
if script_string.trim().is_empty() {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from("Rhai script body cannot be empty."))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
println!("Executing Rhai script: \n{}", script_string);
|
||||
|
||||
match rhai_sys.engine.eval::<Dynamic>(&script_string) {
|
||||
Ok(result) => {
|
||||
let response_body = format!("{}", result);
|
||||
println!("Script result: {}", response_body);
|
||||
Ok(Response::new(Body::from(response_body)))
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Rhai script execution error: {}", e);
|
||||
eprintln!("{}", error_msg);
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Body::from(error_msg))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||
.header("Allow", "POST")
|
||||
.body(Body::from("Method Not Allowed. Please use POST with Rhai script in the body."))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
282
_archive/rhai_autobind_macros/Cargo.lock
generated
Normal file
282
_archive/rhai_autobind_macros/Cargo.lock
generated
Normal file
@ -0,0 +1,282 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[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"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1acc213aa1e33611a4b20b31b738af675113e1c9944d6e3d79e3e7318ce0205"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"instant",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rhai",
|
||||
"serde",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[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 = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
17
_archive/rhai_autobind_macros/Cargo.toml
Normal file
17
_archive/rhai_autobind_macros/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[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"
|
||||
|
||||
[dev-dependencies]
|
||||
rhai = "1.18.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
@ -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);
|
@ -0,0 +1,97 @@
|
||||
use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder};
|
||||
use rhai_autobind_macros::rhai_model_export;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
// Dummy DB type for the example, as rhai_model_export requires a db_type
|
||||
struct DummyDb;
|
||||
impl DummyDb {
|
||||
fn new() -> Self { DummyDb }
|
||||
}
|
||||
|
||||
// Define a simple Calculator struct with the rhai_autobind attribute
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, rhai::CustomType)]
|
||||
#[rhai_model_export(db_type = "DummyDb")] // Provide the required db_type
|
||||
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
|
||||
let dummy_db = DummyDb::new(); // Create an instance of the dummy DB
|
||||
Calculator::register_rhai_bindings_for_calculator(&mut engine, dummy_db);
|
||||
|
||||
// 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(())
|
||||
}
|
152
_archive/rhai_autobind_macros/src/lib.rs
Normal file
152
_archive/rhai_autobind_macros/src/lib.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, format_ident};
|
||||
use syn::{parse_macro_input, Ident, Fields, Visibility, ItemStruct, LitStr, ExprArray, Expr, Lit};
|
||||
use syn::spanned::Spanned; // Add the Spanned trait for span() method
|
||||
use heck::ToSnakeCase; // For converting struct name to snake_case for function names
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroArgs {
|
||||
db_type: syn::Type,
|
||||
collection_name: Option<String>,
|
||||
methods: Vec<String>, // To store method names to be registered
|
||||
}
|
||||
|
||||
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;
|
||||
let mut methods_array: Option<ExprArray> = 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 if ident == "methods" {
|
||||
methods_array = 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());
|
||||
|
||||
let mut methods = Vec::new();
|
||||
if let Some(array_expr) = methods_array {
|
||||
for expr in array_expr.elems {
|
||||
if let Expr::Lit(expr_lit) = expr {
|
||||
if let Lit::Str(lit_str) = expr_lit.lit {
|
||||
methods.push(lit_str.value());
|
||||
} else {
|
||||
return Err(syn::Error::new(expr_lit.lit.span(), "Method name must be a string literal"));
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new(expr.span(), "Method name must be a string literal"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MacroArgs { db_type, collection_name, methods })
|
||||
}
|
||||
}
|
||||
|
||||
#[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_snake_case = struct_name_str.to_snake_case(); // Use heck for snake_case
|
||||
|
||||
let db_type = macro_args.db_type;
|
||||
let collection_name = macro_args.collection_name.unwrap_or_else(|| {
|
||||
// Basic pluralization
|
||||
if struct_name_snake_case.ends_with('y') {
|
||||
format!("{}ies", &struct_name_snake_case[..struct_name_snake_case.len()-1])
|
||||
} else if struct_name_snake_case.ends_with('s') {
|
||||
format!("{}es", struct_name_snake_case)
|
||||
} else {
|
||||
format!("{}s", struct_name_snake_case)
|
||||
}
|
||||
});
|
||||
|
||||
let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_snake_case);
|
||||
let constructor_fn_name = format!("new_{}", struct_name_snake_case);
|
||||
|
||||
let mut field_registrations = 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 field_type = &field.ty;
|
||||
|
||||
// Register getter
|
||||
field_registrations.push(quote! {
|
||||
engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
|
||||
});
|
||||
// Register setter
|
||||
field_registrations.push(quote! {
|
||||
engine.register_set(#field_name_str, |s: &mut #struct_name, val: #field_type| s.#field_name = val);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut method_registrations = Vec::new();
|
||||
for method_name_str in ¯o_args.methods {
|
||||
let method_ident = format_ident!("{}", method_name_str);
|
||||
let rhai_method_name = method_name_str.clone(); // Rhai function name can be the same as Rust method name
|
||||
method_registrations.push(quote! {
|
||||
engine.register_fn(#rhai_method_name, #struct_name::#method_ident);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create a formatted string of method names for printing
|
||||
let methods_str = format!("{:?}", macro_args.methods);
|
||||
|
||||
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 // db is now passed but not used by default for non-DB methods
|
||||
) {
|
||||
// For rhai_wrapper if/when DB operations are added
|
||||
// use rhai_wrapper::*;
|
||||
|
||||
engine.build_type::<#struct_name>();
|
||||
|
||||
// Register constructor (e.g., new_calculator for Calculator::new())
|
||||
engine.register_fn(#constructor_fn_name, #struct_name::new);
|
||||
|
||||
// Register field getters and setters
|
||||
#(#field_registrations)*
|
||||
|
||||
// Register specified instance methods
|
||||
#(#method_registrations)*
|
||||
|
||||
// Placeholder for DB function registrations
|
||||
// e.g., get_by_id, get_all, save, delete
|
||||
|
||||
println!("Registered {} with Rhai (collection: '{}'). Constructor: '{}'. Methods: {}. Fields accessible.",
|
||||
#struct_name_str,
|
||||
#collection_name,
|
||||
#constructor_fn_name,
|
||||
#methods_str
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
@ -11,10 +11,6 @@ path = "src/lib.rs"
|
||||
name = "rhai_engine"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "test_dynamic_loading"
|
||||
path = "examples/loadscripts/test_dynamic_loading.rs"
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.12.0"
|
||||
chrono = "0.4"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user