Files
Glint-Runtime/src/interpreter/rhei.rs

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()
}