use rhai::{Dynamic, Engine, Scope, AST, Module}; use std::collections::HashMap; use std::cell::RefCell; use super::types::Value; use compact_str::CompactString; pub const RHEI_PREFIX: &str = "__rhei:"; pub struct RheiContext { engine: Engine, init_ast: AST, scope: RefCell>, action_cache: RefCell>, expr_cache: RefCell>, } impl RheiContext { pub fn new(scripts: &[String]) -> Self { let ctx = Self::new_empty(scripts); ctx.precompile_scripts(scripts); ctx } fn new_empty(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(); match Module::eval_ast_as_new(Scope::new(), &fn_ast, &engine) { Ok(module) => { engine.register_global_module(module.into()); } Err(e) => { eprintln!("⚠️ Rhei module creation error: {e}"); } } Self { engine, init_ast: combined, scope: RefCell::new(Scope::new()), action_cache: RefCell::new(HashMap::new()), expr_cache: RefCell::new(HashMap::new()), } } pub fn sync_scope(&self, variables: &HashMap) { let mut scope = self.scope.borrow_mut(); for (k, v) in variables.iter() { if scope.contains(k) { if let Some(old_val) = scope.get_value::(k) { if dynamic_to_value(&old_val) == *v { continue; } } scope.set_value(k, value_to_dynamic(v)); } else { scope.push_dynamic(k.clone(), value_to_dynamic(v)); } } } pub fn initialize(&self, variables: &mut HashMap) { self.sync_scope(variables); let mut scope = self.scope.borrow_mut(); if let Err(e) = self.engine.run_ast_with_scope(&mut *scope, &self.init_ast) { eprintln!("⚠️ Rhei initialization error: {e}"); } for (name, _, val) in scope.iter_raw() { let s_val = dynamic_to_value(&val); if variables.get(name).map(|v| v != &s_val).unwrap_or(true) { variables.insert(name.to_string(), s_val); } } } pub fn eval_expr(&self, expr: &str, variables: &HashMap) -> Value { self.sync_scope(variables); let mut scope = self.scope.borrow_mut(); let ast = self.get_or_compile_expr(expr); match ast { Some(ast) => { match self.engine.eval_ast_with_scope::(&mut *scope, &ast) { Ok(val) => dynamic_to_value(&val), Err(e) => { eprintln!("⚠️ Rhei eval_expr error: {e}"); Value::None } } } None => Value::None, } } pub fn eval_condition(&self, expr: &str, variables: &HashMap) -> bool { self.sync_scope(variables); let mut scope = self.scope.borrow_mut(); let ast = self.get_or_compile_expr(expr); match ast { Some(ast) => { match self.engine.eval_ast_with_scope::(&mut *scope, &ast) { Ok(b) => b, Err(e) => { eprintln!("⚠️ Rhei eval_condition error: {e}"); false } } } None => false, } } pub fn execute_action(&self, script: &str, variables: &mut HashMap) { self.sync_scope(variables); let mut scope = self.scope.borrow_mut(); let action_ast = self.get_or_compile_action(script); if let Some(action_ast) = action_ast { if let Err(e) = self.engine.run_ast_with_scope(&mut *scope, &action_ast) { eprintln!("⚠️ Rhei action execution error: {e}"); } } for (name, _, val) in scope.iter_raw() { let s_val = dynamic_to_value(&val); if variables.get(name).map(|v| v != &s_val).unwrap_or(true) { variables.insert(name.to_string(), s_val); } } } fn get_or_compile_action(&self, script: &str) -> Option { let mut cache = self.action_cache.borrow_mut(); if let Some(ast) = cache.get(script) { return Some(ast.clone()); } match self.engine.compile(script) { Ok(ast) => { cache.insert(script.to_string(), ast.clone()); Some(ast) } Err(e) => { eprintln!("⚠️ Rhei action compilation error: {e}"); None } } } fn get_or_compile_expr(&self, expr: &str) -> Option { let mut cache = self.expr_cache.borrow_mut(); if let Some(ast) = cache.get(expr) { return Some(ast.clone()); } match self.engine.compile_expression(expr) { Ok(ast) => { cache.insert(expr.to_string(), ast.clone()); Some(ast) } Err(e) => { eprintln!("⚠️ Rhei expression compilation error: {e}"); None } } } pub fn precompile_scripts(&self, scripts: &[String]) { for script in scripts { self.get_or_compile_action(script); } } pub fn precompile_actions(&self, actions: &[String]) { for action in actions { self.get_or_compile_action(action); } } pub fn precompile_exprs(&self, exprs: &[String]) { for expr in exprs { self.get_or_compile_expr(expr); } } pub fn precompile_all_from_doc(&self, doc: &super::Document) { for script in &doc.rhei_scripts { self.get_or_compile_action(script); } for root in &doc.roots { Self::collect_and_precompile(root, self); } } fn collect_and_precompile(el: &super::Element, ctx: &RheiContext) { for (k, v) in &el.properties { if k.starts_with("__on:") && !v.is_empty() { ctx.get_or_compile_action(v); } if let Some(expr) = v.strip_prefix(RHEI_PREFIX) { ctx.get_or_compile_expr(expr); } } for child in &el.children { Self::collect_and_precompile(child, ctx); } } } impl Default for RheiContext { fn default() -> Self { Self::new(&[]) } } pub fn value_to_dynamic(v: &Value) -> Dynamic { match v { Value::Int(i) => Dynamic::from(*i), Value::Float(f) => Dynamic::from(*f), Value::Bool(b) => Dynamic::from(*b), Value::Str(s) => str_to_dynamic(s), Value::None => Dynamic::UNIT, Value::Array(arr) => { let d: rhai::Dynamic = arr.iter().map(value_to_dynamic).collect(); d } } } pub fn str_to_dynamic(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 dynamic_to_value(d: &Dynamic) -> Value { if d.is_string() { return Value::Str(CompactString::new(d.clone().into_string().unwrap_or_default())); } if d.is_int() { return Value::Int(d.as_int().unwrap_or(0)); } if d.is_float() { return Value::Float(d.as_float().unwrap_or(0.0)); } if d.is_bool() { return Value::Bool(d.as_bool().unwrap_or(false)); } if d.is_array() { let arr = d.clone().into_array().unwrap_or_default(); return Value::Array(arr.iter().map(dynamic_to_value).collect()); } Value::None }