use std::borrow::Cow; use std::collections::HashMap; use std::fmt; use std::ops::Range; use super::reactive::{ElementId, ReactiveTracker}; use super::style::{ComputedStyle, StyleSheet}; use compact_str::CompactString; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct InternedStr(u32); impl InternedStr { pub const fn from_raw(id: u32) -> Self { Self(id) } pub fn raw(&self) -> u32 { self.0 } } impl InternedStr { pub fn eq_str(&self, other: &str) -> bool { with_interner(|interner| interner.lookup(*self) == other) } } #[derive(Debug, Clone)] pub struct Interner { strings: Vec, map: HashMap, next_id: u32, } impl Interner { pub fn new() -> Self { Self { strings: Vec::new(), map: HashMap::new(), next_id: 0, } } pub fn intern(&mut self, s: &str) -> InternedStr { if let Some(&id) = self.map.get(s) { return InternedStr(id); } let id = self.next_id; self.next_id += 1; self.strings.push(s.to_string()); self.map.insert(s.to_string(), id); InternedStr(id) } pub fn lookup(&self, id: InternedStr) -> &str { &self.strings[id.0 as usize] } pub fn intern_or_none(&mut self, s: Option<&str>) -> Option { s.map(|s| self.intern(s)) } } use std::cell::RefCell; thread_local! { static GLOBAL_INTERNER: RefCell = RefCell::new(Interner::new()); } pub fn with_interner(f: F) -> R where F: FnOnce(&mut Interner) -> R, { GLOBAL_INTERNER.with(|i| f(&mut *i.borrow_mut())) } #[derive(Clone, Debug)] pub enum Value { Str(CompactString), Int(i64), Float(f64), Bool(bool), Array(Vec), None, } impl Value { pub fn as_str(&self) -> Option<&str> { match self { Value::Str(s) => Some(s.as_str()), _ => None, } } pub fn to_owned_string(&self) -> CompactString { match self { Value::Str(s) => s.clone(), 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) => CompactString::new(b.to_string()), Value::Array(a) => CompactString::new(a.iter().map(|v| v.to_owned_string()).collect::>().join(",")), Value::None => CompactString::new(""), } } } impl From<&str> for Value { fn from(s: &str) -> Self { Value::Str(CompactString::new(s)) } } impl From for Value { fn from(s: String) -> Self { Value::Str(CompactString::new(s)) } } impl From for Value { fn from(i: i64) -> Self { Value::Int(i) } } impl From for Value { fn from(f: f64) -> Self { Value::Float(f) } } impl From for Value { fn from(b: bool) -> Self { Value::Bool(b) } } impl> From> for Value { fn from(v: Vec) -> Self { Value::Array(v.into_iter().map(Into::into).collect()) } } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::Str(a), Value::Str(b)) => a == b, (Value::Int(a), Value::Int(b)) => a == b, (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON, (Value::Bool(a), Value::Bool(b)) => a == b, (Value::Array(a), Value::Array(b)) => a == b, (Value::None, Value::None) => true, _ => false, } } } #[derive(Debug, Clone)] pub struct Element<'a> { pub type_name: &'a str, pub properties: Vec<(Cow<'a, str>, Cow<'a, str>)>, pub children: Vec>, pub computed_style: ComputedStyle, pub element_id: ElementId, } impl<'a> Element<'a> { pub fn id(&self) -> Option<&str> { self.properties.iter().find_map(|(k, v)| { if **k == *"id" { Some(v.as_ref()) } else { None } }) } pub fn new(type_name: &'a str) -> Self { Self { type_name, properties: Vec::with_capacity(8), children: Vec::with_capacity(4), computed_style: ComputedStyle::default(), element_id: ElementId(u32::MAX), } } pub fn new_with_id(type_name: &'a str, id: ElementId) -> Self { Self { type_name, properties: Vec::with_capacity(8), children: Vec::with_capacity(4), computed_style: ComputedStyle::default(), element_id: id, } } } #[derive(Debug, Clone)] pub struct ComponentDef<'a> { pub name: String, pub params: Vec<(String, String)>, pub children: Vec>, } #[derive(Debug, Clone)] pub struct Document<'a> { pub roots: Vec>, pub components: HashMap>, pub variables: HashMap, pub rhei_scripts: Vec, pub stylesheet: StyleSheet, pub interner: Interner, pub tracker: ReactiveTracker, } pub type NodeId = u32; pub type PropertyIdx = usize; pub type NodeIdx = usize; #[derive(Debug, Clone)] pub struct VNode<'a> { pub id: NodeId, pub type_name: &'a str, pub properties: Range, pub children_range: Range, pub computed_style: ComputedStyle, pub element_id: ElementId, } #[derive(Debug, Clone)] pub struct FlatVDom<'a> { pub nodes: Vec>, pub properties: Vec<(String, String)>, } impl<'a> FlatVDom<'a> { pub fn new() -> Self { Self { nodes: Vec::new(), properties: Vec::new(), } } pub fn from_elements(elements: &[Element<'a>]) -> Self { let mut fv = FlatVDom::new(); fv.append_elements(elements); fv } fn append_elements(&mut self, elements: &[Element<'a>]) -> Range { let start = self.nodes.len(); for el in elements { let node_idx = self.nodes.len(); let prop_start = self.properties.len(); for (k, v) in &el.properties { self.properties.push((k.clone().into_owned(), v.clone().into_owned())); } self.nodes.push(VNode { id: 0, type_name: el.type_name, properties: prop_start..self.properties.len(), children_range: 0..0, computed_style: el.computed_style, element_id: el.element_id, }); let child_range = self.append_elements(&el.children); self.nodes[node_idx].children_range = child_range; } start..self.nodes.len() } pub fn into_elements(self) -> Vec> { Self::nodes_to_elements(&self.nodes, &self.properties, 0..self.nodes.len()) } fn nodes_to_elements(nodes: &[VNode<'a>], props: &[(String, String)], range: Range) -> Vec> { let mut result = Vec::with_capacity(range.len()); for idx in range { let vn = &nodes[idx]; let mut el = Element::new(vn.type_name); el.element_id = vn.element_id; el.computed_style = vn.computed_style; for i in vn.properties.clone() { if i < props.len() { let (k, v) = &props[i]; el.push_prop(k.clone(), v.clone()); } } let child_range = vn.children_range.clone(); el.children = Self::nodes_to_elements(nodes, props, child_range); result.push(el); } result } pub fn get_node(&self, idx: NodeIdx) -> Option<&VNode<'a>> { self.nodes.get(idx) } pub fn node_count(&self) -> usize { self.nodes.len() } } #[derive(Debug)] pub enum InterpError { BadMagic, UnexpectedEof, InvalidUtf8, UnexpectedPop, } impl fmt::Display for InterpError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BadMagic => write!(f, "Bad magic bytes — not a .glbc file"), Self::UnexpectedEof => write!(f, "Unexpected end of bytecode"), Self::InvalidUtf8 => write!(f, "String is not valid UTF-8"), Self::UnexpectedPop => write!(f, "OP_ELEM_POP without matching OP_ELEM_PUSH"), } } } impl std::error::Error for InterpError {}