Phase 1.2-1.6: replace variables HashMap<String,String> with HashMap<String,Value>, rewrite str_to_dyn/dyn_to_str -> value_to_dynamic/dynamic_to_value, update resolve_string/evaluate_condition/is_truthy for Value

This commit is contained in:
Glint Dev
2026-07-21 20:32:29 +03:00
parent 5597f07787
commit 30cf2e5e26
7 changed files with 542 additions and 53 deletions

View File

@@ -10,10 +10,12 @@ pub use rhei::RheiContext;
use style::StyleSheet as SS;
pub use types::{ComponentDef, Document, Element, InterpError};
use compact_str::CompactString;
use opcodes::*;
use reader::Reader;
use rhei::{dyn_to_str, str_to_dyn, RHEI_PREFIX};
use rhei::{value_to_dynamic, dynamic_to_value, RHEI_PREFIX};
use style::{AncestorInfo, ComputedStyle, StructuralContext};
pub use types::Value;
use regex::Regex;
use std::collections::HashMap;
use std::sync::OnceLock;
@@ -51,7 +53,7 @@ impl Interpreter {
fn parse_block_elements<'a>(
r: &mut Reader<'a>,
variables: &mut HashMap<String, String>,
variables: &mut HashMap<String, Value>,
components: &mut HashMap<String, ComponentDef<'a>>,
rhei_scripts: &mut Vec<String>,
stylesheet: &mut SS,
@@ -75,7 +77,7 @@ impl Interpreter {
OP_GLOBAL | OP_LET => {
let name = r.read_string()?;
let vop = r.read_byte()?;
if let Some(value) = r.read_value_as_string(vop)? {
if let Some(value) = r.read_value(vop)? {
variables.insert(name, value);
}
}
@@ -292,7 +294,7 @@ impl Interpreter {
pub fn evaluate_vdom<'a>(
templates: &[Element<'a>],
variables: &mut HashMap<String, String>,
variables: &mut HashMap<String, Value>,
components: &HashMap<String, ComponentDef<'a>>,
rhei: &RheiContext,
stylesheet: &SS,
@@ -357,7 +359,7 @@ impl Interpreter {
let source_expr = el.get_prop("source").unwrap_or_default();
let resolved_source = if let Some(expr) = source_expr.strip_prefix(RHEI_PREFIX) {
Self::normalize_rhai_array(&rhei.eval_expr(expr, variables))
Self::normalize_rhai_array(&rhei.eval_expr(expr, variables).to_owned_string())
} else {
Self::resolve_string(source_expr, variables).into_owned()
};
@@ -369,7 +371,7 @@ impl Interpreter {
};
for item in items {
let old_val = variables.insert(var_name.to_string(), item);
let old_val = variables.insert(var_name.to_string(), Value::Str(CompactString::new(item)));
output.extend(Self::evaluate_vdom(
&el.children, variables, components, rhei, stylesheet, ancestors,
@@ -397,7 +399,7 @@ impl Interpreter {
let mut old_vals = Vec::with_capacity(new_args.len());
for (k, v) in new_args {
old_vals.push((k.clone(), variables.insert(k, v)));
old_vals.push((k.clone(), variables.insert(k, Value::from(v))));
}
let mut vcomp = Element::new(el.type_name);
@@ -489,9 +491,9 @@ impl Interpreter {
stylesheet.matching_rules(el.type_name, el_id, &classes, active_pseudo, structural, ancestors, preceding_siblings, &el_attributes)
}
fn resolve_prop(v: &str, variables: &HashMap<String, String>, rhei: &RheiContext) -> String {
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String {
if let Some(expr) = v.strip_prefix(RHEI_PREFIX) {
rhei.eval_expr(expr, variables)
rhei.eval_expr(expr, variables).to_owned_string().into()
} else {
Self::resolve_string(v, variables).into_owned()
}
@@ -499,7 +501,7 @@ impl Interpreter {
fn evaluate_condition(
cond: &str,
variables: &HashMap<String, String>,
variables: &HashMap<String, Value>,
rhei: &RheiContext,
) -> bool {
if let Some(expr) = cond.strip_prefix(RHEI_PREFIX) {
@@ -515,7 +517,7 @@ impl Interpreter {
let resolved = Self::resolve_string(&clean, variables).trim().to_string();
if Self::is_truthy(&resolved) { return true; }
if Self::is_truthy_str(&resolved) { return true; }
if resolved == "false" || resolved == "0" || resolved.is_empty() { return false; }
let operators: [(&str, fn(f64, f64) -> bool); 6] = [
@@ -542,7 +544,19 @@ impl Interpreter {
}
#[inline]
fn is_truthy(s: &str) -> bool {
fn is_truthy(v: &Value) -> bool {
match v {
Value::Bool(b) => *b,
Value::Int(i) => *i != 0,
Value::Float(f) => *f != 0.0,
Value::Str(s) => Self::is_truthy_str(s),
Value::None => false,
Value::Array(a) => !a.is_empty(),
}
}
#[inline]
fn is_truthy_str(s: &str) -> bool {
match s.trim() {
"" | "false" | "0" | "null" => false,
"true" | "1" => true,
@@ -563,7 +577,7 @@ impl Interpreter {
}
}
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, String>) -> Cow<'a, str> {
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, Value>) -> Cow<'a, str> {
if !val.contains('$') {
return Cow::Borrowed(val);
}
@@ -582,7 +596,8 @@ impl Interpreter {
}
}
if let Some(resolved) = scope.get(&var_name) {
result.push_str(resolved);
let formatted = resolved.to_owned_string();
result.push_str(&formatted);
} else {
result.push('$');
result.push_str(&var_name);

View File

@@ -1,5 +1,6 @@
use super::opcodes::*;
use super::types::InterpError;
use super::types::{InterpError, Value};
use compact_str::CompactString;
pub struct Reader<'a> {
pub data: &'a [u8],
@@ -138,6 +139,77 @@ impl<'a> Reader<'a> {
Ok(s)
}
pub fn read_value(&mut self, type_op: u8) -> Result<Option<Value>, InterpError> {
let val = match type_op {
OP_PROP_STR | OP_PROP_COLOR | OP_PROP_FSPATH | OP_PROP_IDENT => {
Some(Value::Str(CompactString::new(self.read_string()?)))
}
OP_PROP_RHEI => {
let raw = self.read_string()?;
let mut s = String::with_capacity(super::rhei::RHEI_PREFIX.len() + raw.len());
s.push_str(super::rhei::RHEI_PREFIX);
s.push_str(&raw);
Some(Value::Str(CompactString::new(s)))
}
OP_PROP_VAR => {
let name = self.read_str_ref()?;
let mut s = String::with_capacity(name.len() + 1);
s.push('$');
s.push_str(name);
Some(Value::Str(CompactString::new(s)))
}
OP_PROP_INT => Some(Value::Int(self.read_i64()?)),
OP_PROP_FLOAT => Some(Value::Float(self.read_f64()?)),
OP_PROP_BOOL => Some(Value::Bool(self.read_byte()? != 0)),
OP_PROP_NULL => None,
OP_PROP_ARRAY => {
let items = self.read_array_as_values()?;
Some(Value::Array(items))
}
OP_PROP_UNIT => {
let num = self.read_f64()?;
let unit = self.read_str_ref()?;
let s = if num.fract() == 0.0 {
format!("{}{}", num as i64, unit)
} else {
format!("{}{}", num, unit)
};
Some(Value::Str(CompactString::new(s)))
}
OP_PROP_CALL => {
let name = self.read_string()?;
let arg_count = self.read_u32()? as usize;
let mut args = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
let op = self.read_byte()?;
let val = self.read_value(op)?.unwrap_or(Value::None);
args.push(match val {
Value::Str(s) => s.to_string(),
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
_ => String::new(),
});
}
Some(Value::Str(CompactString::new(format!("{}({})", name, args.join(",")))))
}
_ => None,
};
Ok(val)
}
pub fn read_array_as_values(&mut self) -> Result<Vec<Value>, InterpError> {
let count = self.read_u32()? as usize;
let mut items = Vec::with_capacity(count);
for _ in 0..count {
let elem_op = self.read_byte()?;
if let Some(v) = self.read_value(elem_op)? {
items.push(v);
}
}
Ok(items)
}
pub fn read_array_as_strings(&mut self) -> Result<Vec<String>, InterpError> {
let count = self.read_u32()? as usize;
let mut items = Vec::with_capacity(count);

View File

@@ -2,6 +2,9 @@ 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 {
@@ -46,24 +49,24 @@ impl RheiContext {
}
}
pub fn sync_scope(&self, variables: &HashMap<String, String>) {
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 dyn_to_str(&old_val) == *v {
if dynamic_to_value(&old_val) == *v {
continue;
}
}
scope.set_value(k, str_to_dyn(v));
scope.set_value(k, value_to_dynamic(v));
} else {
scope.push_dynamic(k.clone(), str_to_dyn(v));
scope.push_dynamic(k.clone(), value_to_dynamic(v));
}
}
}
pub fn initialize(&self, variables: &mut HashMap<String, String>) {
pub fn initialize(&self, variables: &mut HashMap<String, Value>) {
self.sync_scope(variables);
let mut scope = self.scope.borrow_mut();
@@ -72,27 +75,27 @@ impl RheiContext {
}
for (name, _, val) in scope.iter_raw() {
let s_val = dyn_to_str(&val);
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, String>) -> String {
pub fn eval_expr(&self, expr: &str, variables: &HashMap<String, Value>) -> Value {
self.sync_scope(variables);
let mut scope = self.scope.borrow_mut();
match self.engine.eval_expression_with_scope::<Dynamic>(&mut *scope, expr) {
Ok(val) => dyn_to_str(&val),
Ok(val) => dynamic_to_value(&val),
Err(e) => {
eprintln!("⚠️ Rhei eval_expr error: {e}");
String::new()
Value::None
}
}
}
pub fn eval_condition(&self, expr: &str, variables: &HashMap<String, String>) -> bool {
pub fn eval_condition(&self, expr: &str, variables: &HashMap<String, Value>) -> bool {
self.sync_scope(variables);
let mut scope = self.scope.borrow_mut();
@@ -105,7 +108,7 @@ impl RheiContext {
}
}
pub fn execute_action(&self, script: &str, variables: &mut HashMap<String, String>) {
pub fn execute_action(&self, script: &str, variables: &mut HashMap<String, Value>) {
self.sync_scope(variables);
let mut scope = self.scope.borrow_mut();
@@ -121,7 +124,7 @@ impl RheiContext {
}
for (name, _, val) in scope.iter_raw() {
let s_val = dyn_to_str(&val);
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);
}
@@ -135,16 +138,36 @@ impl Default for RheiContext {
}
}
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 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) => Dynamic::from(s.to_string()),
Value::None => Dynamic::UNIT,
Value::Array(arr) => {
let d: rhai::Dynamic = arr.iter().map(value_to_dynamic).collect();
d
}
}
}
pub fn dyn_to_str(d: &Dynamic) -> String {
pub fn dynamic_to_value(d: &Dynamic) -> Value {
if d.is_string() {
return d.clone().into_string().unwrap_or_default();
return Value::Str(CompactString::new(d.clone().into_string().unwrap_or_default()));
}
d.to_string()
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
}

View File

@@ -3,11 +3,11 @@ use std::collections::HashMap;
use std::fmt;
use super::style::{ComputedStyle, StyleSheet};
use compact_str::CompactStr;
use compact_str::CompactString;
#[derive(Clone, Debug)]
pub enum Value {
Str(CompactStr),
Str(CompactString),
Int(i64),
Float(f64),
Bool(bool),
@@ -23,31 +23,31 @@ impl Value {
}
}
pub fn to_owned_string(&self) -> CompactStr {
pub fn to_owned_string(&self) -> CompactString {
match self {
Value::Str(s) => s.clone(),
Value::Int(i) => CompactStr::new(i.to_string()),
Value::Float(f) => CompactStr::new(if f.fract() == 0.0 {
Value::Int(i) => CompactString::new(i.to_string()),
Value::Float(f) => CompactString::new(if f.fract() == 0.0 {
format!("{:.1}", f)
} else {
f.to_string()
}),
Value::Bool(b) => CompactStr::new(b.to_string()),
Value::Array(a) => CompactStr::new(a.iter().map(|v| v.to_owned_string()).collect::<Vec<_>>().join(",")),
Value::None => CompactStr::new_empty(),
Value::Bool(b) => CompactString::new(b.to_string()),
Value::Array(a) => CompactString::new(a.iter().map(|v| v.to_owned_string()).collect::<Vec<_>>().join(",")),
Value::None => CompactString::new(""),
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Str(CompactStr::new(s))
Value::Str(CompactString::new(s))
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::Str(CompactStr::new(s))
Value::Str(CompactString::new(s))
}
}
@@ -125,7 +125,7 @@ pub struct ComponentDef<'a> {
pub struct Document<'a> {
pub roots: Vec<Element<'a>>,
pub components: HashMap<String, ComponentDef<'a>>,
pub variables: HashMap<String, String>,
pub variables: HashMap<String, Value>,
pub rhei_scripts: Vec<String>,
pub stylesheet: StyleSheet,
}