274 lines
8.4 KiB
Rust
274 lines
8.4 KiB
Rust
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<Scope<'static>>,
|
|
action_cache: RefCell<HashMap<String, AST>>,
|
|
expr_cache: RefCell<HashMap<String, AST>>,
|
|
}
|
|
|
|
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<String, Value>) {
|
|
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::<Dynamic>(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<String, Value>) {
|
|
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<String, Value>) -> 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::<Dynamic>(&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<String, Value>) -> 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::<bool>(&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<String, Value>) {
|
|
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<AST> {
|
|
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<AST> {
|
|
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::<i64>() { return Dynamic::from(i); }
|
|
if let Ok(f) = s.parse::<f64>() { return Dynamic::from(f); }
|
|
if let Ok(b) = s.parse::<bool>() { 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
|
|
}
|