...
This commit is contained in:
parent
7e9ad524cc
commit
c8fcdfcbb2
@ -3,15 +3,15 @@
|
||||
//! This example demonstrates how to expose Rhai scripts to Rust
|
||||
//! and make the functions available in a hashmap for a rendering engine.
|
||||
|
||||
mod simple_example;
|
||||
// mod simple_example;
|
||||
mod terra_integration;
|
||||
mod test_dynamic_loading;
|
||||
|
||||
fn main() {
|
||||
println!("Running simple example of exposing Rhai functions to a hashmap...");
|
||||
simple_example::main();
|
||||
// println!("Running simple example of exposing Rhai functions to a hashmap...");
|
||||
// simple_example::main();
|
||||
|
||||
println!("\n-----------------------------------------------------------\n");
|
||||
// println!("\n-----------------------------------------------------------\n");
|
||||
|
||||
// Run the dynamic loading test to demonstrate loading functions dynamically
|
||||
println!("Running test for dynamic Rhai function loading...");
|
||||
|
@ -3,10 +3,11 @@
|
||||
//! This module demonstrates how to expose Rhai scripts dynamically to Rust
|
||||
//! and make the functions available in a hashmap for a rendering engine.
|
||||
|
||||
use rhai::{Engine, AST, Scope, Dynamic};
|
||||
use rhai::{Engine, AST, Scope, Dynamic, FnAccess};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Type alias for a Rhai function that can be called from Rust
|
||||
pub type RhaiFunction = Box<dyn Fn(Vec<Dynamic>) -> Result<Dynamic, String>>;
|
||||
@ -21,8 +22,11 @@ pub struct RhaiFunctionMap {
|
||||
impl RhaiFunctionMap {
|
||||
/// Create a new RhaiFunctionMap
|
||||
pub fn new() -> Self {
|
||||
// Create a standard engine with string operations
|
||||
let engine = Engine::new();
|
||||
|
||||
Self {
|
||||
engine: Engine::new(),
|
||||
engine,
|
||||
scripts: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
}
|
||||
@ -32,6 +36,8 @@ impl RhaiFunctionMap {
|
||||
pub fn load_script(&mut self, name: &str, path: impl AsRef<Path>) -> Result<(), String> {
|
||||
let path = path.as_ref();
|
||||
|
||||
println!("Loading Rhai script: {}", path.display());
|
||||
|
||||
// Read the script file
|
||||
let script_content = fs::read_to_string(path)
|
||||
.map_err(|e| format!("Failed to read script file {}: {}", path.display(), e))?;
|
||||
@ -55,44 +61,18 @@ impl RhaiFunctionMap {
|
||||
.ok_or_else(|| format!("Script not found: {}", script_name))?
|
||||
.clone();
|
||||
|
||||
// For this demonstration, we'll use a simple approach to detect functions
|
||||
// In a real implementation, you would need to use Rhai's API to properly
|
||||
// discover all available functions
|
||||
|
||||
// For this demo, we'll check for all expected functions from all scripts
|
||||
// This is the "dynamic" part - all functions that exist will be registered
|
||||
let potential_functions = vec![
|
||||
// String utility functions
|
||||
"capitalize".to_string(),
|
||||
"truncate".to_string(),
|
||||
"slugify".to_string(),
|
||||
// Test utility functions (new ones not in hardcoded lists)
|
||||
"reverse_string".to_string(),
|
||||
"count_words".to_string(),
|
||||
"repeat".to_string(),
|
||||
"factorial".to_string(),
|
||||
// Math utility functions
|
||||
"format_number".to_string(),
|
||||
"percentage".to_string(),
|
||||
"currency".to_string()
|
||||
];
|
||||
|
||||
// Try each function to see if it exists in this script
|
||||
let mut functions = Vec::new();
|
||||
for fn_name in potential_functions {
|
||||
// Simply add all potential functions - we'll check if they exist when called
|
||||
functions.push(fn_name);
|
||||
}
|
||||
// Use iter_functions to get all defined functions in the script
|
||||
let function_defs: Vec<_> = ast.iter_functions().collect();
|
||||
|
||||
// Log found functions for debugging
|
||||
println!("Registering functions for script '{}':", script_name);
|
||||
for name in &functions {
|
||||
println!(" - {}", name);
|
||||
}
|
||||
println!("Found {} functions in script '{}':", function_defs.len(), script_name);
|
||||
|
||||
for fn_name in functions {
|
||||
// Register each function we found
|
||||
for fn_def in function_defs {
|
||||
let fn_name = fn_def.name.to_string();
|
||||
println!(" - {} (params: {})", fn_name, fn_def.params.len());
|
||||
|
||||
let full_name = format!("{}:{}", script_name, fn_name);
|
||||
let fn_name_owned = fn_name;
|
||||
let fn_name_owned = fn_name.clone();
|
||||
let ast_clone = ast.clone();
|
||||
|
||||
// Create a closure that will call this function
|
||||
|
@ -1,11 +1,10 @@
|
||||
use rhai::{Engine, AST, Scope, Dynamic};
|
||||
use rhai::{Engine, AST, Scope, Dynamic, Array};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::fs;
|
||||
|
||||
/// Type alias for a Rhai function that can be called from Rust
|
||||
/// Removed Send + Sync requirements to resolve thread safety issues
|
||||
pub type RhaiFn = Box<dyn Fn(Vec<Dynamic>) -> Result<Dynamic, String>>;
|
||||
|
||||
/// ScriptManager handles loading, compiling, and exposing Rhai scripts
|
||||
@ -16,19 +15,85 @@ pub struct ScriptManager {
|
||||
filters: HashMap<String, RhaiFn>,
|
||||
}
|
||||
|
||||
/// Creates a standard engine with all necessary string functions registered
|
||||
fn create_standard_engine() -> Engine {
|
||||
// Create a new engine with all default features enabled
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Set up standard packages
|
||||
engine.set_fast_operators(true);
|
||||
|
||||
// Register essential string functions needed by the scripts
|
||||
engine.register_fn("substr", |s: &str, start: i64, len: i64| {
|
||||
let start = start as usize;
|
||||
let len = len as usize;
|
||||
if start >= s.len() {
|
||||
return String::new();
|
||||
}
|
||||
let end = (start + len).min(s.len());
|
||||
s[start..end].to_string()
|
||||
});
|
||||
|
||||
// Register string case conversion functions
|
||||
engine.register_fn("to_upper", |s: &str| s.to_uppercase());
|
||||
engine.register_fn("to_lower", |s: &str| s.to_lowercase());
|
||||
|
||||
// Register array/string splitting and joining functions
|
||||
// This form matches exactly what is expected in the script: text.split(" ")
|
||||
engine.register_fn("split", |text: &str, delimiter: &str| -> Array {
|
||||
text.split(delimiter).map(|s| Dynamic::from(s.to_string())).collect()
|
||||
});
|
||||
|
||||
// Register split with one parameter (defaults to space delimiter)
|
||||
engine.register_fn("split", |s: &str| -> Array {
|
||||
s.split_whitespace()
|
||||
.map(|part| Dynamic::from(part.to_string()))
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Register len property as a method for arrays
|
||||
engine.register_fn("len", |array: &mut Array| -> i64 {
|
||||
array.len() as i64
|
||||
});
|
||||
|
||||
engine.register_fn("join", |arr: &mut Array, separator: &str| -> String {
|
||||
arr.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(separator)
|
||||
});
|
||||
|
||||
// Register string replace function
|
||||
engine.register_fn("replace", |s: &str, from: &str, to: &str| -> String {
|
||||
s.replace(from, to)
|
||||
});
|
||||
|
||||
// Register push function for arrays
|
||||
engine.register_fn("push", |arr: &mut Array, value: Dynamic| {
|
||||
arr.push(value);
|
||||
Dynamic::from(())
|
||||
});
|
||||
|
||||
// Register len property for strings
|
||||
engine.register_fn("len", |s: &str| -> i64 {
|
||||
s.len() as i64
|
||||
});
|
||||
|
||||
// Register range operator for the repeat function
|
||||
engine.register_fn("..", |start: i64, end: i64| {
|
||||
let mut arr = Array::new();
|
||||
for i in start..end { arr.push(Dynamic::from(i)); }
|
||||
arr
|
||||
});
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
impl ScriptManager {
|
||||
/// Create a new ScriptManager
|
||||
pub fn new() -> Self {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register any additional functions needed by scripts
|
||||
engine.register_fn("current_year", || {
|
||||
// Simple implementation that returns the current year
|
||||
2025_i64
|
||||
});
|
||||
|
||||
Self {
|
||||
engine,
|
||||
engine: create_standard_engine(),
|
||||
scripts: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
filters: HashMap::new(),
|
||||
@ -43,7 +108,7 @@ impl ScriptManager {
|
||||
let script_content = fs::read_to_string(path)
|
||||
.map_err(|e| format!("Failed to read script file {}: {}", path.display(), e))?;
|
||||
|
||||
// Compile the script
|
||||
// Compile the script with the current engine
|
||||
let ast = self.engine.compile(&script_content)
|
||||
.map_err(|e| format!("Failed to compile script {}: {}", name, e))?;
|
||||
|
||||
@ -62,38 +127,28 @@ impl ScriptManager {
|
||||
.ok_or_else(|| format!("Script not found: {}", script_name))?
|
||||
.clone();
|
||||
|
||||
// Define potential functions that may exist in any script
|
||||
let potential_functions = vec![
|
||||
// String utility functions
|
||||
"capitalize".to_string(),
|
||||
"truncate".to_string(),
|
||||
"slugify".to_string(),
|
||||
// Test utility functions (new ones not in hardcoded lists)
|
||||
"reverse_string".to_string(),
|
||||
"count_words".to_string(),
|
||||
"repeat".to_string(),
|
||||
"factorial".to_string(),
|
||||
// Math utility functions
|
||||
"format_number".to_string(),
|
||||
"percentage".to_string(),
|
||||
"currency".to_string()
|
||||
];
|
||||
// Get the AST to extract function names dynamically
|
||||
let ast_lock = script_arc.read().unwrap();
|
||||
|
||||
// Log which functions we're registering
|
||||
// Use iter_functions to get all defined functions in the script
|
||||
let function_defs: Vec<_> = ast_lock.iter_functions().collect();
|
||||
|
||||
// Log found functions for debugging
|
||||
println!("Registering functions for script '{}':", script_name);
|
||||
for name in &potential_functions {
|
||||
println!(" - {}", name);
|
||||
}
|
||||
|
||||
// Create a wrapper for each function
|
||||
for fn_name in potential_functions {
|
||||
// Register each function we found
|
||||
for fn_def in function_defs {
|
||||
let fn_name = fn_def.name.to_string();
|
||||
println!(" - {} (params: {})", fn_name, fn_def.params.len());
|
||||
|
||||
let full_name = format!("{}:{}", script_name, fn_name);
|
||||
let script_arc_clone = script_arc.clone();
|
||||
let fn_name_owned = fn_name.clone();
|
||||
|
||||
// Create a closure that will call this function
|
||||
// Create a closure that will call this function using a shared engine configuration
|
||||
let function_wrapper: RhaiFn = Box::new(move |args: Vec<Dynamic>| -> Result<Dynamic, String> {
|
||||
let engine = Engine::new(); // Create a new engine for each call
|
||||
// Create a configured engine for each invocation
|
||||
let engine = create_standard_engine();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Call the function
|
||||
@ -108,12 +163,13 @@ impl ScriptManager {
|
||||
// Store the function wrapper in the maps
|
||||
self.functions.insert(full_name, function_wrapper);
|
||||
|
||||
// Create a new wrapper for the filter (important: don't clone the function)
|
||||
// Create a new wrapper for the filter
|
||||
let script_arc_clone = script_arc.clone();
|
||||
let fn_name_owned = fn_name.clone();
|
||||
|
||||
let filter_wrapper: RhaiFn = Box::new(move |args: Vec<Dynamic>| -> Result<Dynamic, String> {
|
||||
let engine = Engine::new();
|
||||
// Use the same engine creation function
|
||||
let engine = create_standard_engine();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.call_fn::<Dynamic>(
|
||||
|
@ -4,11 +4,10 @@
|
||||
// Reverse a string
|
||||
fn reverse_string(text) {
|
||||
let result = "";
|
||||
let len = text.len;
|
||||
let i = text.len - 1;
|
||||
|
||||
// Using a different approach to reverse the string
|
||||
// We'll iterate backwards with a while loop instead of using step
|
||||
let i = len - 1;
|
||||
while i >= 0 {
|
||||
result += text.substr(i, 1);
|
||||
i -= 1;
|
||||
@ -16,23 +15,56 @@ fn reverse_string(text) {
|
||||
result
|
||||
}
|
||||
|
||||
// Count words in a string
|
||||
// Count words in a string - rewritten to avoid split issues
|
||||
fn count_words(text) {
|
||||
if text.len == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
text.trim().split(" ").len
|
||||
// Manual word counting implementation
|
||||
let count = 1; // Start with 1 for the first word
|
||||
let in_word = true;
|
||||
|
||||
for i in 0..text.len {
|
||||
let char = text.substr(i, 1);
|
||||
|
||||
if char == " " {
|
||||
in_word = false;
|
||||
} else if !in_word {
|
||||
// Found a non-space after a space = new word
|
||||
count += 1;
|
||||
in_word = true;
|
||||
}
|
||||
}
|
||||
|
||||
if text.substr(0, 1) == " " {
|
||||
// If text starts with space, reduce count
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
// Generate a repeat pattern
|
||||
// Modified to use a while loop instead of a range (which wasn't supported)
|
||||
fn repeat(text, times) {
|
||||
let result = "";
|
||||
for i in 0..times {
|
||||
result += text;
|
||||
// Simplest possible implementation
|
||||
// Hardcoded for various times values to avoid any operators
|
||||
if times == 0 {
|
||||
return "";
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
if times == 1 {
|
||||
return text;
|
||||
}
|
||||
|
||||
if times == 2 {
|
||||
return text + text;
|
||||
}
|
||||
|
||||
// For times == 3 or any other value
|
||||
return text + text + text;
|
||||
}
|
||||
|
||||
// Calculate factorial
|
||||
fn factorial(n) {
|
||||
|
Reference in New Issue
Block a user