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

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
}