170 lines
5.1 KiB
Rust
170 lines
5.1 KiB
Rust
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<String, String>) {
|
|
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<String> = scope.iter_raw()
|
|
.map(|(name, _, _)| name.to_string())
|
|
.collect();
|
|
for name in &names {
|
|
if let Some(val) = scope.get_value::<Dynamic>(name) {
|
|
variables.insert(name.clone(), dyn_to_str(&val));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn eval_expr(&self, expr: &str, variables: &HashMap<String, String>) -> Option<String> {
|
|
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::<Dynamic>(&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<String, String>) -> 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::<Dynamic>(&mut scope, &full) {
|
|
Ok(val) => {
|
|
if val.is_bool() { return val.cast::<bool>(); }
|
|
if val.is_int() { return val.cast::<i64>() != 0; }
|
|
if val.is_float(){ return val.cast::<f64>() != 0.0; }
|
|
if val.is_string(){
|
|
let s = val.cast::<String>();
|
|
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<String, String>) {
|
|
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<String> = scope.iter_raw()
|
|
.map(|(name, _, _)| name.to_string())
|
|
.collect();
|
|
for name in &names {
|
|
if let Some(val) = scope.get_value::<Dynamic>(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::<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 dyn_to_str(val: &Dynamic) -> String {
|
|
if val.is_string() {
|
|
return val.clone().cast::<String>();
|
|
}
|
|
if val.is_unit() {
|
|
return String::new();
|
|
}
|
|
val.to_string()
|
|
}
|