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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user