use rhai::{Dynamic, Engine, Scope, AST}; use std::collections::HashMap; pub const RHEI_PREFIX: &str = "__rhei:"; pub struct RheiContext { engine: Engine, init_ast: AST, fn_ast: AST, } impl RheiContext { pub fn new(scripts: &[String]) -> Self { let mut engine = Engine::new(); engine.on_print(|s| println!("[rhei] {s}")); engine.on_debug(|s, src, pos| { eprintln!("[rhei debug @ {src:?}:{pos}] {s}"); }); let mut combined = AST::empty(); for (i, script) in scripts.iter().enumerate() { match engine.compile(script) { Ok(ast) => combined = combined.merge(&ast), Err(e) => eprintln!("⚠️ Rhei compile error (block #{i}): {e}"), } } let mut fn_ast = combined.clone(); fn_ast.clear_statements(); Self { engine, init_ast: combined, fn_ast } } pub fn initialize(&self, variables: &mut HashMap) { let mut scope = Scope::new(); for (k, v) in variables.iter() { scope.push_dynamic(k.clone(), str_to_dyn(v)); } if let Err(e) = self.engine.run_ast_with_scope(&mut scope, &self.init_ast) { eprintln!("⚠️ Rhei init error: {e}"); } let names: Vec = scope.iter_raw() .map(|(name, _, _)| name.to_string()) .collect(); for name in &names { if let Some(val) = scope.get_value::(name) { variables.insert(name.clone(), dyn_to_str(&val)); } } } pub fn eval_expr(&self, expr: &str, variables: &HashMap) -> Option { let mut scope = Scope::new(); for (k, v) in variables { scope.push_dynamic(k.clone(), str_to_dyn(v)); } let expr_ast = self.engine .compile_expression(expr) .or_else(|_| self.engine.compile(expr)) .ok()?; let full = self.fn_ast.merge(&expr_ast); match self.engine.eval_ast_with_scope::(&mut scope, &full) { Ok(val) => Some(dyn_to_str(&val)), Err(e) => { eprintln!("⚠️ Rhei eval `{expr}`: {e}"); None } } } pub fn eval_condition(&self, expr: &str, variables: &HashMap) -> bool { let mut scope = Scope::new(); for (k, v) in variables { scope.push_dynamic(k.clone(), str_to_dyn(v)); } let ast = match self.engine.compile_expression(expr) { Ok(a) => a, Err(_) => match self.engine.compile(expr) { Ok(a) => a, Err(e) => { eprintln!("⚠️ Rhei condition compile `{expr}`: {e}"); return false; } }, }; let full = self.fn_ast.merge(&ast); match self.engine.eval_ast_with_scope::(&mut scope, &full) { Ok(val) => { if val.is_bool() { return val.cast::(); } if val.is_int() { return val.cast::() != 0; } if val.is_float(){ return val.cast::() != 0.0; } if val.is_string(){ let s = val.cast::(); return !matches!(s.trim(), "" | "false" | "0" | "null"); } !val.is_unit() } Err(e) => { eprintln!("⚠️ Rhei condition eval `{expr}`: {e}"); false } } } pub fn execute_action(&self, script: &str, variables: &mut HashMap) { let mut scope = Scope::new(); for (k, v) in variables.iter() { scope.push_dynamic(k.clone(), str_to_dyn(v)); } match self.engine.compile(script) { Ok(action_ast) => { let full = self.fn_ast.merge(&action_ast); if let Err(e) = self.engine.run_ast_with_scope(&mut scope, &full) { eprintln!("⚠️ Rhei action execution error: {e}"); } } Err(e) => { eprintln!("⚠️ Rhei action compilation error: {e}"); } } let names: Vec = scope.iter_raw() .map(|(name, _, _)| name.to_string()) .collect(); for name in &names { if let Some(val) = scope.get_value::(name) { variables.insert(name.clone(), dyn_to_str(&val)); } } } } impl Default for RheiContext { fn default() -> Self { Self::new(&[]) } } pub fn str_to_dyn(s: &str) -> Dynamic { if let Ok(i) = s.parse::() { return Dynamic::from(i); } if let Ok(f) = s.parse::() { return Dynamic::from(f); } if let Ok(b) = s.parse::() { return Dynamic::from(b); } Dynamic::from(s.to_owned()) } pub fn dyn_to_str(val: &Dynamic) -> String { if val.is_string() { return val.clone().cast::(); } if val.is_unit() { return String::new(); } val.to_string() }