From 98395705b402f396449b1b6f8f69e0f9d41596a7 Mon Sep 17 00:00:00 2001 From: Togglebit Date: Fri, 12 Sep 2025 10:44:55 +0200 Subject: [PATCH 01/25] BUGFIX: template reload no longer fails on globals --- anathema-templates/src/document.rs | 1 + anathema-templates/src/variables.rs | 52 ++++++++++++++++++++++++++--- anathema-widgets/src/nodes/loops.rs | 7 ++-- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/anathema-templates/src/document.rs b/anathema-templates/src/document.rs index d5ff8698..e1d84208 100644 --- a/anathema-templates/src/document.rs +++ b/anathema-templates/src/document.rs @@ -57,6 +57,7 @@ impl Document { } pub fn compile(&mut self, globals: &mut Variables) -> Result { + globals.reset_globals(); let tokens = Lexer::new(&self.template, &mut self.strings).collect::>>()?; let tokens = Tokens::new(tokens, self.template.len()); let parser = Parser::new(tokens, &mut self.strings, &self.template, &mut self.components); diff --git a/anathema-templates/src/variables.rs b/anathema-templates/src/variables.rs index 1ec7afe8..1a25cb73 100644 --- a/anathema-templates/src/variables.rs +++ b/anathema-templates/src/variables.rs @@ -6,8 +6,16 @@ use anathema_store::slab::{Slab, SlabIndex}; use crate::error::ErrorKind; use crate::expressions::Expression; +#[derive(Debug, Clone)] +enum Global { + // The global value was set from the runtime + Runtime(Expression), + // The global value originates from a template + Template(Expression), +} + #[derive(Debug, Default, Clone)] -pub struct Globals(HashMap); +pub struct Globals(HashMap); impl Globals { pub fn empty() -> Self { @@ -19,15 +27,31 @@ impl Globals { } pub fn get(&self, ident: &str) -> Option<&Expression> { - self.0.get(ident) + match self.0.get(ident)? { + Global::Runtime(expression) | Global::Template(expression) => Some(expression), + } } - pub fn set(&mut self, ident: String, value: Expression) { + fn set(&mut self, ident: String, value: Global) { if self.0.contains_key(&ident) { return; } _ = self.0.insert(ident, value); } + + fn clear_template_globals(&mut self) { + let mut clear = vec![]; + for (key, glob) in &self.0 { + match glob { + Global::Runtime(_) => continue, + Global::Template(_) => clear.push(key.to_owned()), + } + } + + for key in clear { + _ = self.0.remove(&key); + } + } } #[derive(Debug, Copy, Clone, PartialEq)] @@ -252,16 +276,34 @@ impl Variables { var_id } - pub fn define_global(&mut self, ident: impl Into, value: impl Into) -> Result<(), ErrorKind> { + fn set_global(&mut self, ident: impl Into, global: Global) -> Result<(), ErrorKind> { let ident = ident.into(); if self.globals.contains(&ident) { return Err(ErrorKind::GlobalAlreadyAssigned(ident)); } - self.globals.set(ident, value.into()); + self.globals.set(ident, global); Ok(()) } + /// Reset the globals defined in the template. + /// This keeps any globals registered through Rust + pub fn reset_globals(&mut self) { + self.globals.clear_template_globals(); + } + + pub fn register_global(&mut self, ident: impl Into, value: impl Into) -> Result<(), ErrorKind> { + let expression = value.into(); + let global = Global::Runtime(expression); + self.set_global(ident, global) + } + + pub fn define_global(&mut self, ident: impl Into, value: impl Into) -> Result<(), ErrorKind> { + let expression = value.into(); + let global = Global::Template(expression); + self.set_global(ident, global) + } + pub fn define_local(&mut self, ident: impl Into, value: impl Into) -> VarId { let value = value.into(); let scope_id = self.current.clone(); diff --git a/anathema-widgets/src/nodes/loops.rs b/anathema-widgets/src/nodes/loops.rs index 6e608cc7..38810c24 100644 --- a/anathema-widgets/src/nodes/loops.rs +++ b/anathema-widgets/src/nodes/loops.rs @@ -28,6 +28,10 @@ impl<'bp> For<'bp> { parent_widget: Option, dirty_widgets: &mut DirtyWidgets, ) -> Result<()> { + if let Some(widget) = parent_widget { + dirty_widgets.push(widget); + } + match change { Change::Inserted(index) => { // 1. Declare insert path @@ -89,9 +93,6 @@ impl<'bp> For<'bp> { // then truncate the tree self.collection.reload(ctx.attribute_storage); ctx.truncate_children(&mut tree); - if let Some(widget) = parent_widget { - dirty_widgets.push(widget); - } } } From c2ce422794d8d4bdfed7c966c670a5a7dc7c04cf Mon Sep 17 00:00:00 2001 From: Togglebit Date: Sat, 13 Sep 2025 12:43:03 +0200 Subject: [PATCH 02/25] List has clear and drain --- CHANGES.md | 1 + anathema-state/src/value/list.rs | 66 ++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1a823f74..a4c84ede 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ * `attributes.value_as::` is now available on `Attributes` * Performance improvements * New template function: `len` + * `List` now has `clear` and `retain` * 0.2.11 * FEATURE: ranges * `padding` function diff --git a/anathema-state/src/value/list.rs b/anathema-state/src/value/list.rs index 8e91549e..067d7c03 100644 --- a/anathema-state/src/value/list.rs +++ b/anathema-state/src/value/list.rs @@ -58,6 +58,8 @@ impl Default for List { /// list.push(123); /// ``` impl Value> { + // This does not trigger a change but still gives mutable access + // to the underlying list. fn with_mut(&mut self, f: F) -> U where F: FnOnce(&mut List) -> U, @@ -75,11 +77,37 @@ impl Value> { ret_val } + /// Create an empty list pub fn empty() -> Self { let list = List { inner: VecDeque::new() }; Value::new(list) } + /// Clear the list + pub fn clear(&mut self) { + self.set(List::empty()); + } + + /// Retain all values that matches the predicate + pub fn retain(&mut self, mut f: F) + where + F: FnMut(&Value) -> bool, + { + let key = self.key; + let mut index = 0; + self.with_mut(|list| { + list.inner.retain(|value| { + let retain = f(value); + if !retain { + changed(key, Change::Removed(index)); + } + index += 1; + retain + }); + }); + } + + /// Get a reference to a value pub fn get<'a>(&'a self, index: usize) -> Option> { let list = &*self.to_ref(); let value = list.get(index)?; @@ -90,6 +118,7 @@ impl Value> { Some(shared) } + /// Get a mutable reference to a value pub fn get_mut<'a>(&'a mut self, index: usize) -> Option> { let list = &*self.to_ref(); let value = list.get(index)?; @@ -184,10 +213,7 @@ impl Value> { }); } - pub fn len(&self) -> usize { - self.to_ref().len() - } - + /// Merge the list with another list. pub fn merge(&mut self, other: &mut Self) { while let Some(value) = other.pop_front() { let index = self.with_mut(|list| { @@ -200,6 +226,12 @@ impl Value> { } } + /// Return the length of the list + pub fn len(&self) -> usize { + self.to_ref().len() + } + + /// Returns true if the list is empty pub fn is_empty(&self) -> bool { self.to_ref().is_empty() } @@ -303,6 +335,32 @@ mod test { assert_eq!(*front.unwrap().to_ref(), 1); } + #[test] + fn notify_clear() { + let mut list = Value::new(List::::empty()); + list.reference().subscribe(Subscriber::ZERO); + list.clear(); + + let change = drain_changes().remove(0); + assert!(matches!(change, (_, Change::Changed))); + } + + #[test] + fn notify_retain() { + let mut list = Value::new(List::::empty()); + list.push_back(1); + list.push_back(2); + list.push_back(3); + + list.reference().subscribe(Subscriber::ZERO); + + list.retain(|val| *val.to_ref() == 1); + + let mut changes = drain_changes(); + assert!(matches!(changes.remove(0), (_, Change::Removed(2)))); + assert!(matches!(changes.remove(0), (_, Change::Removed(1)))); + } + #[test] fn notify_pop_back() { let mut list = Value::new(List::::empty()); From 4c3bac9151a521a0dca3712a6eac4809f82d7638 Mon Sep 17 00:00:00 2001 From: Togglebit Date: Mon, 15 Sep 2025 08:45:50 +0200 Subject: [PATCH 03/25] wip --- anathema-state/src/value/list.rs | 47 +++++++++++++++++++++++ anathema-state/src/value/maybe.rs | 11 ++++++ anathema-value-resolver/src/expression.rs | 9 +++-- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/anathema-state/src/value/list.rs b/anathema-state/src/value/list.rs index 067d7c03..870e0060 100644 --- a/anathema-state/src/value/list.rs +++ b/anathema-state/src/value/list.rs @@ -107,6 +107,33 @@ impl Value> { }); } + /// Return all values matching the predicate. + /// + /// Note that unlike `Vec::extract_if`, this will allocate a vector for the result + pub fn extract_if(&mut self, mut f: F) -> Vec> + where + F: FnMut(&Value) -> bool, + { + let key = self.key; + self.with_mut(|List { inner: list, .. }| { + let mut extraction = vec![]; + + let mut del = 0; + + for i in 0..list.len() { + let index = i - del; + if f(&list[index]) { + let value = list.remove(index).expect("value is present"); + extraction.push(value); + changed(key, Change::Removed(index as u32)); + del += 1; + } + } + + extraction + }) + } + /// Get a reference to a value pub fn get<'a>(&'a self, index: usize) -> Option> { let list = &*self.to_ref(); @@ -361,6 +388,26 @@ mod test { assert!(matches!(changes.remove(0), (_, Change::Removed(1)))); } + #[test] + fn notify_extract_if() { + let mut list = Value::new(List::::empty()); + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.push_back(4); + + list.reference().subscribe(Subscriber::ZERO); + + let result = list.extract_if(|val| *val.to_ref() % 2 == 0); + + let mut changes = drain_changes(); + assert!(matches!(changes.remove(0), (_, Change::Removed(2)))); + assert!(matches!(changes.remove(0), (_, Change::Removed(1)))); + + assert_eq!(*result[0].to_ref(), 2); + assert_eq!(*result[1].to_ref(), 4); + } + #[test] fn notify_pop_back() { let mut list = Value::new(List::::empty()); diff --git a/anathema-state/src/value/maybe.rs b/anathema-state/src/value/maybe.rs index 79de998e..7762ecb3 100644 --- a/anathema-state/src/value/maybe.rs +++ b/anathema-state/src/value/maybe.rs @@ -141,6 +141,8 @@ impl Value> { #[cfg(test)] mod test { use super::*; + use crate::store::testing::drain_changes; + use crate::{Change, Subscriber}; #[test] fn nullable_int() { @@ -155,4 +157,13 @@ mod test { let one = value.and_then_ref(|inner_map| inner_map.map_ref(|m| *m)).unwrap(); assert_eq!(one, 1); } + + #[test] + fn changing_value() { + let mut value = Value::new(Maybe::::none()); + value.reference().subscribe(Subscriber::ZERO); + value.to_mut().update(Some(1)); + let mut changes = drain_changes(); + assert!(matches!(changes.remove(0), (_, Change::Changed))); + } } diff --git a/anathema-value-resolver/src/expression.rs b/anathema-value-resolver/src/expression.rs index 49c78a15..06ba566d 100644 --- a/anathema-value-resolver/src/expression.rs +++ b/anathema-value-resolver/src/expression.rs @@ -349,7 +349,10 @@ fn resolve_pending<'bp>(val: PendingValue, ctx: &mut ValueResolutionContext<'_, let maybe = or_null!(state.as_maybe()); // If there is no value, subscribe to the `Maybe` let inner = match maybe.get() { - Some(inner) => inner, + Some(inner) => { + ctx.maybe_subscribe(&val); + inner + } None => { ctx.maybe_subscribe(&val); return ValueExpr::Null; @@ -377,11 +380,11 @@ fn resolve_index<'bp>( let val = map.lookup(&key); let val = match val { - Some(key) => { + Some(val) => { if ctx.is_partially_resolved() { value.unsubscribe(ctx.sub); } - key + val } None => { ctx.partially_resolved(value); From 0d524033393b32086ea895fbb4c6f85339aa1738 Mon Sep 17 00:00:00 2001 From: Togglebit Date: Tue, 30 Sep 2025 12:30:07 +0200 Subject: [PATCH 04/25] Dedupe expressions --- Cargo.toml | 7 +- anathema-default-widgets/src/border.rs | 2 +- anathema-default-widgets/src/testing.rs | 9 + anathema-runtime/src/builder.rs | 3 +- anathema-runtime/src/runtime/mod.rs | 4 +- anathema-store/src/slab/generational.rs | 14 + anathema-templates/src/blueprints.rs | 15 +- anathema-templates/src/components.rs | 7 +- anathema-templates/src/document.rs | 6 + anathema-templates/src/expressions/mod.rs | 69 +++- .../src/statements/const_eval.rs | 14 +- anathema-templates/src/statements/eval.rs | 59 ++- anathema-templates/src/statements/mod.rs | 80 ++-- anathema-templates/src/statements/parser.rs | 4 +- anathema-templates/src/variables.rs | 109 +++--- anathema-value-resolver/Cargo.toml | 1 + anathema-value-resolver/src/attributes.rs | 71 ++-- anathema-value-resolver/src/context.rs | 8 +- anathema-value-resolver/src/expression.rs | 291 +++++++------- anathema-value-resolver/src/immediate.rs | 60 +-- anathema-value-resolver/src/scope.rs | 12 +- anathema-value-resolver/src/testing.rs | 26 +- anathema-value-resolver/src/value.rs | 364 +++++++++++------- anathema-widgets/src/layout/mod.rs | 6 + anathema-widgets/src/nodes/eval.rs | 48 ++- anathema-widgets/src/testing.rs | 3 +- 26 files changed, 769 insertions(+), 523 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f20ef105..b8c3b04e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,12 @@ anathema-testutils = { path = "anathema-testutils" } [features] default = [] -profile = ["anathema-runtime/profile", "anathema-widgets/profile", "anathema-backend/profile", "anathema-value-resolver/profile"] +profile = [ + "anathema-runtime/profile", + "anathema-widgets/profile", + "anathema-backend/profile", + "anathema-value-resolver/profile", +] serde = ["anathema-state/serde", "anathema-store/serde"] # filelog = ["anathema-debug/filelog", "anathema-widgets/filelog", "anathema-runtime/filelog"] diff --git a/anathema-default-widgets/src/border.rs b/anathema-default-widgets/src/border.rs index cdf43a77..bbeb834f 100644 --- a/anathema-default-widgets/src/border.rs +++ b/anathema-default-widgets/src/border.rs @@ -428,7 +428,7 @@ impl Widget for Border { id: WidgetId, ctx: &mut LayoutCtx<'_, 'bp>, ) -> Result { - let attributes = ctx.attribute_storage.get_mut(id); + let attributes = ctx.attribute_storage.get(id); self.sides = attributes.get_as::("sides").unwrap_or_default(); diff --git a/anathema-default-widgets/src/testing.rs b/anathema-default-widgets/src/testing.rs index b0591013..1d001e5c 100644 --- a/anathema-default-widgets/src/testing.rs +++ b/anathema-default-widgets/src/testing.rs @@ -134,6 +134,7 @@ pub struct TestRunner { variables: Variables, components: Components, function_table: FunctionTable, + doc: Document, } impl TestRunner { @@ -171,6 +172,7 @@ impl TestRunner { variables, components: Components::new(), function_table: FunctionTable::new(), + doc, } } @@ -184,6 +186,7 @@ impl TestRunner { &mut self.component_registry, &mut self.components, &self.function_table, + &self.doc, ) } } @@ -202,6 +205,7 @@ pub struct TestInstance<'bp> { changes: Changes, glyph_map: GlyphMap, function_table: &'bp FunctionTable, + doc: &'bp Document, } impl<'bp> TestInstance<'bp> { @@ -214,6 +218,7 @@ impl<'bp> TestInstance<'bp> { component_registry: &'bp mut ComponentRegistry, components: &'bp mut Components, function_table: &'bp FunctionTable, + doc: &'bp Document, ) -> Self { let mut tree = WidgetTree::empty(); let mut attribute_storage = AttributeStorage::empty(); @@ -233,6 +238,7 @@ impl<'bp> TestInstance<'bp> { &mut glyph_map, &mut viewport, function_table, + &doc.expressions, ); let mut ctx = ctx.eval_ctx(None, None); @@ -254,6 +260,7 @@ impl<'bp> TestInstance<'bp> { changes: Changes::empty(), glyph_map, function_table, + doc, } } @@ -285,6 +292,7 @@ impl<'bp> TestInstance<'bp> { &mut self.glyph_map, &mut self.viewport, self.function_table, + &self.doc.expressions, ); self.changes.iter().for_each(|(sub, change)| { @@ -331,6 +339,7 @@ impl<'bp> TestInstance<'bp> { &mut self.glyph_map, &mut self.viewport, self.function_table, + &self.doc.expressions, ); let mut cycle = WidgetCycle::new(self.backend, self.tree.view(), constraints); diff --git a/anathema-runtime/src/builder.rs b/anathema-runtime/src/builder.rs index 46b7755a..158d21c2 100644 --- a/anathema-runtime/src/builder.rs +++ b/anathema-runtime/src/builder.rs @@ -171,7 +171,8 @@ impl Builder { } pub fn register_global(&mut self, key: impl Into, value: impl Into) -> Result<()> { - self.variables.define_global(key, value).map_err(|e| e.to_error(None))?; + let id = self.document.expressions.insert_at_root(value.into()); + self.variables.define_global(key, id).map_err(|e| e.to_error(None))?; Ok(()) } diff --git a/anathema-runtime/src/runtime/mod.rs b/anathema-runtime/src/runtime/mod.rs index 224ca467..e51102f8 100644 --- a/anathema-runtime/src/runtime/mod.rs +++ b/anathema-runtime/src/runtime/mod.rs @@ -66,7 +66,8 @@ impl Runtime<()> { } pub fn register_global(&mut self, key: impl Into, value: impl Into) -> Result<()> { - self.variables.define_global(key, value).map_err(|e| e.to_error(None))?; + let id = self.document.expressions.insert_at_root(value.into()); + self.variables.define_global(key, id).map_err(|e| e.to_error(None))?; Ok(()) } @@ -239,6 +240,7 @@ impl Runtime { &mut self.glyph_map, &mut self.viewport, &self.function_table, + &self.document.expressions, ); let inst = Frame { diff --git a/anathema-store/src/slab/generational.rs b/anathema-store/src/slab/generational.rs index fb7f33a6..faa95857 100644 --- a/anathema-store/src/slab/generational.rs +++ b/anathema-store/src/slab/generational.rs @@ -460,6 +460,20 @@ where } } +// ----------------------------------------------------------------------------- +// - Index - +// ----------------------------------------------------------------------------- +impl std::ops::Index for GenSlab { + type Output = T; + + fn index(&self, index: Key) -> &Self::Output { + match self.get(index) { + Some(val) => val, + None => panic!("invalid index or generation"), + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/anathema-templates/src/blueprints.rs b/anathema-templates/src/blueprints.rs index 6652f18b..852db556 100644 --- a/anathema-templates/src/blueprints.rs +++ b/anathema-templates/src/blueprints.rs @@ -1,28 +1,29 @@ use anathema_store::smallmap::SmallMap; use anathema_store::storage::strings::StringId; +use crate::ComponentBlueprintId; use crate::components::AssocEventMapping; -use crate::{ComponentBlueprintId, Expression}; +use crate::expressions::ExpressionId; #[derive(Debug, Clone, PartialEq)] pub struct Single { pub ident: String, pub children: Vec, - pub attributes: SmallMap, - pub value: Option, + pub attributes: SmallMap, + pub value: Option, } #[derive(Debug, Clone, PartialEq)] pub struct For { pub binding: String, - pub data: Expression, + pub data: ExpressionId, pub body: Vec, } #[derive(Debug, Clone, PartialEq)] pub struct With { pub binding: String, - pub data: Expression, + pub data: ExpressionId, pub body: Vec, } @@ -34,7 +35,7 @@ pub struct ControlFlow { #[derive(Debug, Clone, PartialEq)] pub struct Else { - pub cond: Option, + pub cond: Option, pub body: Vec, } @@ -44,7 +45,7 @@ pub struct Component { pub name_id: StringId, pub id: ComponentBlueprintId, pub body: Vec, - pub attributes: SmallMap, + pub attributes: SmallMap, pub assoc_functions: Vec, /// The parent component in the blueprint pub parent: Option, diff --git a/anathema-templates/src/components.rs b/anathema-templates/src/components.rs index 0323c44b..dc9e483b 100644 --- a/anathema-templates/src/components.rs +++ b/anathema-templates/src/components.rs @@ -10,6 +10,7 @@ use anathema_store::storage::Storage; use crate::Lexer; use crate::blueprints::Blueprint; use crate::error::{Error, ErrorKind, Result}; +use crate::expressions::Expressions; use crate::statements::eval::Scope; use crate::statements::parser::Parser; use crate::statements::{Context, Statements}; @@ -223,6 +224,7 @@ impl ComponentTemplates { variables: &mut Variables, slots: SmallMap>, strings: &mut Strings, + expressions: &mut Expressions, ) -> Result> { let ticket = self.components.checkout(component_id); let (_, component_src) = &*ticket; @@ -238,7 +240,7 @@ impl ComponentTemplates { // NOTE // The ticket has to be restored to the component store, // this is why the error is returned rather than using `?` on `self.compile`. - let ret = self.compile(component_src, variables, slots, strings, component_id); + let ret = self.compile(component_src, variables, slots, strings, expressions, component_id); self.components.restore(ticket); self.dependencies.pop(); ret @@ -250,6 +252,7 @@ impl ComponentTemplates { variables: &mut Variables, slots: SmallMap>, strings: &mut Strings, + expressions: &mut Expressions, parent: ComponentBlueprintId, ) -> Result> { let tokens = Lexer::new(template, strings).collect::>>()?; @@ -258,7 +261,7 @@ impl ComponentTemplates { let statements = parser.collect::>()?; - let mut context = Context::new(template, variables, self, strings, slots, Some(parent)); + let mut context = Context::new(template, variables, self, strings, expressions, slots, Some(parent)); Scope::new(statements).eval(&mut context) } diff --git a/anathema-templates/src/document.rs b/anathema-templates/src/document.rs index e1d84208..a87e36f3 100644 --- a/anathema-templates/src/document.rs +++ b/anathema-templates/src/document.rs @@ -6,6 +6,7 @@ use anathema_store::smallmap::SmallMap; use crate::blueprints::Blueprint; use crate::components::{ComponentTemplates, SourceKind, TemplateSource}; use crate::error::{Error, ErrorKind, Result}; +use crate::expressions::Expressions; use crate::statements::eval::Scope; use crate::statements::parser::Parser; use crate::statements::{Context, Statements}; @@ -21,6 +22,7 @@ use crate::{ComponentBlueprintId, Lexer, Variables}; pub struct Document { template: TemplateSource, pub strings: Strings, + pub expressions: Expressions, components: ComponentTemplates, pub hot_reload: bool, } @@ -31,6 +33,7 @@ impl Document { Self { template, strings: Strings::new(), + expressions: Expressions::empty(), components: ComponentTemplates::new(), hot_reload: true, } @@ -58,6 +61,8 @@ impl Document { pub fn compile(&mut self, globals: &mut Variables) -> Result { globals.reset_globals(); + self.expressions.clear(); + let tokens = Lexer::new(&self.template, &mut self.strings).collect::>>()?; let tokens = Tokens::new(tokens, self.template.len()); let parser = Parser::new(tokens, &mut self.strings, &self.template, &mut self.components); @@ -68,6 +73,7 @@ impl Document { template: &self.template, variables: globals, strings: &mut self.strings, + expressions: &mut self.expressions, components: &mut self.components, slots: SmallMap::empty(), current_component_parent: None, diff --git a/anathema-templates/src/expressions/mod.rs b/anathema-templates/src/expressions/mod.rs index 83de162e..34d0e856 100644 --- a/anathema-templates/src/expressions/mod.rs +++ b/anathema-templates/src/expressions/mod.rs @@ -2,13 +2,80 @@ use std::collections::HashMap; use std::fmt::Display; use anathema_state::Hex; +use anathema_store::slab::Index; use crate::primitives::Primitive; -use crate::variables::VarId; +use crate::variables::{ScopeId, VarId}; pub(crate) mod eval; pub(crate) mod parser; +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ExpressionId(u32); + +impl From for Index { + fn from(value: ExpressionId) -> Self { + value.0.into() + } +} + +#[derive(Debug)] +pub struct Expressions { + inner: Vec<(Expression, ScopeId)>, +} + +impl Expressions { + pub fn empty() -> Self { + Self { inner: vec![] } + } + + /// Get a reference to an expression + /// + /// # Panics + /// + /// Panics if the expression id is greater than the length of expressions. + /// This should never happen as the ids are assigned to blueprints using them. + /// + /// Only time this could happen is if the expression id is created outside of the tempalte + /// generation. + pub fn get(&self, id: ExpressionId) -> &Expression { + &self.inner[id.0 as usize].0 + } + + /// Insert an expression in the root scope + pub fn insert_at_root(&mut self, expression: impl Into) -> ExpressionId { + self.insert(expression.into(), ScopeId::root().clone()) + } + + /// Insert an expression and return the id to the newly inserted expression + pub fn insert(&mut self, expression: Expression, boundary: ScopeId) -> ExpressionId { + let id = ExpressionId(self.inner.len() as u32); + match self + .inner + .iter() + .position(|(e, scope)| expression.eq(e) && boundary.eq(scope)) + { + Some(id) => ExpressionId(id as u32), + None => { + self.inner.push((expression, boundary)); + id + } + } + } + + pub(crate) fn clear(&mut self) { + self.inner.clear() + } +} + +impl std::ops::Index for Expressions { + type Output = Expression; + + fn index(&self, index: ExpressionId) -> &Self::Output { + &self.inner[index.0 as usize].0 + } +} + #[derive(Debug, Copy, Clone, PartialEq)] pub enum Op { Add, diff --git a/anathema-templates/src/statements/const_eval.rs b/anathema-templates/src/statements/const_eval.rs index b45b768c..04ab4795 100644 --- a/anathema-templates/src/statements/const_eval.rs +++ b/anathema-templates/src/statements/const_eval.rs @@ -35,7 +35,7 @@ fn eval_path(expr: Expression, ctx: &Context<'_>) -> Option { } // Returning `None` here means we evaluated a const expression but the expression didn't exist, -// e.g indexing outside of a list of primitives. +// e.g indexing outside of a list of primitives or refering to state value / attributes. pub(crate) fn const_eval(expr: impl Into, ctx: &Context<'_>) -> Option { use {Expression as E, Primitive as P}; @@ -54,11 +54,11 @@ pub(crate) fn const_eval(expr: impl Into, ctx: &Context<'_>) -> Opti Some(expr) => E::Either(expr.into(), ce!(second)), None => return None, }, - E::Not(expr) => E::Not(ce!(*expr)), - E::Negative(expr) => E::Negative(ce!(*expr)), - E::Equality(lhs, rhs, eq) => E::Equality(ce!(*lhs), ce!(*rhs), eq), - E::LogicalOp(lhs, rhs, op) => E::LogicalOp(ce!(*lhs), ce!(*rhs), op), - E::Range(from, to) => E::Range(ce!(*from), ce!(*to)), + E::Not(expr) => E::Not(ce!(expr)), + E::Negative(expr) => E::Negative(ce!(expr)), + E::Equality(lhs, rhs, eq) => E::Equality(ce!(lhs), ce!(rhs), eq), + E::LogicalOp(lhs, rhs, op) => E::LogicalOp(ce!(lhs), ce!(rhs), op), + E::Range(from, to) => E::Range(ce!(from), ce!(to)), E::Ident(_) | E::Index(..) => eval_path(expr, ctx)?, E::Variable(_) => unreachable!("const eval is not recursive so this can never happen"), @@ -76,7 +76,7 @@ pub(crate) fn const_eval(expr: impl Into, ctx: &Context<'_>) -> Opti let hm = HashMap::from_iter(map.into_iter().flat_map(|(k, v)| Some((k, ce!(v))))); E::Map(hm) } - E::Op(lhs, rhs, op) => match (ce!(*lhs), ce!(*rhs)) { + E::Op(lhs, rhs, op) => match (ce!(lhs), ce!(rhs)) { (E::Primitive(P::Int(lhs)), E::Primitive(P::Int(rhs))) => { let val = match op { Op::Add => lhs + rhs, diff --git a/anathema-templates/src/statements/eval.rs b/anathema-templates/src/statements/eval.rs index c4852626..30181d29 100644 --- a/anathema-templates/src/statements/eval.rs +++ b/anathema-templates/src/statements/eval.rs @@ -5,7 +5,7 @@ use super::const_eval::const_eval; use super::{Context, Statement, Statements}; use crate::blueprints::{Blueprint, Component, ControlFlow, Else, For, Single, With}; use crate::error::{ErrorKind, Result}; -use crate::expressions::{Equality, Expression}; +use crate::expressions::{Equality, Expression, ExpressionId}; use crate::{ComponentBlueprintId, Primitive}; pub(crate) struct Scope { @@ -52,7 +52,9 @@ impl Scope { is_global, } => { let Some(value) = const_eval(value, ctx) else { continue }; + let expr_id = ctx.insert_expression(value); let binding = ctx.strings.get_unchecked(binding); + // TODO: have a list of keywords here that we fail on if binding == "state" { return Err( ErrorKind::InvalidStatement(format!("{binding} is a reserved identifier")) @@ -60,8 +62,8 @@ impl Scope { ); } match is_global { - false => _ = ctx.variables.define_local(binding, value), - true => match ctx.variables.define_global(binding, value) { + false => _ = ctx.variables.define_local(binding, expr_id), + true => match ctx.variables.define_global(binding, expr_id) { Ok(()) => (), Err(kind) => return Err(kind.to_error(ctx.template.path())), }, @@ -91,7 +93,11 @@ impl Scope { fn eval_node(&mut self, ident: StringId, ctx: &mut Context<'_>) -> Result { let ident = ctx.strings.get_unchecked(ident); let attributes = self.eval_attributes(ctx)?; - let value = self.statements.take_value().and_then(|v| const_eval(v, ctx)); + let value = self + .statements + .take_value() + .and_then(|v| const_eval(v, ctx)) + .map(|expr| ctx.insert_expression(expr)); ctx.variables.push(); let children = self.consume_scope(ctx)?; @@ -109,21 +115,33 @@ impl Scope { fn eval_for(&mut self, binding: StringId, data: Expression, ctx: &mut Context<'_>) -> Result> { let Some(data) = const_eval(data, ctx) else { return Ok(None) }; + let expr_id = ctx.insert_expression(data); + let binding = ctx.strings.get_unchecked(binding); // add binding to globals so nothing can resolve past the binding outside of the loop - ctx.variables.declare_local(binding.clone()); + ctx.variables.declare_local(binding.clone(), ctx.expressions); let body = self.consume_scope(ctx)?; - let node = Blueprint::For(For { binding, data, body }); + let node = Blueprint::For(For { + binding, + data: expr_id, + body, + }); Ok(Some(node)) } fn eval_with(&mut self, binding: StringId, data: Expression, ctx: &mut Context<'_>) -> Result> { let Some(data) = const_eval(data, ctx) else { return Ok(None) }; + let expr_id = ctx.insert_expression(data); + let binding = ctx.strings.get_unchecked(binding); // add binding to globals so nothing can resolve past the binding outside of the loop - ctx.variables.declare_local(binding.clone()); + ctx.variables.declare_local(binding.clone(), ctx.expressions); let body = self.consume_scope(ctx)?; - let node = Blueprint::With(With { binding, data, body }); + let node = Blueprint::With(With { + binding, + data: expr_id, + body, + }); Ok(Some(node)) } @@ -132,13 +150,14 @@ impl Scope { scope.eval(ctx) } - fn eval_attributes(&mut self, ctx: &mut Context<'_>) -> Result> { + fn eval_attributes(&mut self, ctx: &mut Context<'_>) -> Result> { let mut hm = SmallMap::empty(); for (key, value) in self.statements.take_attributes() { let Some(value) = const_eval(value, ctx) else { continue }; + let expr_id = ctx.insert_expression(value); let key = ctx.strings.get_unchecked(key); - hm.set(key, value); + hm.set(key, expr_id); } Ok(hm) @@ -147,16 +166,22 @@ impl Scope { fn eval_if(&mut self, cond: Expression, ctx: &mut Context<'_>) -> Result { // Const eval fail = static false let cond = const_eval(cond, ctx).unwrap_or(Expression::Primitive(Primitive::Bool(false))); + let expr_id = ctx.insert_expression(cond); let body = self.consume_scope(ctx)?; if body.is_empty() { return Err(ErrorKind::EmptyBody.to_error(ctx.template.path())); } - let mut elses = vec![Else { cond: Some(cond), body }]; + let mut elses = vec![Else { + cond: Some(expr_id), + body, + }]; while let Some(cond) = self.statements.next_else() { let body = self.consume_scope(ctx)?; - let cond = cond.and_then(|v| const_eval(v, ctx)); + let cond = cond + .and_then(|v| const_eval(v, ctx)) + .map(|expr| ctx.insert_expression(expr)); if body.is_empty() { return Err(ErrorKind::EmptyBody.to_error(ctx.template.path())); @@ -179,6 +204,7 @@ impl Scope { Some(ref switch) => Expression::Equality(switch.clone().into(), case.into(), Equality::Eq), None => Expression::Primitive(Primitive::Bool(false)), }; + let expr_id = ctx.insert_expression(cond); let body = match body.is_next_scope() { true => body.take_scope(), @@ -186,7 +212,10 @@ impl Scope { }; let body = Scope::new(body).eval(ctx)?; - elses.push(Else { cond: Some(cond), body }); + elses.push(Else { + cond: Some(expr_id), + body, + }); } if body.next_default() { @@ -266,8 +295,8 @@ mod test { #[test] fn eval_node() { let mut doc = Document::new("node"); - let bp = doc.compile(&mut Variables::new()).unwrap(); - assert_eq!(bp, single!("node")); + let blueprint = doc.compile(&mut Variables::new()).unwrap(); + assert_eq!(blueprint, single!("node")); } #[test] diff --git a/anathema-templates/src/statements/mod.rs b/anathema-templates/src/statements/mod.rs index 76bca877..cc7a46a6 100644 --- a/anathema-templates/src/statements/mod.rs +++ b/anathema-templates/src/statements/mod.rs @@ -4,7 +4,7 @@ use crate::ComponentBlueprintId; use crate::blueprints::Blueprint; use crate::components::{AssocEventMapping, ComponentTemplates, TemplateSource}; use crate::error::Result; -use crate::expressions::Expression; +use crate::expressions::{Expression, ExpressionId, Expressions}; use crate::strings::{StringId, Strings}; use crate::variables::{VarId, Variables}; @@ -17,6 +17,7 @@ pub(crate) struct Context<'vars> { pub(crate) variables: &'vars mut Variables, pub(crate) components: &'vars mut ComponentTemplates, pub(crate) strings: &'vars mut Strings, + pub(crate) expressions: &'vars mut Expressions, pub(crate) slots: SmallMap>, pub(crate) current_component_parent: Option, } @@ -27,6 +28,7 @@ impl<'vars> Context<'vars> { variables: &'vars mut Variables, components: &'vars mut ComponentTemplates, strings: &'vars mut Strings, + expressions: &'vars mut Expressions, slots: SmallMap>, current_component_parent: Option, ) -> Self { @@ -35,10 +37,15 @@ impl<'vars> Context<'vars> { variables, components, strings, + expressions, slots, current_component_parent, } } + + fn insert_expression(&mut self, expression: Expression) -> ExpressionId { + self.expressions.insert(expression, self.variables.boundary()) + } } impl Context<'_> { @@ -55,7 +62,8 @@ impl Context<'_> { component_id: ComponentBlueprintId, slots: SmallMap>, ) -> Result> { - self.components.load(component_id, self.variables, slots, self.strings) + self.components + .load(component_id, self.variables, slots, self.strings, self.expressions) } } @@ -94,32 +102,36 @@ pub(crate) enum Statement { } #[derive(Debug, PartialEq)] -pub(crate) struct Statements(Vec); +pub(crate) struct Statements { + inner: Vec, +} impl From> for Statements { - fn from(value: Vec) -> Self { - Self(value) + fn from(statements: Vec) -> Self { + Self { inner: statements } } } impl FromIterator for Statements { fn from_iter>(iter: T) -> Self { - Self(iter.into_iter().collect()) + Self { + inner: iter.into_iter().collect(), + } } } impl Statements { fn next(&mut self) -> Option { match self.is_empty() { - false => Some(self.0.remove(0)), + false => Some(self.inner.remove(0)), true => None, } } fn take_value(&mut self) -> Option { - match matches!(&self.0.first(), Some(Statement::LoadValue(_))) { - true => match self.0.remove(0) { - Statement::LoadValue(expr) => Some(expr), + match matches!(&self.inner.first(), Some(Statement::LoadValue(_))) { + true => match self.inner.remove(0) { + Statement::LoadValue(expr_id) => Some(expr_id), _ => unreachable!(), }, false => None, @@ -128,8 +140,8 @@ impl Statements { fn take_attributes(&mut self) -> Vec<(StringId, Expression)> { let mut v = vec![]; - while matches!(&self.0.first(), Some(Statement::LoadAttribute { .. })) { - match self.0.remove(0) { + while matches!(&self.inner.first(), Some(Statement::LoadAttribute { .. })) { + match self.inner.remove(0) { Statement::LoadAttribute { key, value } => v.push((key, value)), _ => unreachable!(), } @@ -139,8 +151,8 @@ impl Statements { fn take_assoc_functions(&mut self) -> Vec { let mut v = vec![]; - while matches!(&self.0.first(), Some(Statement::AssociatedFunction { .. })) { - match self.0.remove(0) { + while matches!(&self.inner.first(), Some(Statement::AssociatedFunction { .. })) { + match self.inner.remove(0) { Statement::AssociatedFunction(map) => v.push(map), _ => unreachable!(), } @@ -153,19 +165,19 @@ impl Statements { return vec![].into(); } - if self.0[0] != Statement::ScopeStart { + if self.inner[0] != Statement::ScopeStart { return vec![].into(); } let mut level = 0; - for i in 0..self.0.len() { - match &self.0[i] { + for i in 0..self.inner.len() { + match &self.inner[i] { Statement::ScopeStart => level += 1, Statement::ScopeEnd if level - 1 == 0 => { - let mut scope = self.0.split_off(i); + let mut scope = self.inner.split_off(i); scope.remove(0); // remove the scope start - std::mem::swap(&mut scope, &mut self.0); + std::mem::swap(&mut scope, &mut self.inner); scope.remove(0); // remove the scope end return scope.into(); } @@ -184,20 +196,20 @@ impl Statements { let mut statements = vec![]; - while !self.0.is_empty() { - match self.0[0] { + while !self.inner.is_empty() { + match self.inner[0] { Statement::Case(_) | Statement::Default | Statement::Eof => break, _ => {} } - statements.push(self.0.remove(0)); + statements.push(self.inner.remove(0)); } statements.into() } fn next_else(&mut self) -> Option> { - match matches!(self.0.first(), Some(Statement::Else(_))) { - true => match self.0.remove(0) { + match matches!(self.inner.first(), Some(Statement::Else(_))) { + true => match self.inner.remove(0) { Statement::Else(cond) => Some(cond), _ => unreachable!(), }, @@ -206,8 +218,8 @@ impl Statements { } fn next_case(&mut self) -> Option { - match matches!(self.0.first(), Some(Statement::Case(_))) { - true => match self.0.remove(0) { + match matches!(self.inner.first(), Some(Statement::Case(_))) { + true => match self.inner.remove(0) { Statement::Case(cond) => Some(cond), _ => unreachable!(), }, @@ -216,9 +228,9 @@ impl Statements { } fn next_default(&mut self) -> bool { - match matches!(self.0.first(), Some(Statement::Default)) { + match matches!(self.inner.first(), Some(Statement::Default)) { true => { - _ = self.0.remove(0); + _ = self.inner.remove(0); true } false => false, @@ -226,8 +238,8 @@ impl Statements { } fn next_slot(&mut self) -> Option { - match matches!(self.0.first(), Some(Statement::ComponentSlot(_))) { - true => match self.0.remove(0) { + match matches!(self.inner.first(), Some(Statement::ComponentSlot(_))) { + true => match self.inner.remove(0) { Statement::ComponentSlot(slot_id) => Some(slot_id), _ => unreachable!(), }, @@ -236,15 +248,15 @@ impl Statements { } fn is_next_slot(&mut self) -> bool { - matches!(self.0.first(), Some(Statement::ComponentSlot(_))) + matches!(self.inner.first(), Some(Statement::ComponentSlot(_))) } fn is_next_scope(&mut self) -> bool { - matches!(self.0.first(), Some(Statement::ScopeStart)) + matches!(self.inner.first(), Some(Statement::ScopeStart)) } fn is_empty(&self) -> bool { - self.0.is_empty() + self.inner.is_empty() } } @@ -256,6 +268,7 @@ where { let mut globals = Variables::new(); let mut strings = Strings::new(); + let mut expressions = Expressions::empty(); let mut components = ComponentTemplates::new(); let template_source = TemplateSource::Static(""); @@ -263,6 +276,7 @@ where template: &template_source, variables: &mut globals, strings: &mut strings, + expressions: &mut expressions, components: &mut components, slots: SmallMap::empty(), current_component_parent: None, diff --git a/anathema-templates/src/statements/parser.rs b/anathema-templates/src/statements/parser.rs index 9255abee..a423ef87 100644 --- a/anathema-templates/src/statements/parser.rs +++ b/anathema-templates/src/statements/parser.rs @@ -42,12 +42,12 @@ pub(crate) struct Parser<'src, 'strings, 'components> { done: bool, } -impl<'src, 'strings, 'view> Parser<'src, 'strings, 'view> { +impl<'src, 'strings, 'components> Parser<'src, 'strings, 'components> { pub(crate) fn new( mut tokens: Tokens, strings: &'strings mut Strings, src: &'src TemplateSource, - components: &'view mut ComponentTemplates, + components: &'components mut ComponentTemplates, ) -> Self { tokens.consume_newlines(); let base_indent = match tokens.peek() { diff --git a/anathema-templates/src/variables.rs b/anathema-templates/src/variables.rs index 1a25cb73..70a214d8 100644 --- a/anathema-templates/src/variables.rs +++ b/anathema-templates/src/variables.rs @@ -4,14 +4,14 @@ use std::sync::OnceLock; use anathema_store::slab::{Slab, SlabIndex}; use crate::error::ErrorKind; -use crate::expressions::Expression; +use crate::expressions::{Expression, ExpressionId, Expressions}; -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] enum Global { // The global value was set from the runtime - Runtime(Expression), + Runtime(ExpressionId), // The global value originates from a template - Template(Expression), + Template(ExpressionId), } #[derive(Debug, Default, Clone)] @@ -26,8 +26,8 @@ impl Globals { self.0.contains_key(ident) } - pub fn get(&self, ident: &str) -> Option<&Expression> { - match self.0.get(ident)? { + pub fn get(&self, ident: &str) -> Option { + match self.0.get(ident).copied()? { Global::Runtime(expression) | Global::Template(expression) => Some(expression), } } @@ -76,15 +76,15 @@ impl SlabIndex for VarId { pub enum Variable { /// A variable is defined but the value will be available at runtime, e.g `for-loops` and /// `with` - Definition(Expression), + Definition(ExpressionId), /// A value is declared, either as a local value or a global value - Declaration(Expression), + Declaration(ExpressionId), } impl Variable { - fn as_expression(&self) -> &Expression { + fn as_expression(&self) -> ExpressionId { match self { - Variable::Definition(expr) | Variable::Declaration(expr) => expr, + Variable::Definition(expr) | Variable::Declaration(expr) => *expr, } } } @@ -96,7 +96,7 @@ impl Variable { pub struct ScopeId(Box<[u16]>); impl ScopeId { - fn root() -> &'static Self { + pub(crate) fn root() -> &'static Self { static ROOT: OnceLock = OnceLock::new(); ROOT.get_or_init(|| ScopeId(Box::new([]))) } @@ -220,10 +220,10 @@ impl Declarations { Self(HashMap::new()) } - fn add(&mut self, ident: impl Into, id: impl Into, value_id: impl Into) { + fn add(&mut self, ident: impl Into, scope_id: impl Into, value_id: impl Into) { let value_id = value_id.into(); let ids = self.0.entry(ident.into()).or_default(); - ids.push((id.into(), value_id)); + ids.push((scope_id.into(), value_id)); } // Get the scope id that is closest to the argument @@ -292,36 +292,40 @@ impl Variables { self.globals.clear_template_globals(); } - pub fn register_global(&mut self, ident: impl Into, value: impl Into) -> Result<(), ErrorKind> { - let expression = value.into(); - let global = Global::Runtime(expression); + pub fn register_global( + &mut self, + ident: impl Into, + value: impl Into, + expressions: &mut Expressions, + ) -> Result<(), ErrorKind> { + let id = expressions.insert(value.into(), ScopeId::root().clone()); + let global = Global::Runtime(id); self.set_global(ident, global) } - pub fn define_global(&mut self, ident: impl Into, value: impl Into) -> Result<(), ErrorKind> { - let expression = value.into(); + pub fn define_global(&mut self, ident: impl Into, expression: ExpressionId) -> Result<(), ErrorKind> { let global = Global::Template(expression); self.set_global(ident, global) } - pub fn define_local(&mut self, ident: impl Into, value: impl Into) -> VarId { - let value = value.into(); + pub fn define_local(&mut self, ident: impl Into, value: ExpressionId) -> VarId { let scope_id = self.current.clone(); let var_id = self.store.insert(Variable::Declaration(value)); self.declare_at(ident, var_id, scope_id) } - pub fn declare_local(&mut self, ident: impl Into) -> VarId { + pub fn declare_local(&mut self, ident: impl Into, expressions: &mut Expressions) -> VarId { + let scope_id = self.current.clone(); let ident = ident.into(); - let value = Variable::Definition(Expression::Ident(ident.clone())); + let id = expressions.insert(Expression::Ident(ident.clone()), scope_id.clone()); + let value = Variable::Definition(id); let var_id = self.store.insert(value); - let scope_id = self.current.clone(); self.declare_at(ident, var_id, scope_id) } /// Fetch a value starting from the current path. pub fn fetch(&self, ident: &str) -> Option { - self.declarations.get(ident, &self.current, self.boundary()) + self.declarations.get(ident, &self.current, self.boundary_ref()) } /// Create a new scope and set that scope as a boundary. @@ -355,24 +359,28 @@ impl Variables { } /// Load a variable from the store - pub fn load(&self, var: VarId) -> Option<&Expression> { + pub fn load(&self, var: VarId) -> Option { self.store.get(var).map(Variable::as_expression) } // Fetch and load a value from its ident #[cfg(test)] - fn fetch_load(&self, ident: &str) -> Option<&Expression> { - let id = self.declarations.get(ident, &self.current, self.boundary())?; + fn fetch_load(&self, ident: &str) -> Option { + let id = self.declarations.get(ident, &self.current, self.boundary_ref())?; self.load(id) } - pub fn global_lookup(&self, ident: &str) -> Option<&Expression> { + pub fn global_lookup(&self, ident: &str) -> Option { self.globals.get(ident) } - fn boundary(&self) -> &ScopeId { + fn boundary_ref(&self) -> &ScopeId { self.boundary.last().unwrap_or(ScopeId::root()) } + + pub(crate) fn boundary(&self) -> ScopeId { + self.boundary.last().unwrap_or(ScopeId::root()).clone() + } } impl From for HashMap { @@ -394,7 +402,6 @@ impl From for HashMap { #[cfg(test)] mod test { use super::*; - use crate::expressions::num; impl From for VarId { fn from(value: usize) -> Self { @@ -434,36 +441,40 @@ mod test { #[test] fn variable_declaration() { let mut vars = Variables::new(); - let expected = Expression::from(123i64); + let mut expressions = Expressions::empty(); - vars.define_local("var", expected.clone()); + let expected = expressions.insert_at_root(Expression::from(123i64)); + vars.define_local("var", expected); let id = vars.fetch("var").unwrap(); let value = vars.load(id).unwrap(); - assert_eq!(&expected, value); + assert_eq!(expected, value); } #[test] fn shadow_value() { - let ident = "var"; let mut vars = Variables::new(); - let value_a = Expression::from("1"); - let value_b = Expression::from("2"); + let mut expressions = Expressions::empty(); + let ident = "var"; + let value_a = expressions.insert_at_root(Expression::from("1")); + let value_b = expressions.insert_at_root(Expression::from("2")); - let first_value_ref = vars.define_local(ident, value_a.clone()); - let second_value_ref = vars.define_local(ident, value_b.clone()); - assert_eq!(&value_a, vars.load(first_value_ref).unwrap()); - assert_eq!(&value_b, vars.load(second_value_ref).unwrap()); + let first_value_ref = vars.define_local(ident, value_a); + let second_value_ref = vars.define_local(ident, value_b); + assert_eq!(value_a, vars.load(first_value_ref).unwrap()); + assert_eq!(value_b, vars.load(second_value_ref).unwrap()); } #[test] fn scoping_variables_inaccessible_sibling() { // Declare a variable in a sibling and fail to access that value let mut vars = Variables::new(); + let mut expressions = Expressions::empty(); + let inaccessible = expressions.insert_at_root("inaccessible"); let ident = "var"; vars.push(); - vars.define_local(ident, "inaccessible"); + vars.define_local(ident, inaccessible); assert!(vars.fetch(ident).is_some()); vars.pop(); @@ -514,25 +525,29 @@ mod test { #[test] fn get_inside_boundary() { let mut vars = Variables::new(); + let mut expressions = Expressions::empty(); + let one = expressions.insert_at_root(1); + let two = expressions.insert_at_root(2); + let three = expressions.insert_at_root(3); // Define a variable in the root scope - _ = vars.define_local("var", 1); + _ = vars.define_local("var", one); // Create a new unique scope and boundary. // * `var` should be inaccessible from within the new scope boundary // * `outer_var` should be inaccessible to the root scope vars.push_scope_boundary(); assert!(vars.fetch("var").is_none()); - _ = vars.define_local("var", 2); - _ = vars.define_local("other_var", 3); - assert_eq!(vars.fetch_load("var").unwrap(), &*num(2)); + _ = vars.define_local("var", two); + _ = vars.define_local("other_var", three); + assert_eq!(vars.fetch_load("var").unwrap(), two); vars.push(); - assert_eq!(vars.fetch_load("other_var").unwrap(), &*num(3)); + assert_eq!(vars.fetch_load("other_var").unwrap(), three); vars.pop(); // Return to root scope vars.pop_scope_boundary(); - assert_eq!(vars.fetch_load("var").unwrap(), &*num(1)); + assert_eq!(vars.fetch_load("var").unwrap(), one); assert!(vars.fetch("other_var").is_none()); } } diff --git a/anathema-value-resolver/Cargo.toml b/anathema-value-resolver/Cargo.toml index 5fa45266..1a087756 100644 --- a/anathema-value-resolver/Cargo.toml +++ b/anathema-value-resolver/Cargo.toml @@ -15,6 +15,7 @@ anathema-store = { workspace = true } unicode-width = { workspace = true } puffin = { version = "0.19.1", features = ["web"], optional = true } puffin_http = { version = "0.16.1", optional = true } +parking_lot = "0.12.4" [features] default = [] diff --git a/anathema-value-resolver/src/attributes.rs b/anathema-value-resolver/src/attributes.rs index 8441a520..57a6bfb7 100644 --- a/anathema-value-resolver/src/attributes.rs +++ b/anathema-value-resolver/src/attributes.rs @@ -1,17 +1,16 @@ use std::borrow::Borrow; use anathema_store::slab::{Gen, SecondaryMap}; -use anathema_store::smallmap::SmallIndex; +use anathema_store::smallmap::{SmallIndex, SmallMap}; use crate::ValueKind; -use crate::expression::ValueExpr; -use crate::value::{Value, Values}; +use crate::expression::ResolvedExpr; +use crate::value::Value; type WidgetId = anathema_store::slab::Key; -#[derive(Debug, Copy, Clone, PartialEq, Default)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum ValueKey<'bp> { - #[default] Value, Attribute(&'bp str), } @@ -91,8 +90,7 @@ impl<'bp> AttributeStorage<'bp> { #[derive(Debug)] pub struct Attributes<'bp> { - pub(crate) attribs: Values<'bp>, - pub value: Option, + inner: SmallMap, Value<'bp>>, } // TODO @@ -105,8 +103,7 @@ impl<'bp> Attributes<'bp> { /// Create an empty set of attributes pub fn empty() -> Self { Self { - attribs: Values::empty(), - value: None, + inner: SmallMap::empty(), } } @@ -121,7 +118,7 @@ impl<'bp> Attributes<'bp> { pub fn set(&mut self, key: &'bp str, value: impl Into>) { let key = ValueKey::Attribute(key); let value = Value::static_val(value); - self.attribs.set(key, value); + self.inner.set(key, value); } /// Set an attribute value. @@ -133,21 +130,9 @@ impl<'bp> Attributes<'bp> { /// attributes.value_as::<&str>().unwrap(); /// ``` pub fn set_value(&mut self, value: impl Into>) { + let key = ValueKey::Value; let value = Value::static_val(value); - - match self.value { - Some(index) => match self.attribs.get_mut_with_index(index) { - Some(current) => *current = value, - None => { - let index = self.attribs.insert_with(ValueKey::Value, |_| value); - self.value = Some(index); - } - }, - None => { - let index = self.attribs.insert_with(ValueKey::Value, |_| value); - self.value = Some(index); - } - } + self.inner.set(key, value); } // This is only used for inserting values during widget creation where values originate from @@ -157,20 +142,19 @@ impl<'bp> Attributes<'bp> { where F: FnMut(SmallIndex) -> Value<'bp>, { - self.attribs.insert_with(key, f) + self.inner.insert_with(key, f) } /// Remove a value from attributes pub fn remove(&mut self, key: &'bp str) -> Option> { let key = ValueKey::Attribute(key); - self.attribs.remove(&key) + self.inner.remove(&key) } /// Get the `Value` out of attributes. /// This is always the first item pub fn value(&self) -> Option<&ValueKind<'bp>> { - let idx = self.value?; - self.attribs.get_with_index(idx).map(|val| &val.kind) + self.inner.get(&ValueKey::Value).map(|val| &val.kind) } /// Get a value as a specific type @@ -178,14 +162,13 @@ impl<'bp> Attributes<'bp> { where T: TryFrom<&'a ValueKind<'bp>>, { - let idx = self.value?; - self.attribs - .get_with_index(idx) + self.inner + .get(&ValueKey::Value) .and_then(|val| (&val.kind).try_into().ok()) } pub fn get(&self, key: &str) -> Option<&ValueKind<'bp>> { - self.attribs.get(key).map(|val| &val.kind) + self.inner.get(key).map(|val| &val.kind) } /// Get a value as a given type. @@ -204,7 +187,7 @@ impl<'bp> Attributes<'bp> { where T: TryFrom<&'a ValueKind<'bp>>, { - self.attribs.get(key).and_then(|val| (&val.kind).try_into().ok()) + self.inner.get(key).and_then(|val| (&val.kind).try_into().ok()) } /// Iterate over values of a given type @@ -223,7 +206,7 @@ impl<'bp> Attributes<'bp> { where T: TryFrom<&'a ValueKind<'bp>>, { - self.attribs + self.inner .get(key) .and_then(|val| match &val.kind { ValueKind::List(value_kinds) => { @@ -237,24 +220,24 @@ impl<'bp> Attributes<'bp> { .flatten() } - #[doc(hidden)] - /// This should only be used internally by the widgets - /// when updating a value. - pub fn get_mut_with_index(&mut self, index: SmallIndex) -> Option<&mut Value<'bp>> { - self.attribs.get_mut_with_index(index) - } - /// Iterate over attributes. /// This will skip the value pub fn iter(&self) -> impl Iterator, &ValueKind<'bp>)> { - self.attribs.iter().filter_map(|(key, val)| match key { + self.inner.iter().filter_map(|(key, val)| match key { ValueKey::Value => None, ValueKey::Attribute(_) => Some((key, &val.kind)), }) } - pub(super) fn get_value_expr(&self, key: &str) -> Option> { - let value = self.attribs.get(key)?; + #[doc(hidden)] + /// This should only be used internally by the widgets + /// when updating a value. + pub fn get_mut_with_index(&mut self, index: SmallIndex) -> Option<&mut Value<'bp>> { + self.inner.get_mut_with_index(index) + } + + pub(super) fn get_value_expr(&self, key: &str) -> Option> { + let value = self.inner.get(key)?; Some(value.expr.clone()) } } diff --git a/anathema-value-resolver/src/context.rs b/anathema-value-resolver/src/context.rs index 93457770..6523e07d 100644 --- a/anathema-value-resolver/src/context.rs +++ b/anathema-value-resolver/src/context.rs @@ -1,5 +1,6 @@ use anathema_state::States; use anathema_templates::Variables; +use anathema_templates::expressions::Expressions; use crate::AttributeStorage; use crate::functions::{Function, FunctionTable}; @@ -7,19 +8,21 @@ use crate::scope::Scope; pub struct ResolverCtx<'frame, 'bp> { pub(crate) scope: &'frame Scope<'frame, 'bp>, - pub(crate) variables: &'bp Variables, + pub(crate) variables: &'frame Variables, pub(crate) states: &'frame States, pub(crate) attribute_storage: &'frame AttributeStorage<'bp>, pub(crate) function_table: &'bp FunctionTable, + pub(crate) expressions: &'bp Expressions, } impl<'frame, 'bp> ResolverCtx<'frame, 'bp> { pub fn new( - variables: &'bp Variables, + variables: &'frame Variables, scope: &'frame Scope<'frame, 'bp>, states: &'frame States, attribute_storage: &'frame AttributeStorage<'bp>, function_table: &'bp FunctionTable, + expressions: &'bp Expressions, ) -> Self { Self { scope, @@ -27,6 +30,7 @@ impl<'frame, 'bp> ResolverCtx<'frame, 'bp> { states, attribute_storage, function_table, + expressions, } } diff --git a/anathema-value-resolver/src/expression.rs b/anathema-value-resolver/src/expression.rs index 06ba566d..9516c37a 100644 --- a/anathema-value-resolver/src/expression.rs +++ b/anathema-value-resolver/src/expression.rs @@ -14,7 +14,7 @@ macro_rules! or_null { ($val:expr) => { match $val { Some(val) => val, - None => return ValueExpr::Null, + None => return ResolvedExpr::Null, } }; } @@ -95,8 +95,11 @@ pub enum Kind { Dyn(PendingValue), } +// ----------------------------------------------------------------------------- +// - Resolved expression - +// ----------------------------------------------------------------------------- #[derive(Debug, Clone)] -pub enum ValueExpr<'bp> { +pub enum ResolvedExpr<'bp> { Bool(Kind), Char(Kind), Int(Kind), @@ -124,13 +127,13 @@ pub enum ValueExpr<'bp> { Call { fun_ptr: &'bp Function, - args: Box<[ValueExpr<'bp>]>, + args: Box<[ResolvedExpr<'bp>]>, }, Null, } -impl<'bp> From for ValueExpr<'bp> { +impl<'bp> From for ResolvedExpr<'bp> { fn from(value: Primitive) -> Self { match value { Primitive::Bool(b) => Self::Bool(Kind::Static(b)), @@ -142,7 +145,7 @@ impl<'bp> From for ValueExpr<'bp> { } } -impl<'bp> From for ValueExpr<'bp> { +impl<'bp> From for ResolvedExpr<'bp> { fn from(value: PendingValue) -> Self { match value.type_info() { Type::Int => Self::Int(Kind::Dyn(value)), @@ -165,15 +168,15 @@ impl<'bp> From for ValueExpr<'bp> { // Resolve an expression to a value kind, this is the final value in the chain pub(crate) fn resolve_value<'a, 'bp>( - value_expr: &ValueExpr<'bp>, + value_expr: &ResolvedExpr<'bp>, ctx: &mut ValueResolutionContext<'a, 'bp>, ) -> ValueKind<'bp> { match value_expr { // ----------------------------------------------------------------------------- // - Primitives - // ----------------------------------------------------------------------------- - ValueExpr::Bool(Kind::Static(b)) => ValueKind::Bool(*b), - ValueExpr::Bool(Kind::Dyn(pending)) => { + ResolvedExpr::Bool(Kind::Static(b)) => ValueKind::Bool(*b), + ResolvedExpr::Bool(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_bool() { @@ -181,8 +184,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Char(Kind::Static(c)) => ValueKind::Char(*c), - ValueExpr::Char(Kind::Dyn(pending)) => { + ResolvedExpr::Char(Kind::Static(c)) => ValueKind::Char(*c), + ResolvedExpr::Char(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_char() { @@ -190,8 +193,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Int(Kind::Static(i)) => ValueKind::Int(*i), - ValueExpr::Int(Kind::Dyn(pending)) => { + ResolvedExpr::Int(Kind::Static(i)) => ValueKind::Int(*i), + ResolvedExpr::Int(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_int() { @@ -199,8 +202,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Float(Kind::Static(f)) => ValueKind::Float(*f), - ValueExpr::Float(Kind::Dyn(pending)) => { + ResolvedExpr::Float(Kind::Static(f)) => ValueKind::Float(*f), + ResolvedExpr::Float(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_float() { @@ -208,8 +211,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Hex(Kind::Static(h)) => ValueKind::Hex(*h), - ValueExpr::Hex(Kind::Dyn(pending)) => { + ResolvedExpr::Hex(Kind::Static(h)) => ValueKind::Hex(*h), + ResolvedExpr::Hex(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_hex() { @@ -217,8 +220,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Color(Kind::Static(h)) => ValueKind::Color(*h), - ValueExpr::Color(Kind::Dyn(pending)) => { + ResolvedExpr::Color(Kind::Static(h)) => ValueKind::Color(*h), + ResolvedExpr::Color(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_color() { @@ -226,8 +229,8 @@ pub(crate) fn resolve_value<'a, 'bp>( None => ValueKind::Null, } } - ValueExpr::Str(Kind::Static(s)) => ValueKind::Str(Cow::Borrowed(s)), - ValueExpr::Str(Kind::Dyn(pending)) => { + ResolvedExpr::Str(Kind::Static(s)) => ValueKind::Str(Cow::Borrowed(s)), + ResolvedExpr::Str(Kind::Dyn(pending)) => { ctx.maybe_subscribe(pending); let Some(state) = pending.as_state() else { return ValueKind::Null }; match state.as_str() { @@ -239,16 +242,16 @@ pub(crate) fn resolve_value<'a, 'bp>( // ----------------------------------------------------------------------------- // - Operations and conditionals - // ----------------------------------------------------------------------------- - ValueExpr::Not(value_expr) => { + ResolvedExpr::Not(value_expr) => { let value = resolve_value(value_expr, ctx); ValueKind::Bool(!value.truthiness()) } - ValueExpr::Negative(value_expr) => match resolve_value(value_expr, ctx) { + ResolvedExpr::Negative(value_expr) => match resolve_value(value_expr, ctx) { ValueKind::Int(n) => ValueKind::Int(-n), ValueKind::Float(n) => ValueKind::Float(-n), _ => ValueKind::Null, }, - ValueExpr::Equality(lhs, rhs, equality) => { + ResolvedExpr::Equality(lhs, rhs, equality) => { let lhs = resolve_value(lhs, ctx); let rhs = resolve_value(rhs, ctx); let b = match equality { @@ -261,7 +264,7 @@ pub(crate) fn resolve_value<'a, 'bp>( }; ValueKind::Bool(b) } - ValueExpr::LogicalOp(lhs, rhs, logical_op) => { + ResolvedExpr::LogicalOp(lhs, rhs, logical_op) => { let ValueKind::Bool(lhs) = resolve_value(lhs, ctx) else { return ValueKind::Null }; let ValueKind::Bool(rhs) = resolve_value(rhs, ctx) else { return ValueKind::Null }; let b = match logical_op { @@ -270,21 +273,21 @@ pub(crate) fn resolve_value<'a, 'bp>( }; ValueKind::Bool(b) } - ValueExpr::Op(lhs, rhs, op) => match (resolve_value(lhs, ctx), resolve_value(rhs, ctx)) { + ResolvedExpr::Op(lhs, rhs, op) => match (resolve_value(lhs, ctx), resolve_value(rhs, ctx)) { (ValueKind::Int(lhs), ValueKind::Int(rhs)) => ValueKind::Int(int_op(lhs, rhs, *op)), (ValueKind::Int(lhs), ValueKind::Float(rhs)) => ValueKind::Float(float_op(lhs as f64, rhs, *op)), (ValueKind::Float(lhs), ValueKind::Int(rhs)) => ValueKind::Float(float_op(lhs, rhs as f64, *op)), (ValueKind::Float(lhs), ValueKind::Float(rhs)) => ValueKind::Float(float_op(lhs, rhs, *op)), _ => ValueKind::Null, }, - ValueExpr::Either(first, second) => { + ResolvedExpr::Either(first, second) => { let value = resolve_value(first, ctx); if value.truthiness() { return value; } resolve_value(second, ctx) } - ValueExpr::Range(from, to) => { + ResolvedExpr::Range(from, to) => { let from = match resolve_int(from, ctx) { Some(i) => i, None => return ValueKind::Null, @@ -300,27 +303,27 @@ pub(crate) fn resolve_value<'a, 'bp>( // ----------------------------------------------------------------------------- // - Maps, lists and maybe - // ----------------------------------------------------------------------------- - ValueExpr::Map(_) => ValueKind::Map, - ValueExpr::DynMap(map) => ValueKind::DynMap(*map), - ValueExpr::Attributes(_) => ValueKind::Attributes, - ValueExpr::DynList(value) => { + ResolvedExpr::Map(_) => ValueKind::Map, + ResolvedExpr::DynMap(map) => ValueKind::DynMap(*map), + ResolvedExpr::Attributes(_) => ValueKind::Attributes, + ResolvedExpr::DynList(value) => { ctx.maybe_subscribe(value); ValueKind::DynList(*value) } - ValueExpr::List(l) => { + ResolvedExpr::List(l) => { let values = l.iter().map(|v| resolve_value(v, ctx)).collect(); ValueKind::List(values) } - ValueExpr::Index(src, index) => { + ResolvedExpr::Index(src, index) => { let expr = resolve_index(src, index, ctx); resolve_value(&expr, ctx) } - ValueExpr::Composite(comp) => ValueKind::Composite(*comp), + ResolvedExpr::Composite(comp) => ValueKind::Composite(*comp), // ----------------------------------------------------------------------------- // - Call - // ----------------------------------------------------------------------------- - ValueExpr::Call { fun_ptr, args } => { + ResolvedExpr::Call { fun_ptr, args } => { let args = args.iter().map(|arg| resolve_value(arg, ctx)).collect::>(); fun_ptr.invoke(&args) } @@ -328,22 +331,22 @@ pub(crate) fn resolve_value<'a, 'bp>( // ----------------------------------------------------------------------------- // - Null - // ----------------------------------------------------------------------------- - ValueExpr::Null => ValueKind::Null, + ResolvedExpr::Null => ValueKind::Null, } } -fn resolve_pending<'bp>(val: PendingValue, ctx: &mut ValueResolutionContext<'_, 'bp>) -> ValueExpr<'static> { +fn resolve_pending<'bp>(val: PendingValue, ctx: &mut ValueResolutionContext<'_, 'bp>) -> ResolvedExpr<'static> { match val.type_info() { - Type::Int => ValueExpr::Int(Kind::Dyn(val)), - Type::Float => ValueExpr::Float(Kind::Dyn(val)), - Type::Char => ValueExpr::Char(Kind::Dyn(val)), - Type::String => ValueExpr::Str(Kind::Dyn(val)), - Type::Bool => ValueExpr::Bool(Kind::Dyn(val)), - Type::Hex => ValueExpr::Hex(Kind::Dyn(val)), - Type::Color => ValueExpr::Color(Kind::Dyn(val)), - Type::Map | Type::Composite => ValueExpr::DynMap(val), - Type::List => ValueExpr::DynList(val), - Type::Unit => ValueExpr::Null, + Type::Int => ResolvedExpr::Int(Kind::Dyn(val)), + Type::Float => ResolvedExpr::Float(Kind::Dyn(val)), + Type::Char => ResolvedExpr::Char(Kind::Dyn(val)), + Type::String => ResolvedExpr::Str(Kind::Dyn(val)), + Type::Bool => ResolvedExpr::Bool(Kind::Dyn(val)), + Type::Hex => ResolvedExpr::Hex(Kind::Dyn(val)), + Type::Color => ResolvedExpr::Color(Kind::Dyn(val)), + Type::Map | Type::Composite => ResolvedExpr::DynMap(val), + Type::List => ResolvedExpr::DynList(val), + Type::Unit => ResolvedExpr::Null, Type::Maybe => { let state = or_null!(val.as_state()); let maybe = or_null!(state.as_maybe()); @@ -355,7 +358,7 @@ fn resolve_pending<'bp>(val: PendingValue, ctx: &mut ValueResolutionContext<'_, } None => { ctx.maybe_subscribe(&val); - return ValueExpr::Null; + return ResolvedExpr::Null; } }; resolve_pending(inner, ctx) @@ -364,16 +367,16 @@ fn resolve_pending<'bp>(val: PendingValue, ctx: &mut ValueResolutionContext<'_, } fn resolve_index<'bp>( - src: &ValueExpr<'bp>, - index: &ValueExpr<'bp>, + src: &ResolvedExpr<'bp>, + index: &ResolvedExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>, -) -> ValueExpr<'bp> { +) -> ResolvedExpr<'bp> { match src { - ValueExpr::DynMap(value) | ValueExpr::Composite(value) => { + ResolvedExpr::DynMap(value) | ResolvedExpr::Composite(value) => { let state = or_null!(value.as_state()); let map = match state.as_any_map() { Some(map) => map, - None => return ValueExpr::Null, + None => return ResolvedExpr::Null, }; let key = or_null!(resolve_str(index, ctx)); @@ -388,18 +391,18 @@ fn resolve_index<'bp>( } None => { ctx.partially_resolved(value); - return ValueExpr::Null; + return ResolvedExpr::Null; } }; ctx.resolved(&val); resolve_pending(val, ctx) } - ValueExpr::DynList(value) => { + ResolvedExpr::DynList(value) => { let state = or_null!(value.as_state()); let list = match state.as_any_list() { Some(list) => list, - None => return ValueExpr::Null, + None => return ResolvedExpr::Null, }; let index = or_null!(resolve_int(index, ctx)); @@ -418,42 +421,43 @@ fn resolve_index<'bp>( } None => { ctx.partially_resolved(value); - return ValueExpr::Null; + return ResolvedExpr::Null; } }; resolve_pending(val, ctx) } - ValueExpr::Attributes(widget_id) => { + ResolvedExpr::Attributes(widget_id) => { let key = or_null!(resolve_str(index, ctx)); let attributes = ctx.attribute_storage.get(*widget_id); - or_null!(attributes.get_value_expr(&key)) + let value = attributes.get_value_expr(&key); + or_null!(value) } - ValueExpr::List(list) => { + ResolvedExpr::List(list) => { let index = or_null!(resolve_int(index, ctx)); match list.get(index).cloned() { Some(val) => val, - None => ValueExpr::Null, + None => ResolvedExpr::Null, } } - ValueExpr::Map(hash_map) => { + ResolvedExpr::Map(hash_map) => { let key = or_null!(resolve_str(index, ctx)); or_null!(hash_map.get(&*key).cloned()) } - ValueExpr::Index(inner_src, inner_index) => { + ResolvedExpr::Index(inner_src, inner_index) => { let src = resolve_index(inner_src, inner_index, ctx); resolve_index(&src, index, ctx) } - ValueExpr::Either(first, second) => { + ResolvedExpr::Either(first, second) => { let src = match resolve_expr(first, ctx) { - None | Some(ValueExpr::Null) => match resolve_expr(second, ctx) { - None | Some(ValueExpr::Null) => return ValueExpr::Null, + None | Some(ResolvedExpr::Null) => match resolve_expr(second, ctx) { + None | Some(ResolvedExpr::Null) => return ResolvedExpr::Null, Some(e) => e, }, Some(e) => e, }; resolve_index(&src, index, ctx) } - ValueExpr::Null => ValueExpr::Null, + ResolvedExpr::Null => ResolvedExpr::Null, // TODO: see unreachable message val => unreachable!( "resolving index: this should return null eventually: {val:?} (you probably did something like x.y on a string)" @@ -461,25 +465,28 @@ fn resolve_index<'bp>( } } -fn resolve_expr<'bp>(expr: &ValueExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>) -> Option> { +pub(crate) fn resolve_expr<'bp>( + expr: &ResolvedExpr<'bp>, + ctx: &mut ValueResolutionContext<'_, 'bp>, +) -> Option> { match expr { - ValueExpr::Either(first, second) => match resolve_expr(first, ctx) { - None | Some(ValueExpr::Null) => resolve_expr(second, ctx), + ResolvedExpr::Either(first, second) => match resolve_expr(first, ctx) { + None | Some(ResolvedExpr::Null) => resolve_expr(second, ctx), expr => expr, }, - ValueExpr::Index(src, index) => Some(resolve_index(src, index, ctx)), + ResolvedExpr::Index(src, index) => Some(resolve_index(src, index, ctx)), _ => None, } } -fn resolve_str<'bp>(index: &ValueExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>) -> Option> { +fn resolve_str<'bp>(index: &ResolvedExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>) -> Option> { match resolve_value(index, ctx) { ValueKind::Str(s) => Some(s), _ => None, } } -fn resolve_int<'bp>(index: &ValueExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>) -> Option { +fn resolve_int<'bp>(index: &ResolvedExpr<'bp>, ctx: &mut ValueResolutionContext<'_, 'bp>) -> Option { let value = resolve_value(index, ctx); match value { ValueKind::Int(index) => Some(index as usize), @@ -523,95 +530,95 @@ fn float_op(lhs: f64, rhs: f64, op: Op) -> f64 { #[cfg(test)] mod test { - use anathema_state::{Changes, Map, Maybe, States, drain_changes}; - use anathema_templates::expressions::{ident, index, num, strlit}; + // use anathema_state::{drain_changes, Changes, Map, Maybe, States}; + // use anathema_templates::expressions::{ident, index, num, strlit}; - use crate::testing::setup; + // use crate::testing::setup; - #[test] - fn subscribe_if_not_exist() { - // In this case the list is empty but it exists + // #[test] + // fn subscribe_if_not_exist() { + // // In this case the list is empty but it exists - let mut changes = Changes::empty(); - drain_changes(&mut changes); - assert!(changes.is_empty()); + // let mut changes = Changes::empty(); + // drain_changes(&mut changes); + // assert!(changes.is_empty()); - let mut states = States::new(); - setup(&mut states, Default::default(), |test| { - let expr = index(index(ident("state"), strlit("list")), num(0)); + // let mut states = States::new(); + // setup(&mut states, Default::default(), |test| { + // let expr = index(index(ident("state"), strlit("list")), num(0)); - let mut value = test.eval(&expr); + // let mut value = test.eval(*expr); - assert_eq!(value.as_int(), None); + // assert_eq!(value.as_int(), None); - test.with_state(|state| state.list.push("a")); + // test.with_state(|state| state.list.push("a")); - drain_changes(&mut changes); - for (subs, _) in changes.drain() { - for sub in subs.iter() { - if sub == value.sub { - value.reload(&test.attributes); - } - } - } + // drain_changes(&mut changes); + // for (subs, _) in changes.drain() { + // for sub in subs.iter() { + // if sub == value.sub { + // value.reload(&test.attributes); + // } + // } + // } - assert_eq!(value.as_str().unwrap(), "a"); - }); - } + // assert_eq!(value.as_str().unwrap(), "a"); + // }); + // } - #[test] - fn list_preceding_value_removed() { - let mut changes = Changes::empty(); - let mut states = States::new(); + // #[test] + // fn list_preceding_value_removed() { + // let mut changes = Changes::empty(); + // let mut states = States::new(); - setup(&mut states, Default::default(), |test| { - // state.list[1] - let expr = index(index(ident("state"), strlit("list")), num(1)); + // setup(&mut states, Default::default(), |test| { + // // state.list[1] + // let expr = index(index(ident("state"), strlit("list")), num(1)); - test.with_state(|state| { - state.list.push("a"); - state.list.push("b"); - state.list.push("c"); - }); + // test.with_state(|state| { + // state.list.push("a"); + // state.list.push("b"); + // state.list.push("c"); + // }); - let mut value = test.eval(&expr); + // let mut value = test.eval(*expr); - assert_eq!(value.as_str().unwrap(), "b"); + // assert_eq!(value.as_str().unwrap(), "b"); - test.with_state(|state| state.list.remove(0)); + // test.with_state(|state| state.list.remove(0)); - drain_changes(&mut changes); - assert!(!changes.is_empty()); + // drain_changes(&mut changes); + // assert!(!changes.is_empty()); - for (subs, _) in changes.drain() { - if subs.iter().any(|sub| sub == value.sub) { - value.reload(&test.attributes); - } - } - assert_eq!(value.as_str().unwrap(), "c"); - }); - } + // for (subs, _) in changes.drain() { + // if subs.iter().any(|sub| sub == value.sub) { + // value.reload(&test.attributes); + // } + // } + // assert_eq!(value.as_str().unwrap(), "c"); + // }); + // } - #[test] - fn optional_map_from_empty_to_value() { - let mut changes = Changes::empty(); + // #[test] + // fn optional_map_from_empty_to_value() { + // let mut changes = Changes::empty(); - let mut states = States::new(); - setup(&mut states, Default::default(), |test| { - // let expr = index(index(ident("state"), strlit("opt_map")), strlit("key")); - let expr = index(ident("state"), strlit("opt_map")); + // let mut states = States::new(); + // setup(&mut states, Default::default(), |test| { + // // let expr = index(index(ident("state"), strlit("opt_map")), strlit("key")); + // let expr = index(ident("state"), strlit("opt_map")); - let value = test.eval(&expr); - assert!(value.as_str().is_none()); + // let value = test.eval(*expr); + // assert!(value.as_str().is_none()); - test.with_state(|state| { - let mut map = Map::empty(); - map.insert("key", 123); - state.opt_map.set(Maybe::some(map)); - }); + // test.with_state(|state| { + // let mut map = Map::empty(); + // map.insert("key", 123); + // state.opt_map.set(Maybe::some(map)); + // }); - drain_changes(&mut changes); - assert!(!changes.is_empty()); - }); - } + // drain_changes(&mut changes); + // assert!(!changes.is_empty()); + // }); + // } } diff --git a/anathema-value-resolver/src/immediate.rs b/anathema-value-resolver/src/immediate.rs index fb25d450..eba2f8e7 100644 --- a/anathema-value-resolver/src/immediate.rs +++ b/anathema-value-resolver/src/immediate.rs @@ -1,7 +1,7 @@ use anathema_templates::Expression; use crate::context::ResolverCtx; -use crate::expression::{Kind, ValueExpr}; +use crate::expression::{Kind, ResolvedExpr}; pub struct Resolver<'a, 'frame, 'bp> { ctx: &'a ResolverCtx<'frame, 'bp>, @@ -12,70 +12,70 @@ impl<'a, 'frame, 'bp> Resolver<'a, 'frame, 'bp> { Self { ctx } } - fn lookup(&self, ident: &str) -> ValueExpr<'bp> { + fn lookup(&self, ident: &str) -> ResolvedExpr<'bp> { match ident { "state" => { - let Some(state_id) = self.ctx.scope.get_state() else { return ValueExpr::Null }; - let Some(state) = self.ctx.states.get(state_id) else { return ValueExpr::Null }; + let Some(state_id) = self.ctx.scope.get_state() else { return ResolvedExpr::Null }; + let Some(state) = self.ctx.states.get(state_id) else { return ResolvedExpr::Null }; let value = state.reference(); value.into() } "attributes" => { - // TODO: unwrap? Why is this okay? - let component = self.ctx.scope.get_attributes().unwrap(); - ValueExpr::Attributes(component) + let Some(component) = self.ctx.scope.get_attributes() else { return ResolvedExpr::Null }; + ResolvedExpr::Attributes(component) } ident => match self.ctx.scope.lookup(ident) { Some(value) => value, None => { - let Some(expr) = self.ctx.variables.global_lookup(ident) else { return ValueExpr::Null }; + let Some(id) = self.ctx.variables.global_lookup(ident) else { return ResolvedExpr::Null }; + let expr = self.ctx.expressions.get(id); self.resolve(expr) } }, } } - pub(crate) fn resolve(&self, expr: &'bp Expression) -> ValueExpr<'bp> { + pub(crate) fn resolve(&self, expr: &'bp Expression) -> ResolvedExpr<'bp> { match expr { - Expression::Primitive(primitive) => ValueExpr::from(*primitive), - Expression::Variable(var) => match self.ctx.variables.load(*var) { + Expression::Primitive(primitive) => ResolvedExpr::from(*primitive), + Expression::Variable(var) => match self.ctx.variables.load(*var).map(|id| self.ctx.expressions.get(id)) { Some(expr) => self.resolve(expr), - None => ValueExpr::Null, + None => ResolvedExpr::Null, }, - Expression::Str(s) => ValueExpr::Str(Kind::Static(s)), - Expression::List(vec) => ValueExpr::List(vec.iter().map(|e| self.resolve(e)).collect()), - Expression::Map(map) => ValueExpr::Map(map.iter().map(|(k, e)| (k.as_str(), self.resolve(e))).collect()), - Expression::TextSegments(vec) => ValueExpr::List(vec.iter().map(|e| self.resolve(e)).collect()), - Expression::Not(expr) => ValueExpr::Not(self.resolve(expr).into()), - Expression::Negative(expr) => ValueExpr::Negative(self.resolve(expr).into()), + Expression::Str(s) => ResolvedExpr::Str(Kind::Static(s)), + Expression::List(vec) => ResolvedExpr::List(vec.iter().map(|e| self.resolve(e)).collect()), + Expression::Map(map) => ResolvedExpr::Map(map.iter().map(|(k, e)| (k.as_str(), self.resolve(e))).collect()), + Expression::TextSegments(vec) => ResolvedExpr::List(vec.iter().map(|e| self.resolve(e)).collect()), + Expression::Not(expr) => ResolvedExpr::Not(self.resolve(expr).into()), + Expression::Negative(expr) => ResolvedExpr::Negative(self.resolve(expr).into()), Expression::Equality(lhs, rhs, equality) => { let lhs = self.resolve(lhs); let rhs = self.resolve(rhs); - ValueExpr::Equality(lhs.into(), rhs.into(), *equality) + ResolvedExpr::Equality(lhs.into(), rhs.into(), *equality) } Expression::LogicalOp(lhs, rhs, op) => { let lhs = self.resolve(lhs).into(); let rhs = self.resolve(rhs).into(); - ValueExpr::LogicalOp(lhs, rhs, *op) + ResolvedExpr::LogicalOp(lhs, rhs, *op) } Expression::Op(lhs, rhs, op) => { let lhs = self.resolve(lhs).into(); let rhs = self.resolve(rhs).into(); - ValueExpr::Op(lhs, rhs, *op) + ResolvedExpr::Op(lhs, rhs, *op) } Expression::Either(first, second) => { - ValueExpr::Either(self.resolve(first).into(), self.resolve(second).into()) + ResolvedExpr::Either(self.resolve(first).into(), self.resolve(second).into()) } Expression::Ident(ident) => self.lookup(ident), Expression::Index(source, index) => { let source = self.resolve(source); let index = self.resolve(index); - ValueExpr::Index(source.into(), index.into()) + ResolvedExpr::Index(source.into(), index.into()) } Expression::Range(from, to) => { let from = self.resolve(from); let to = self.resolve(to); - ValueExpr::Range(from.into(), to.into()) + ResolvedExpr::Range(from.into(), to.into()) } Expression::Call { fun, args } => { match &**fun { @@ -83,25 +83,25 @@ impl<'a, 'frame, 'bp> Resolver<'a, 'frame, 'bp> { Expression::Ident(fun) => match self.ctx.lookup_function(fun) { Some(fun_ptr) => { let args = args.iter().map(|arg| self.resolve(arg)).collect::>(); - ValueExpr::Call { fun_ptr, args } + ResolvedExpr::Call { fun_ptr, args } } - None => ValueExpr::Null, + None => ResolvedExpr::Null, }, // some.value.function(args) Expression::Index(lhs, rhs) => { let first_arg = self.resolve(lhs); - let Expression::Str(fun) = &**rhs else { return ValueExpr::Null }; + let Expression::Str(fun) = &**rhs else { return ResolvedExpr::Null }; match self.ctx.lookup_function(fun) { Some(fun_ptr) => { let args = std::iter::once(first_arg) .chain(args.iter().map(|arg| self.resolve(arg))) .collect::>(); - ValueExpr::Call { fun_ptr, args } + ResolvedExpr::Call { fun_ptr, args } } - None => ValueExpr::Null, + None => ResolvedExpr::Null, } } - _ => ValueExpr::Null, + _ => ResolvedExpr::Null, } } } diff --git a/anathema-value-resolver/src/scope.rs b/anathema-value-resolver/src/scope.rs index 1f4db87c..062298a9 100644 --- a/anathema-value-resolver/src/scope.rs +++ b/anathema-value-resolver/src/scope.rs @@ -1,7 +1,7 @@ use anathema_state::{PendingValue, StateId}; use anathema_store::slab::Key; -use crate::expression::{Kind, ValueExpr}; +use crate::expression::{Kind, ResolvedExpr}; use crate::{Collection, Value, ValueKind}; #[derive(Debug)] @@ -96,17 +96,17 @@ impl<'parent, 'bp> Scope<'parent, 'bp> { } } - pub(crate) fn lookup(&self, key: &str) -> Option> { + pub(crate) fn lookup(&self, key: &str) -> Option> { match self.value { Entry::WithValue(ident, val) if ident == key => Some(val.expr.clone()), - Entry::Index(_, _, loop_index) if key == "loop" => Some(ValueExpr::Int(Kind::Dyn(loop_index))), + Entry::Index(_, _, loop_index) if key == "loop" => Some(ResolvedExpr::Int(Kind::Dyn(loop_index))), Entry::Index(binding, index, _) if key == binding => { match self.parent.expect("the parent can only be a collection").value { Entry::Collection(collection) => match &collection.0.kind { ValueKind::List(_) => { - let value_expr = ValueExpr::Index( + let value_expr = ResolvedExpr::Index( collection.0.expr.clone().into(), - ValueExpr::Int(Kind::Static(index as i64)).into(), + ResolvedExpr::Int(Kind::Static(index as i64)).into(), ); Some(value_expr) } @@ -118,7 +118,7 @@ impl<'parent, 'bp> Scope<'parent, 'bp> { } &ValueKind::Range(from, to) => (from..to) .skip(index) - .map(|num| ValueExpr::Int(Kind::Static(num as i64))) + .map(|num| ResolvedExpr::Int(Kind::Static(num as i64))) .next(), _ => unreachable!("none of the other values can be a collection"), }, diff --git a/anathema-value-resolver/src/testing.rs b/anathema-value-resolver/src/testing.rs index 56f75926..21bda347 100644 --- a/anathema-value-resolver/src/testing.rs +++ b/anathema-value-resolver/src/testing.rs @@ -1,6 +1,7 @@ use anathema_state::{AnyMap, List, Map, Maybe, State, StateId, States, Subscriber, Value}; use anathema_store::slab::Key; -use anathema_templates::{Expression, Variables}; +use anathema_templates::Variables; +use anathema_templates::expressions::{ExpressionId, Expressions}; use super::*; use crate::context::ResolverCtx; @@ -61,7 +62,7 @@ impl AnyMap for TestState { } pub(crate) struct TestCase<'a, 'bp> { - variables: &'static Variables, + variables: &'static mut Variables, states: &'a mut States, pub attributes: AttributeStorage<'bp>, function_table: &'static FunctionTable, @@ -80,25 +81,24 @@ impl<'a, 'bp> TestCase<'a, 'bp> { } } - pub(crate) fn eval(&self, expr: &'bp Expression) -> crate::value::Value<'bp> { + pub(crate) fn eval(&mut self, expressions: &'bp Expressions, expr: ExpressionId) -> crate::value::Value<'bp> { let state_id = StateId::ZERO; let scope = Scope::with_component(state_id, Key::ZERO, None); - let ctx = ResolverCtx::new( + + let mut ctx = ResolverCtx::new( self.variables, &scope, self.states, &self.attributes, self.function_table, + expressions, ); - resolve(expr, &ctx, Subscriber::ZERO) + resolve(expr, &mut ctx, Subscriber::ZERO) } - pub fn set_attribute(&mut self, key: &'bp str, expr: &'bp Expression) { - let scope = Scope::with_component(StateId::ZERO, Key::ZERO, None); - self.attributes.with_mut(Key::ZERO, |attributes, storage| { - let ctx = ResolverCtx::new(self.variables, &scope, self.states, storage, self.function_table); - attributes.insert_with(ValueKey::Attribute(key), |_index| resolve(expr, &ctx, Subscriber::ZERO)); - }); + pub fn set_attribute(&mut self, key: &'bp str, value: impl Into>) { + self.attributes + .with_mut(Key::ZERO, |attributes, _| attributes.set(key, value)); } pub(crate) fn with_state(&mut self, f: F) -> U @@ -109,6 +109,10 @@ impl<'a, 'bp> TestCase<'a, 'bp> { let mut state = state.to_mut_cast::(); f(&mut state) } + + pub(crate) fn register_global(&mut self, key: &str, id: ExpressionId) { + self.variables.define_global(key, id).unwrap(); + } } pub(crate) fn setup<'bp, F>(states: &mut States, variables: Variables, mut f: F) diff --git a/anathema-value-resolver/src/value.rs b/anathema-value-resolver/src/value.rs index abe26af3..6a6aa872 100644 --- a/anathema-value-resolver/src/value.rs +++ b/anathema-value-resolver/src/value.rs @@ -2,28 +2,37 @@ use std::borrow::Cow; use std::ops::{Deref, DerefMut}; use anathema_state::{Color, Hex, PendingValue, SubTo, Subscriber, Type}; -use anathema_store::smallmap::SmallMap; +use anathema_store::slab::Key; +use anathema_store::smallmap::SmallIndex; use anathema_templates::Expression; +use anathema_templates::expressions::ExpressionId; -use crate::attributes::ValueKey; -use crate::expression::{ResolvedState, ValueExpr, ValueResolutionContext, resolve_value}; +use crate::expression::{ResolvedExpr, ResolvedState, ValueResolutionContext, resolve_value}; use crate::immediate::Resolver; use crate::{AttributeStorage, ResolverCtx}; -pub type Values<'bp> = SmallMap, Value<'bp>>; +pub fn resolve<'bp>(expr_id: ExpressionId, ctx: &mut ResolverCtx<'_, 'bp>, sub: impl Into) -> Value<'bp> { + let expr = ctx.expressions.get(expr_id); + resolve_expr(expr, ctx, sub) +} -pub fn resolve<'bp>(expr: &'bp Expression, ctx: &ResolverCtx<'_, 'bp>, sub: impl Into) -> Value<'bp> { +pub(crate) fn resolve_expr<'bp>( + expr: &'bp Expression, + ctx: &mut ResolverCtx<'_, 'bp>, + sub: impl Into, +) -> Value<'bp> { let resolver = Resolver::new(ctx); let value_expr = resolver.resolve(expr); Value::resolve(value_expr, sub.into(), ctx.attribute_storage) } pub fn resolve_collection<'bp>( - expr: &'bp Expression, - ctx: &ResolverCtx<'_, 'bp>, - sub: impl Into, + expr: ExpressionId, + ctx: &mut ResolverCtx<'_, 'bp>, + widget_id: Key, + value_index: SmallIndex, ) -> Collection<'bp> { - let value = resolve(expr, ctx, sub); + let value = resolve(expr, ctx, (widget_id, value_index)); Collection(value) } @@ -64,17 +73,19 @@ impl<'bp> Collection<'bp> { /// This should be evaluated fully for the `ValueKind` #[derive(Debug)] pub struct Value<'bp> { - pub(crate) expr: ValueExpr<'bp>, - pub(crate) sub: Subscriber, pub(crate) kind: ValueKind<'bp>, pub(crate) resolved: ResolvedState, + + // TODO: get rid of these fields once it has been moved into `ResolvedExpressions<'_>` + pub(crate) expr: ResolvedExpr<'bp>, + pub(crate) sub: Subscriber, sub_keys: SubTo, } impl<'bp> Value<'bp> { pub(crate) fn static_val(value: impl Into>) -> Self { Self { - expr: ValueExpr::Null, + expr: ResolvedExpr::Null, kind: value.into(), sub: anathema_state::Subscriber::MAX, resolved: ResolvedState::Resolved, @@ -82,7 +93,7 @@ impl<'bp> Value<'bp> { } } - pub fn resolve(expr: ValueExpr<'bp>, sub: Subscriber, attribute_storage: &AttributeStorage<'bp>) -> Self { + fn resolve(expr: ResolvedExpr<'bp>, sub: Subscriber, attribute_storage: &AttributeStorage<'bp>) -> Self { let mut ctx = ValueResolutionContext::new(attribute_storage, sub, ResolvedState::Unresolved); let kind = resolve_value(&expr, &mut ctx); @@ -90,7 +101,7 @@ impl<'bp> Value<'bp> { // This is a special edge case where the map or state is used // as a final value `Option`. // - // This would only hold value in an if-statement: + // This would only hold a meaningful value in an if-statement: // ``` // if state.opt_map // text "show this if there is a map" @@ -569,8 +580,8 @@ pub(crate) mod test { use anathema_state::{Hex, Map, Maybe, States}; use anathema_templates::Variables; use anathema_templates::expressions::{ - add, and, boolean, chr, div, either, eq, float, greater_than, greater_than_equal, hex, ident, index, less_than, - less_than_equal, list, map, modulo, mul, neg, not, num, or, strlit, sub, text_segments, + Expressions, add, and, boolean, chr, div, either, eq, float, greater_than, greater_than_equal, hex, ident, + index, less_than, less_than_equal, list, map, modulo, mul, neg, not, num, or, strlit, sub, text_segments, }; use crate::ValueKind; @@ -578,38 +589,43 @@ pub(crate) mod test { #[test] fn attribute_lookup() { - let expr = index(ident("attributes"), strlit("a")); - let int = num(123); + panic!("this fails because get_value_expr doesn't work for statically assigned attributes"); + // let mut expressions = Expressions::empty(); + // let expr = expressions.insert_at_root(index(ident("attributes"), strlit("a"))); - let mut states = States::new(); - setup(&mut states, Default::default(), |test| { - test.set_attribute("a", &int); - let value = test.eval(&expr); - assert_eq!(123, value.as_int().unwrap()); - }); + // let mut states = States::new(); + // setup(&mut states, Default::default(), |test| { + // test.set_attribute("a", 123); + // let value = test.eval(&expressions, expr); + // assert_eq!(123, value.as_int().unwrap()); + // }); } #[test] fn expr_list_dyn_index() { - let expr = index(list([1, 2, 3]), add(ident("index"), num(1))); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(list([1, 2, 3]), add(ident("index"), num(1)))); + let glob = expressions.insert_at_root(0); let mut states = States::new(); - let mut globals = Variables::new(); - globals.define_global("index", 0).unwrap(); + let globals = Variables::new(); setup(&mut states, globals, |test| { - let value = test.eval(&expr); + test.register_global("index", glob); + + let value = test.eval(&expressions, expr); assert_eq!(2, value.as_int().unwrap()); }); } #[test] fn expr_list() { - let expr = index(list([1, 2, 3]), num(0)); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(list([1, 2, 3]), num(0))); let mut states = States::new(); setup(&mut states, Default::default(), |test| { - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(1, value.as_int().unwrap()); }); } @@ -617,21 +633,21 @@ pub(crate) mod test { #[test] fn either_index() { // state[0] ? attributes[0] + let mut expressions = Expressions::empty(); let expr = either( index(index(ident("state"), strlit("list")), num(0)), index(index(ident("attributes"), strlit("list")), num(0)), ); - - let list = list([strlit("from attribute")]); + let expr = expressions.insert_at_root(expr); let mut states = States::new(); setup(&mut states, Default::default(), |test| { // Set list for attributes - test.set_attribute("list", &list); + test.set_attribute("list", vec!["from attribute"]); // Evaluate the value. // The state is not yet set so it will fall back to attributes - let mut value = test.eval(&expr); + let mut value = test.eval(&expressions, expr); assert_eq!("from attribute", value.as_str().unwrap()); // Set the state value @@ -646,49 +662,57 @@ pub(crate) mod test { #[test] fn either_then_index() { // (state ? attributes)[0] + panic!("this fails because get_value_expr doesn't work for statically assigned attributes"); - let list = list([num(123)]); - let mut states = States::new(); - setup(&mut states, Default::default(), |test| { - let expr = index( - either( - index(ident("attributes"), strlit("list")), - index(ident("state"), strlit("list")), - ), - num(0), - ); - - test.with_state(|state| state.list.push("a string")); - let value = test.eval(&expr); - assert_eq!("a string", value.as_str().unwrap()); + // let mut states = States::new(); - test.set_attribute("list", &list); - let value = test.eval(&expr); - assert_eq!(123, value.as_int().unwrap()); - }); + // let mut expressions = Expressions::empty(); + // let expr = expressions.insert_at_root(index( + // either( + // index(ident("attributes"), strlit("list")), + // index(ident("state"), strlit("list")), + // ), + // num(0), + // )); + + // setup(&mut states, Default::default(), |test| { + // test.with_state(|state| state.list.push("a string")); + // let value = test.eval(&expressions, expr); + // assert_eq!("a string", value.as_str().unwrap()); + + // test.set_attribute("list", vec![123]); + // let value = test.eval(&expressions, expr); + // assert_eq!(123, value.as_int().unwrap()); + // }); } #[test] fn either_or() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + + // There is no num_3, so use num_2 (see state) + let expr_1 = either( + index(ident("state"), strlit("num_3")), + index(ident("state"), strlit("num_2")), + ); + let expr_1 = expressions.insert_at_root(expr_1); + + // There is num, so don't use num_2 + let expr_2 = either( + index(ident("state"), strlit("num")), + index(ident("state"), strlit("num_2")), + ); + let expr_2 = expressions.insert_at_root(expr_2); + setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(1)); test.with_state(|state| state.num_2.set(2)); - // There is no c, so use b - let expr = either( - index(ident("state"), strlit("num_3")), - index(ident("state"), strlit("num_2")), - ); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr_1); assert_eq!(2, value.as_int().unwrap()); - // There is a, so don't use b - let expr = either( - index(ident("state"), strlit("num")), - index(ident("state"), strlit("num_2")), - ); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr_2); assert_eq!(1, value.as_int().unwrap()); }); } @@ -696,11 +720,12 @@ pub(crate) mod test { #[test] fn mods() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let lookup = index(ident("state"), strlit("num")); + let expr = expressions.insert_at_root(modulo(lookup, num(3))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(5)); - let lookup = index(ident("state"), strlit("num")); - let expr = modulo(lookup, num(3)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(2, value.as_int().unwrap()); }); } @@ -708,11 +733,12 @@ pub(crate) mod test { #[test] fn division() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let lookup = index(ident("state"), strlit("num")); + let expr = expressions.insert_at_root(div(lookup, num(2))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(6)); - let lookup = index(ident("state"), strlit("num")); - let expr = div(lookup, num(2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(3, value.as_int().unwrap()); }); } @@ -720,11 +746,12 @@ pub(crate) mod test { #[test] fn multiplication() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let lookup = index(ident("state"), strlit("num")); + let expr = expressions.insert_at_root(mul(lookup, num(2))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(2)); - let lookup = index(ident("state"), strlit("num")); - let expr = mul(lookup, num(2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(4, value.as_int().unwrap()); }); } @@ -732,11 +759,12 @@ pub(crate) mod test { #[test] fn subtraction() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let lookup = index(ident("state"), strlit("num")); + let expr = expressions.insert_at_root(sub(lookup, num(2))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(1)); - let lookup = index(ident("state"), strlit("num")); - let expr = sub(lookup, num(2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(-1, value.as_int().unwrap()); }); } @@ -744,11 +772,12 @@ pub(crate) mod test { #[test] fn addition() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let lookup = index(ident("state"), strlit("num")); + let expr = expressions.insert_at_root(add(lookup, num(2))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.num.set(1)); - let lookup = index(ident("state"), strlit("num")); - let expr = add(lookup, num(2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(3, value.as_int().unwrap()); }); } @@ -756,9 +785,10 @@ pub(crate) mod test { #[test] fn test_or() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(or(boolean(false), boolean(true))); setup(&mut states, Default::default(), |test| { - let is_true = or(boolean(false), boolean(true)); - let is_true = test.eval(&is_true); + let is_true = test.eval(&expressions, is_true); assert!(is_true.as_bool().unwrap()); }); } @@ -766,9 +796,10 @@ pub(crate) mod test { #[test] fn test_and() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(and(boolean(true), boolean(true))); setup(&mut states, Default::default(), |test| { - let is_true = and(boolean(true), boolean(true)); - let is_true = test.eval(&is_true); + let is_true = test.eval(&expressions, is_true); assert!(is_true.as_bool().unwrap()); }); } @@ -776,11 +807,12 @@ pub(crate) mod test { #[test] fn lte() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(less_than_equal(num(1), num(2))); + let is_also_true = expressions.insert_at_root(less_than_equal(num(1), num(1))); setup(&mut states, Default::default(), |test| { - let is_true = less_than_equal(num(1), num(2)); - let is_also_true = less_than_equal(num(1), num(1)); - let is_true = test.eval(&is_true); - let is_also_true = test.eval(&is_also_true); + let is_true = test.eval(&expressions, is_true); + let is_also_true = test.eval(&expressions, is_also_true); assert!(is_true.as_bool().unwrap()); assert!(is_also_true.as_bool().unwrap()); }); @@ -789,11 +821,12 @@ pub(crate) mod test { #[test] fn lt() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(less_than(num(1), num(2))); + let is_false = expressions.insert_at_root(less_than(num(1), num(1))); setup(&mut states, Default::default(), |test| { - let is_true = less_than(num(1), num(2)); - let is_false = less_than(num(1), num(1)); - let is_true = test.eval(&is_true); - let is_false = test.eval(&is_false); + let is_true = test.eval(&expressions, is_true); + let is_false = test.eval(&expressions, is_false); assert!(is_true.as_bool().unwrap()); assert!(!is_false.as_bool().unwrap()); }); @@ -802,11 +835,12 @@ pub(crate) mod test { #[test] fn gte() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(greater_than_equal(num(2), num(1))); + let is_also_true = expressions.insert_at_root(greater_than_equal(num(2), num(2))); setup(&mut states, Default::default(), |test| { - let is_true = greater_than_equal(num(2), num(1)); - let is_also_true = greater_than_equal(num(2), num(2)); - let is_true = test.eval(&is_true); - let is_also_true = test.eval(&is_also_true); + let is_true = test.eval(&expressions, is_true); + let is_also_true = test.eval(&expressions, is_also_true); assert!(is_true.as_bool().unwrap()); assert!(is_also_true.as_bool().unwrap()); }); @@ -815,11 +849,12 @@ pub(crate) mod test { #[test] fn gt() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(greater_than(num(2), num(1))); + let is_false = expressions.insert_at_root(greater_than(num(2), num(2))); setup(&mut states, Default::default(), |test| { - let is_true = greater_than(num(2), num(1)); - let is_false = greater_than(num(2), num(2)); - let is_true = test.eval(&is_true); - let is_false = test.eval(&is_false); + let is_true = test.eval(&expressions, is_true); + let is_false = test.eval(&expressions, is_false); assert!(is_true.as_bool().unwrap()); assert!(!is_false.as_bool().unwrap()); }); @@ -828,11 +863,12 @@ pub(crate) mod test { #[test] fn equality() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let is_true = expressions.insert_at_root(eq(num(1), num(1))); + let is_false = expressions.insert_at_root(not(eq(num(1), num(1)))); setup(&mut states, Default::default(), |test| { - let is_true = eq(num(1), num(1)); - let is_true = test.eval(&is_true); - let is_false = ¬(eq(num(1), num(1))); - let is_false = test.eval(is_false); + let is_true = test.eval(&expressions, is_true); + let is_false = test.eval(&expressions, is_false); assert!(is_true.as_bool().unwrap()); assert!(!is_false.as_bool().unwrap()); }); @@ -841,9 +877,10 @@ pub(crate) mod test { #[test] fn neg_float() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(neg(float(123.1))); setup(&mut states, Default::default(), |test| { - let expr = neg(float(123.1)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(-123.1, value.as_float().unwrap()); }); } @@ -851,9 +888,10 @@ pub(crate) mod test { #[test] fn neg_num() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(neg(num(123))); setup(&mut states, Default::default(), |test| { - let expr = neg(num(123)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(-123, value.as_int().unwrap()); }); } @@ -861,9 +899,10 @@ pub(crate) mod test { #[test] fn not_true() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(not(boolean(false))); setup(&mut states, Default::default(), |test| { - let expr = not(boolean(false)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert!(value.as_bool().unwrap()); }); } @@ -871,9 +910,10 @@ pub(crate) mod test { #[test] fn map_resolve() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(map([("a", 123), ("b", 456)])); setup(&mut states, Default::default(), |test| { - let expr = map([("a", 123), ("b", 456)]); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(ValueKind::Map, value.kind); }); } @@ -881,10 +921,11 @@ pub(crate) mod test { #[test] fn optional_map_resolve() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(ident("state"), strlit("opt_map"))); setup(&mut states, Default::default(), |test| { // At first there is no map... - let expr = index(ident("state"), strlit("opt_map")); - let mut value = test.eval(&expr); + let mut value = test.eval(&expressions, expr); assert!(matches!(value.kind, ValueKind::Null)); // ... then we insert a map @@ -903,11 +944,16 @@ pub(crate) mod test { // state[empty|full] let mut states = States::new(); let mut globals = Variables::new(); - globals.define_global("full", "string").unwrap(); + let mut expressions = Expressions::empty(); + + let id = expressions.insert_at_root("string"); + globals.define_global("full", id).unwrap(); + + let expr = index(ident("state"), either(ident("empty"), ident("full"))); + let expr = expressions.insert_at_root(expr); setup(&mut states, globals, |test| { - let expr = index(ident("state"), either(ident("empty"), ident("full"))); test.with_state(|state| state.string.set("a string")); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!("a string", value.as_str().unwrap()); }); } @@ -915,10 +961,11 @@ pub(crate) mod test { #[test] fn state_string() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(ident("state"), strlit("string"))); setup(&mut states, Default::default(), |test| { test.with_state(|state| state.string.set("a string")); - let expr = index(ident("state"), strlit("string")); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!("a string", value.as_str().unwrap()); }); } @@ -926,10 +973,11 @@ pub(crate) mod test { #[test] fn state_float() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(ident("state"), strlit("float"))); setup(&mut states, Default::default(), |test| { - let expr = index(ident("state"), strlit("float")); test.with_state(|state| state.float.set(1.2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(1.2, value.as_float().unwrap()); }); } @@ -937,17 +985,18 @@ pub(crate) mod test { #[test] fn test_either_with_state() { let mut states = States::new(); - let expr = either(index(ident("state"), strlit("num")), num(2)); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(either(index(ident("state"), strlit("num")), num(2))); setup(&mut states, Variables::new(), |test| { test.with_state(|state| state.num.set(0)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(2, value.as_int().unwrap()); }); setup(&mut states, Variables::new(), |test| { test.with_state(|state| state.num.set(1)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(1, value.as_int().unwrap()); }); } @@ -956,10 +1005,13 @@ pub(crate) mod test { fn test_either() { let mut states = States::new(); let mut globals = Variables::new(); - globals.define_global("missing", 111).unwrap(); + let mut expressions = Expressions::empty(); + let id = expressions.insert_at_root(111); + globals.define_global("missing", id).unwrap(); + let expr = expressions.insert_at_root(either(ident("missings"), num(2))); + setup(&mut states, globals, |test| { - let expr = either(ident("missings"), num(2)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(2, value.as_int().unwrap()); }); } @@ -967,9 +1019,11 @@ pub(crate) mod test { #[test] fn test_hex() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(hex((1, 2, 3))); + setup(&mut states, Default::default(), |test| { - let expr = hex((1, 2, 3)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(Hex::from((1, 2, 3)), value.as_hex().unwrap()); }); } @@ -977,9 +1031,10 @@ pub(crate) mod test { #[test] fn test_char() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(chr('x')); setup(&mut states, Default::default(), |test| { - let expr = chr('x'); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!('x', value.as_char().unwrap()); }); } @@ -987,9 +1042,10 @@ pub(crate) mod test { #[test] fn test_float() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(float(123.123)); setup(&mut states, Default::default(), |test| { - let expr = float(123.123); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(123.123, value.as_float().unwrap()); }); } @@ -997,9 +1053,10 @@ pub(crate) mod test { #[test] fn test_int() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(num(123)); setup(&mut states, Default::default(), |test| { - let expr = num(123); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(123, value.as_int().unwrap()); }); } @@ -1007,9 +1064,11 @@ pub(crate) mod test { #[test] fn test_bool() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(boolean(true)); + setup(&mut states, Default::default(), |test| { - let expr = boolean(true); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert!(value.as_bool().unwrap()); }); } @@ -1017,13 +1076,15 @@ pub(crate) mod test { #[test] fn test_dyn_list() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(index(ident("state"), strlit("list")), num(1))); + setup(&mut states, Default::default(), |test| { test.with_state(|state| { state.list.push("abc"); state.list.push("def"); }); - let expr = index(index(ident("state"), strlit("list")), num(1)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!("def", value.as_str().unwrap()); }); } @@ -1031,10 +1092,11 @@ pub(crate) mod test { #[test] fn test_expression_map_state_key() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(map([("value", 123)]), index(ident("state"), strlit("string")))); setup(&mut states, Default::default(), |test| { - let expr = index(map([("value", 123)]), index(ident("state"), strlit("string"))); test.with_state(|state| state.string.set("value")); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(123, value.as_int().unwrap()); }); } @@ -1042,9 +1104,10 @@ pub(crate) mod test { #[test] fn test_expression_map() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(map([("value", 123)]), strlit("value"))); setup(&mut states, Default::default(), |test| { - let expr = index(map([("value", 123)]), strlit("value")); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(123, value.as_int().unwrap()); }); } @@ -1052,9 +1115,11 @@ pub(crate) mod test { #[test] fn test_state_lookup() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(ident("state"), strlit("num"))); + setup(&mut states, Default::default(), |test| { - let expr = index(ident("state"), strlit("num")); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(0, value.as_int().unwrap()); }); } @@ -1062,10 +1127,11 @@ pub(crate) mod test { #[test] fn test_nested_map() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(index(index(ident("state"), strlit("map")), strlit("value"))); setup(&mut states, Default::default(), |test| { - let expr = index(index(ident("state"), strlit("map")), strlit("value")); test.with_state(|state| state.map.to_mut().insert("value", 123)); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); assert_eq!(123, value.as_int().unwrap()); }); } @@ -1073,9 +1139,11 @@ pub(crate) mod test { #[test] fn stringify() { let mut states = States::new(); + let mut expressions = Expressions::empty(); + let expr = expressions.insert_at_root(text_segments([strlit("hello"), strlit(" "), strlit("world")])); + setup(&mut states, Default::default(), |test| { - let expr = text_segments([strlit("hello"), strlit(" "), strlit("world")]); - let value = test.eval(&expr); + let value = test.eval(&expressions, expr); let mut actual = String::new(); value.strings(|st| { actual.push_str(st); diff --git a/anathema-widgets/src/layout/mod.rs b/anathema-widgets/src/layout/mod.rs index 70115479..e9ce4e88 100644 --- a/anathema-widgets/src/layout/mod.rs +++ b/anathema-widgets/src/layout/mod.rs @@ -1,6 +1,7 @@ use anathema_geometry::{Pos, Region, Size}; use anathema_state::{State, StateId, States}; use anathema_store::tree::TreeView; +use anathema_templates::expressions::Expressions; use anathema_templates::{ComponentBlueprintId, Variables}; use anathema_value_resolver::{AttributeStorage, Attributes, FunctionTable}; use display::DISPLAY; @@ -32,6 +33,7 @@ pub struct LayoutCtx<'frame, 'bp> { pub new_components: Vec<(WidgetId, StateId)>, pub stop_runtime: bool, pub(super) function_table: &'bp FunctionTable, + pub(super) expressions: &'bp Expressions, } impl<'frame, 'bp> LayoutCtx<'frame, 'bp> { @@ -46,6 +48,7 @@ impl<'frame, 'bp> LayoutCtx<'frame, 'bp> { glyph_map: &'frame mut GlyphMap, viewport: &'frame mut Viewport, function_table: &'bp FunctionTable, + expressions: &'bp Expressions, ) -> Self { Self { states, @@ -60,6 +63,7 @@ impl<'frame, 'bp> LayoutCtx<'frame, 'bp> { new_components: vec![], stop_runtime: false, function_table, + expressions, } } @@ -84,6 +88,7 @@ impl<'frame, 'bp> LayoutCtx<'frame, 'bp> { parent_widget: parent_element, new_components: &mut self.new_components, function_table: self.function_table, + expressions: self.expressions, } } @@ -118,6 +123,7 @@ pub struct EvalCtx<'frame, 'bp> { pub(super) globals: &'bp Variables, pub(super) factory: &'frame Factory, pub(super) function_table: &'bp FunctionTable, + pub(super) expressions: &'bp Expressions, pub(super) parent_component: Option, pub(super) parent_widget: Option, } diff --git a/anathema-widgets/src/nodes/eval.rs b/anathema-widgets/src/nodes/eval.rs index 5a150e47..83c9ec94 100644 --- a/anathema-widgets/src/nodes/eval.rs +++ b/anathema-widgets/src/nodes/eval.rs @@ -1,5 +1,4 @@ use anathema_geometry::Region; -use anathema_state::Subscriber; use anathema_store::slab::SlabIndex; use anathema_store::smallmap::SmallIndex; use anathema_templates::blueprints::{Blueprint, Component, ControlFlow, For, Single, With}; @@ -48,31 +47,32 @@ impl Evaluator for SingleEval { // ----------------------------------------------------------------------------- let mut attributes = Attributes::empty(); - if let Some(expr) = single.value.as_ref() { - let ctx = ResolverCtx::new( + if let Some(expr_id) = single.value.as_ref() { + let mut ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, ); - let value = attributes.insert_with(ValueKey::Value, |value_index| { - resolve(expr, &ctx, (widget_id, value_index)) + _ = attributes.insert_with(ValueKey::Value, |value_index| { + resolve(*expr_id, &mut ctx, (widget_id, value_index)) }); - attributes.value = Some(value); } - for (key, expr) in single.attributes.iter() { + for (key, expr_id) in single.attributes.iter() { attributes.insert_with(ValueKey::Attribute(key), |value_index| { - let ctx = ResolverCtx::new( + let mut ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, ); - resolve(expr, &ctx, (widget_id, value_index)) + resolve(*expr_id, &mut ctx, (widget_id, value_index)) }); } @@ -122,16 +122,21 @@ impl Evaluator for ForLoopEval { tree: &mut WidgetTreeView<'_, 'bp>, ) -> Result<()> { let transaction = tree.insert(parent); - let value_id = Subscriber::from((transaction.node_id(), SmallIndex::ZERO)); - let resolver_ctx = ResolverCtx::new( + let mut resolver_ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, + ); + let collection = resolve_collection( + for_loop.data, + &mut resolver_ctx, + transaction.node_id(), + SmallIndex::ZERO, ); - let collection = resolve_collection(&for_loop.data, &resolver_ctx, value_id); let for_loop = super::loops::For { binding: &for_loop.binding, @@ -163,16 +168,16 @@ impl Evaluator for WithEval { tree: &mut WidgetTreeView<'_, 'bp>, ) -> Result<()> { let transaction = tree.insert(parent); - let value_id = Subscriber::from((transaction.node_id(), SmallIndex::ZERO)); - let resolver_ctx = ResolverCtx::new( + let mut resolver_ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, ); - let data = resolve(&with.data, &resolver_ctx, value_id); + let data = resolve(with.data, &mut resolver_ctx, (transaction.node_id(), SmallIndex::ZERO)); let with = super::with::With { binding: &with.binding, @@ -212,19 +217,19 @@ impl Evaluator for ControlFlowEval { .iter() .enumerate() .map(|(i, e)| { - let ctx = ResolverCtx::new( + let mut ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, ); controlflow::Else { cond: e .cond - .as_ref() - .map(|cond| resolve(cond, &ctx, (widget_id, SmallIndex::from_usize(i)))), + .map(|cond| resolve(cond, &mut ctx, (widget_id, SmallIndex::from_usize(i)))), body: &e.body, show: false, } @@ -257,16 +262,17 @@ impl Evaluator for ComponentEval { let mut attributes = Attributes::empty(); - for (key, expr) in input.attributes.iter() { + for (key, expr_id) in input.attributes.iter() { attributes.insert_with(ValueKey::Attribute(key), |value_index| { - let ctx = ResolverCtx::new( + let mut ctx = ResolverCtx::new( ctx.globals, scope, ctx.states, ctx.attribute_storage, ctx.function_table, + ctx.expressions, ); - resolve(expr, &ctx, (widget_id, value_index)) + resolve(*expr_id, &mut ctx, (widget_id, value_index)) }); } diff --git a/anathema-widgets/src/testing.rs b/anathema-widgets/src/testing.rs index c2b492a7..a326996e 100644 --- a/anathema-widgets/src/testing.rs +++ b/anathema-widgets/src/testing.rs @@ -18,7 +18,7 @@ where F: for<'bp> FnMut(WidgetTreeView<'_, 'bp>, &mut AttributeStorage<'bp>), { let mut tree = WidgetTree::empty(); - let mut doc = Document::new(tpl); + let doc = Box::leak(Document::new(tpl).into()); let mut variables = Default::default(); let blueprint = doc.compile(&mut variables).unwrap(); let variables = Box::leak(Box::new(variables)); @@ -52,6 +52,7 @@ where &mut glyph_map, &mut viewport, function_table, + &doc.expressions, ); let mut ctx = layout_ctx.eval_ctx(None, None); From cd1d353927698f327188d29b3fab50d9ea3d6755 Mon Sep 17 00:00:00 2001 From: Togglebit Date: Wed, 1 Oct 2025 07:24:32 +0200 Subject: [PATCH 05/25] wip --- anathema-runtime/src/builder.rs | 2 +- anathema-widgets/src/components/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/anathema-runtime/src/builder.rs b/anathema-runtime/src/builder.rs index 158d21c2..be928f37 100644 --- a/anathema-runtime/src/builder.rs +++ b/anathema-runtime/src/builder.rs @@ -141,7 +141,7 @@ impl Builder { ) -> Result<()> where FC: 'static + Fn() -> C, - FS: 'static + FnMut() -> C::State, + FS: 'static + Fn() -> C::State, C: Component + 'static, { let id = self.document.add_component(ident, template.to_source_kind())?; diff --git a/anathema-widgets/src/components/mod.rs b/anathema-widgets/src/components/mod.rs index b7a52902..21dde957 100644 --- a/anathema-widgets/src/components/mod.rs +++ b/anathema-widgets/src/components/mod.rs @@ -21,7 +21,7 @@ pub mod deferred; pub mod events; pub type ComponentFn = dyn Fn() -> Box; -pub type StateFn = dyn FnMut() -> Box; +pub type StateFn = dyn Fn() -> Box; enum ComponentType { Component(Option>, Option>), @@ -45,10 +45,10 @@ impl ComponentRegistry { self.0.insert_at(id, comp_type); } - pub fn add_prototype(&mut self, id: ComponentBlueprintId, proto: FC, mut state: FS) + pub fn add_prototype(&mut self, id: ComponentBlueprintId, proto: FC, state: FS) where FC: 'static + Fn() -> C, - FS: 'static + FnMut() -> S, + FS: 'static + Fn() -> S, C: Component + 'static, S: State + 'static, { From 0b7cd4bf9f704ed4490e90c237f8f6a0cd0487a9 Mon Sep 17 00:00:00 2001 From: Togglebit Date: Tue, 7 Oct 2025 08:57:04 +0200 Subject: [PATCH 06/25] wip --- Cargo.toml | 35 +- .../anathema-debug}/Cargo.toml | 0 .../anathema-debug}/src/lib.rs | 0 .../anathema-default-widgets}/Cargo.toml | 0 .../src/alignment.rs | 0 .../anathema-default-widgets}/src/border.rs | 0 .../anathema-default-widgets}/src/canvas.rs | 0 .../src/container.rs | 0 .../anathema-default-widgets}/src/expand.rs | 0 .../src/layout/alignment.rs | 0 .../src/layout/border.rs | 0 .../src/layout/expand.rs | 0 .../src/layout/many.rs | 0 .../src/layout/mod.rs | 0 .../src/layout/spacers.rs | 0 .../anathema-default-widgets}/src/lib.rs | 0 .../anathema-default-widgets}/src/overflow.rs | 0 .../anathema-default-widgets}/src/padding.rs | 0 .../anathema-default-widgets}/src/position.rs | 0 .../anathema-default-widgets}/src/spacer.rs | 0 .../src/stacks/column.rs | 0 .../src/stacks/hstack.rs | 0 .../src/stacks/mod.rs | 0 .../src/stacks/row.rs | 0 .../src/stacks/vstack.rs | 0 .../src/stacks/zstack.rs | 0 .../anathema-default-widgets}/src/testing.rs | 0 .../anathema-default-widgets}/src/text.rs | 0 .../anathema-runtime}/Cargo.toml | 0 .../anathema-runtime}/src/builder.rs | 0 .../anathema-runtime}/src/error.rs | 0 .../anathema-runtime}/src/events.rs | 0 .../anathema-runtime}/src/lib.rs | 0 .../anathema-runtime}/src/renderonly.rs | 0 .../anathema-runtime}/src/runtime/error.rs | 0 .../anathema-runtime}/src/runtime/mod.rs | 0 .../anathema-runtime}/src/runtime/testing.rs | 0 .../anathema-templates}/Cargo.toml | 3 - .../anathema-templates}/src/blueprints.rs | 0 .../anathema-templates}/src/components.rs | 0 .../anathema-templates}/src/document.rs | 2 - .../anathema-templates}/src/error/mod.rs | 0 .../anathema-templates}/src/error/parse.rs | 0 .../src/expressions/eval.rs | 0 .../src/expressions/mod.rs | 0 .../src/expressions/parser.rs | 0 .../anathema-templates}/src/lexer.rs | 0 .../anathema-templates}/src/lib.rs | 0 .../anathema-templates}/src/primitives.rs | 0 .../src/statements/const_eval.rs | 0 .../src/statements/eval.rs | 0 .../anathema-templates}/src/statements/mod.rs | 0 .../src/statements/parser.rs | 0 .../anathema-templates}/src/strings.rs | 0 .../anathema-templates}/src/token.rs | 0 .../anathema-templates}/src/variables.rs | 0 .../anathema-testutils}/Cargo.toml | 0 .../anathema-testutils}/src/lib.rs | 0 _throw/anathema-ui/Cargo.toml | 12 + _throw/anathema-ui/src/component.rs | 44 + _throw/anathema-ui/src/components.rs | 49 + _throw/anathema-ui/src/constraints.rs | 2 + _throw/anathema-ui/src/elements.rs | 67 ++ _throw/anathema-ui/src/lib.rs | 33 + _throw/anathema-ui/src/nodes.rs | 15 + _throw/anathema-ui/src/query.rs | 13 + .../anathema-value-resolver}/Cargo.toml | 0 .../src/attributes.rs | 0 .../anathema-value-resolver}/src/context.rs | 0 .../src/expression.rs | 0 .../src/functions/list.rs | 0 .../src/functions/mod.rs | 0 .../src/functions/number.rs | 0 .../src/functions/string.rs | 0 .../anathema-value-resolver}/src/immediate.rs | 0 .../anathema-value-resolver}/src/lib.rs | 0 .../anathema-value-resolver}/src/scope.rs | 0 .../anathema-value-resolver}/src/testing.rs | 0 .../anathema-value-resolver}/src/value.rs | 0 .../anathema-widgets}/Cargo.toml | 14 +- .../src/components/deferred.rs | 0 .../src/components/events/key.rs | 0 .../src/components/events/mod.rs | 0 .../src/components/events/mouse.rs | 0 .../anathema-widgets}/src/components/mod.rs | 0 .../anathema-widgets}/src/container.rs | 0 .../anathema-widgets}/src/error.rs | 0 .../src/layout/constraints.rs | 0 .../anathema-widgets}/src/layout/display.rs | 0 .../anathema-widgets}/src/layout/mod.rs | 0 .../anathema-widgets}/src/layout/text.rs | 0 _throw/anathema-widgets/src/lib.rs | 27 + .../anathema-widgets}/src/nodes/component.rs | 0 .../src/nodes/controlflow.rs | 0 .../anathema-widgets}/src/nodes/element.rs | 0 .../anathema-widgets}/src/nodes/eval.rs | 0 .../anathema-widgets}/src/nodes/loops.rs | 0 .../anathema-widgets}/src/nodes/mod.rs | 0 .../anathema-widgets}/src/nodes/stringify.rs | 0 .../anathema-widgets}/src/nodes/update.rs | 0 .../anathema-widgets}/src/nodes/with.rs | 0 .../anathema-widgets}/src/paint.rs | 0 .../anathema-widgets}/src/query/components.rs | 0 .../anathema-widgets}/src/query/deferred.rs | 0 .../anathema-widgets}/src/query/elements.rs | 0 .../anathema-widgets}/src/query/mod.rs | 0 .../anathema-widgets}/src/tabindex.rs | 0 .../anathema-widgets}/src/testing.rs | 0 .../anathema-widgets}/src/tree/debug.rs | 0 .../anathema-widgets}/src/tree/mod.rs | 0 .../src/widget/attributes.rs | 0 .../anathema-widgets}/src/widget/factory.rs | 0 .../anathema-widgets}/src/widget/mod.rs | 0 .../anathema-widgets}/src/widget/style.rs | 0 anathema-backend/src/lib.rs | 210 ---- anathema-backend/src/testing/events.rs | 61 - anathema-backend/src/testing/mod.rs | 98 -- anathema-backend/src/testing/surface.rs | 69 -- anathema-backend/src/tui/buffer.rs | 453 ------- anathema-backend/src/tui/events.rs | 101 -- anathema-backend/src/tui/mod.rs | 268 ----- anathema-backend/src/tui/screen.rs | 258 ---- anathema-backend/src/tui/style.rs | 103 -- anathema-backend/src/tuiscroll/mod.rs | 88 -- anathema-core/Cargo.toml | 12 + anathema-core/src/attributes.rs | 33 + anathema-core/src/elements/eval/mod.rs | 121 ++ anathema-core/src/elements/eval/scope.rs | 15 + anathema-core/src/elements/mod.rs | 49 + anathema-core/src/layout.rs | 30 + anathema-core/src/lib.rs | 8 + anathema-core/src/nodes.rs | 155 +++ anathema-core/src/templates/blueprints.rs | 108 ++ anathema-core/src/templates/components.rs | 292 +++++ anathema-core/src/templates/document.rs | 98 ++ anathema-core/src/templates/error/mod.rs | 91 ++ anathema-core/src/templates/error/parse.rs | 118 ++ .../src/templates/expressions/eval.rs | 372 ++++++ .../src/templates/expressions/mod.rs | 407 +++++++ .../src/templates/expressions/parser.rs | 403 +++++++ anathema-core/src/templates/lexer.rs | 583 +++++++++ anathema-core/src/templates/mod.rs | 21 + anathema-core/src/templates/primitives.rs | 49 + .../src/templates/statements/const_eval.rs | 191 +++ .../src/templates/statements/eval.rs | 486 ++++++++ anathema-core/src/templates/statements/mod.rs | 404 +++++++ .../src/templates/statements/parser.rs | 1063 +++++++++++++++++ anathema-core/src/templates/strings.rs | 40 + anathema-core/src/templates/token.rs | 278 +++++ anathema-core/src/templates/variables.rs | 553 +++++++++ anathema-core/src/testing.rs | 38 + anathema-core/src/ui/component.rs | 44 + anathema-core/src/ui/components.rs | 49 + anathema-core/src/ui/constraints.rs | 2 + anathema-core/src/ui/document.rs | 60 + anathema-core/src/ui/layout.rs | 0 anathema-core/src/ui/mod.rs | 6 + anathema-core/src/ui/query.rs | 13 + anathema-default-widgets/tags | 471 -------- anathema-geometry/src/region.rs | 29 +- anathema-state/src/states.rs | 10 - anathema-store/src/lib.rs | 1 + anathema-store/src/remotecell.rs | 73 ++ anathema-store/src/slab/basic.rs | 86 +- anathema-store/src/slab/generational.rs | 9 + anathema-store/src/slab/secondary_map.rs | 26 + anathema-store/src/storage/mod.rs | 18 +- anathema-widgets/Cargo.toml | 25 +- anathema-widgets/src/border.rs | 26 + anathema-widgets/src/lib.rs | 31 +- examples/animate.rs | 79 -- examples/basic.rs | 26 - examples/buttons.rs | 128 -- examples/message-passing.rs | 90 -- examples/templates/animate/animate.aml | 11 - examples/templates/basic/basic.aml | 7 - examples/templates/buttons/button.aml | 21 - examples/templates/buttons/buttons.aml | 7 - .../message-passing/message_passing.aml | 14 - .../templates/message-passing/messages.aml | 5 - src/lib.rs | 26 +- tests/custom_function.rs | 50 - tests/if-bug-component-reuse.rs | 56 - tests/if.rs | 77 -- tests/mount_unmount.rs | 50 - tests/nested_collections.rs | 59 - tests/quit_from_context.rs | 36 - tests/resize_event.rs | 56 - tests/statechange.rs | 32 - tests/tabindex.rs | 212 ---- tests/with.rs | 27 - 191 files changed, 6694 insertions(+), 3397 deletions(-) rename {anathema-debug => _throw/anathema-debug}/Cargo.toml (100%) rename {anathema-debug => _throw/anathema-debug}/src/lib.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/Cargo.toml (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/alignment.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/border.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/canvas.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/container.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/expand.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/alignment.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/border.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/expand.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/many.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/mod.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/layout/spacers.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/lib.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/overflow.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/padding.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/position.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/spacer.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/column.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/hstack.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/mod.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/row.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/vstack.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/stacks/zstack.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/testing.rs (100%) rename {anathema-default-widgets => _throw/anathema-default-widgets}/src/text.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/Cargo.toml (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/builder.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/error.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/events.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/lib.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/renderonly.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/runtime/error.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/runtime/mod.rs (100%) rename {anathema-runtime => _throw/anathema-runtime}/src/runtime/testing.rs (100%) rename {anathema-templates => _throw/anathema-templates}/Cargo.toml (86%) rename {anathema-templates => _throw/anathema-templates}/src/blueprints.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/components.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/document.rs (98%) rename {anathema-templates => _throw/anathema-templates}/src/error/mod.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/error/parse.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/expressions/eval.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/expressions/mod.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/expressions/parser.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/lexer.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/lib.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/primitives.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/statements/const_eval.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/statements/eval.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/statements/mod.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/statements/parser.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/strings.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/token.rs (100%) rename {anathema-templates => _throw/anathema-templates}/src/variables.rs (100%) rename {anathema-testutils => _throw/anathema-testutils}/Cargo.toml (100%) rename {anathema-testutils => _throw/anathema-testutils}/src/lib.rs (100%) create mode 100644 _throw/anathema-ui/Cargo.toml create mode 100644 _throw/anathema-ui/src/component.rs create mode 100644 _throw/anathema-ui/src/components.rs create mode 100644 _throw/anathema-ui/src/constraints.rs create mode 100644 _throw/anathema-ui/src/elements.rs create mode 100644 _throw/anathema-ui/src/lib.rs create mode 100644 _throw/anathema-ui/src/nodes.rs create mode 100644 _throw/anathema-ui/src/query.rs rename {anathema-value-resolver => _throw/anathema-value-resolver}/Cargo.toml (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/attributes.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/context.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/expression.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/functions/list.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/functions/mod.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/functions/number.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/functions/string.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/immediate.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/lib.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/scope.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/testing.rs (100%) rename {anathema-value-resolver => _throw/anathema-value-resolver}/src/value.rs (100%) rename {anathema-backend => _throw/anathema-widgets}/Cargo.toml (71%) rename {anathema-widgets => _throw/anathema-widgets}/src/components/deferred.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/components/events/key.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/components/events/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/components/events/mouse.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/components/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/container.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/error.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/layout/constraints.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/layout/display.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/layout/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/layout/text.rs (100%) create mode 100644 _throw/anathema-widgets/src/lib.rs rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/component.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/controlflow.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/element.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/eval.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/loops.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/stringify.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/update.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/nodes/with.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/paint.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/query/components.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/query/deferred.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/query/elements.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/query/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/tabindex.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/testing.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/tree/debug.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/tree/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/widget/attributes.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/widget/factory.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/widget/mod.rs (100%) rename {anathema-widgets => _throw/anathema-widgets}/src/widget/style.rs (100%) delete mode 100644 anathema-backend/src/lib.rs delete mode 100644 anathema-backend/src/testing/events.rs delete mode 100644 anathema-backend/src/testing/mod.rs delete mode 100644 anathema-backend/src/testing/surface.rs delete mode 100644 anathema-backend/src/tui/buffer.rs delete mode 100644 anathema-backend/src/tui/events.rs delete mode 100644 anathema-backend/src/tui/mod.rs delete mode 100644 anathema-backend/src/tui/screen.rs delete mode 100644 anathema-backend/src/tui/style.rs delete mode 100644 anathema-backend/src/tuiscroll/mod.rs create mode 100644 anathema-core/Cargo.toml create mode 100644 anathema-core/src/attributes.rs create mode 100644 anathema-core/src/elements/eval/mod.rs create mode 100644 anathema-core/src/elements/eval/scope.rs create mode 100644 anathema-core/src/elements/mod.rs create mode 100644 anathema-core/src/layout.rs create mode 100644 anathema-core/src/lib.rs create mode 100644 anathema-core/src/nodes.rs create mode 100644 anathema-core/src/templates/blueprints.rs create mode 100644 anathema-core/src/templates/components.rs create mode 100644 anathema-core/src/templates/document.rs create mode 100644 anathema-core/src/templates/error/mod.rs create mode 100644 anathema-core/src/templates/error/parse.rs create mode 100644 anathema-core/src/templates/expressions/eval.rs create mode 100644 anathema-core/src/templates/expressions/mod.rs create mode 100644 anathema-core/src/templates/expressions/parser.rs create mode 100644 anathema-core/src/templates/lexer.rs create mode 100644 anathema-core/src/templates/mod.rs create mode 100644 anathema-core/src/templates/primitives.rs create mode 100644 anathema-core/src/templates/statements/const_eval.rs create mode 100644 anathema-core/src/templates/statements/eval.rs create mode 100644 anathema-core/src/templates/statements/mod.rs create mode 100644 anathema-core/src/templates/statements/parser.rs create mode 100644 anathema-core/src/templates/strings.rs create mode 100644 anathema-core/src/templates/token.rs create mode 100644 anathema-core/src/templates/variables.rs create mode 100644 anathema-core/src/testing.rs create mode 100644 anathema-core/src/ui/component.rs create mode 100644 anathema-core/src/ui/components.rs create mode 100644 anathema-core/src/ui/constraints.rs create mode 100644 anathema-core/src/ui/document.rs create mode 100644 anathema-core/src/ui/layout.rs create mode 100644 anathema-core/src/ui/mod.rs create mode 100644 anathema-core/src/ui/query.rs delete mode 100644 anathema-default-widgets/tags create mode 100644 anathema-store/src/remotecell.rs create mode 100644 anathema-widgets/src/border.rs delete mode 100644 examples/animate.rs delete mode 100644 examples/basic.rs delete mode 100644 examples/buttons.rs delete mode 100644 examples/message-passing.rs delete mode 100644 examples/templates/animate/animate.aml delete mode 100644 examples/templates/basic/basic.aml delete mode 100644 examples/templates/buttons/button.aml delete mode 100644 examples/templates/buttons/buttons.aml delete mode 100644 examples/templates/message-passing/message_passing.aml delete mode 100644 examples/templates/message-passing/messages.aml delete mode 100644 tests/custom_function.rs delete mode 100644 tests/if-bug-component-reuse.rs delete mode 100644 tests/if.rs delete mode 100644 tests/mount_unmount.rs delete mode 100644 tests/nested_collections.rs delete mode 100644 tests/quit_from_context.rs delete mode 100644 tests/resize_event.rs delete mode 100644 tests/statechange.rs delete mode 100644 tests/tabindex.rs delete mode 100644 tests/with.rs diff --git a/Cargo.toml b/Cargo.toml index b8c3b04e..1dab3dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,31 +14,18 @@ publish = true rust-version = "1.88" [dependencies] -anathema-backend = { workspace = true } -anathema-default-widgets = { workspace = true } +anathema-core = { workspace = true } anathema-geometry = { workspace = true } -anathema-runtime = { workspace = true } anathema-state = { workspace = true } anathema-state-derive = { workspace = true } anathema-store = { workspace = true } -anathema-templates = { workspace = true } -anathema-value-resolver = { workspace = true } +anathema-strings = { workspace = true } anathema-widgets = { workspace = true } -[dev-dependencies] -# anathema-debug = { path = "anathema-debug" } -anathema-testutils = { path = "anathema-testutils" } - [features] default = [] -profile = [ - "anathema-runtime/profile", - "anathema-widgets/profile", - "anathema-backend/profile", - "anathema-value-resolver/profile", -] +profile = [] serde = ["anathema-state/serde", "anathema-store/serde"] -# filelog = ["anathema-debug/filelog", "anathema-widgets/filelog", "anathema-runtime/filelog"] [lints] workspace = true @@ -53,29 +40,23 @@ crossterm = "0.28.1" unicode-width = "0.1.11" flume = "0.11.0" notify = "6.1.1" -anathema-default-widgets = { path = "./anathema-default-widgets", version = "0.2.12-beta" } -anathema-backend = { path = "./anathema-backend", version = "0.2.12-beta" } -anathema-runtime = { path = "./anathema-runtime", version = "0.2.12-beta" } +anathema-core = { path = "./anathema-core", version = "0.2.12-beta" } +anathema-geometry = { path = "./anathema-geometry", version = "0.2.12-beta" } anathema-state = { path = "./anathema-state", version = "0.2.12-beta" } anathema-state-derive = { path = "./anathema-state-derive", version = "0.2.12-beta" } anathema-store = { path = "./anathema-store", version = "0.2.12-beta" } -anathema-templates = { path = "./anathema-templates", version = "0.2.12-beta" } +anathema-strings = { path = "./anathema-strings", version = "0.2.12-beta" } anathema-widgets = { path = "./anathema-widgets", version = "0.2.12-beta" } -anathema-geometry = { path = "./anathema-geometry", version = "0.2.12-beta" } -anathema-value-resolver = { path = "./anathema-value-resolver", version = "0.2.12-beta" } [workspace] members = [ - "anathema-backend", - "anathema-runtime", - "anathema-default-widgets", + "anathema-core", "anathema-geometry", "anathema-state", "anathema-state-derive", "anathema-store", - "anathema-templates", + "anathema-strings", "anathema-widgets", - "anathema-value-resolver", ] [workspace.lints.rust] diff --git a/anathema-debug/Cargo.toml b/_throw/anathema-debug/Cargo.toml similarity index 100% rename from anathema-debug/Cargo.toml rename to _throw/anathema-debug/Cargo.toml diff --git a/anathema-debug/src/lib.rs b/_throw/anathema-debug/src/lib.rs similarity index 100% rename from anathema-debug/src/lib.rs rename to _throw/anathema-debug/src/lib.rs diff --git a/anathema-default-widgets/Cargo.toml b/_throw/anathema-default-widgets/Cargo.toml similarity index 100% rename from anathema-default-widgets/Cargo.toml rename to _throw/anathema-default-widgets/Cargo.toml diff --git a/anathema-default-widgets/src/alignment.rs b/_throw/anathema-default-widgets/src/alignment.rs similarity index 100% rename from anathema-default-widgets/src/alignment.rs rename to _throw/anathema-default-widgets/src/alignment.rs diff --git a/anathema-default-widgets/src/border.rs b/_throw/anathema-default-widgets/src/border.rs similarity index 100% rename from anathema-default-widgets/src/border.rs rename to _throw/anathema-default-widgets/src/border.rs diff --git a/anathema-default-widgets/src/canvas.rs b/_throw/anathema-default-widgets/src/canvas.rs similarity index 100% rename from anathema-default-widgets/src/canvas.rs rename to _throw/anathema-default-widgets/src/canvas.rs diff --git a/anathema-default-widgets/src/container.rs b/_throw/anathema-default-widgets/src/container.rs similarity index 100% rename from anathema-default-widgets/src/container.rs rename to _throw/anathema-default-widgets/src/container.rs diff --git a/anathema-default-widgets/src/expand.rs b/_throw/anathema-default-widgets/src/expand.rs similarity index 100% rename from anathema-default-widgets/src/expand.rs rename to _throw/anathema-default-widgets/src/expand.rs diff --git a/anathema-default-widgets/src/layout/alignment.rs b/_throw/anathema-default-widgets/src/layout/alignment.rs similarity index 100% rename from anathema-default-widgets/src/layout/alignment.rs rename to _throw/anathema-default-widgets/src/layout/alignment.rs diff --git a/anathema-default-widgets/src/layout/border.rs b/_throw/anathema-default-widgets/src/layout/border.rs similarity index 100% rename from anathema-default-widgets/src/layout/border.rs rename to _throw/anathema-default-widgets/src/layout/border.rs diff --git a/anathema-default-widgets/src/layout/expand.rs b/_throw/anathema-default-widgets/src/layout/expand.rs similarity index 100% rename from anathema-default-widgets/src/layout/expand.rs rename to _throw/anathema-default-widgets/src/layout/expand.rs diff --git a/anathema-default-widgets/src/layout/many.rs b/_throw/anathema-default-widgets/src/layout/many.rs similarity index 100% rename from anathema-default-widgets/src/layout/many.rs rename to _throw/anathema-default-widgets/src/layout/many.rs diff --git a/anathema-default-widgets/src/layout/mod.rs b/_throw/anathema-default-widgets/src/layout/mod.rs similarity index 100% rename from anathema-default-widgets/src/layout/mod.rs rename to _throw/anathema-default-widgets/src/layout/mod.rs diff --git a/anathema-default-widgets/src/layout/spacers.rs b/_throw/anathema-default-widgets/src/layout/spacers.rs similarity index 100% rename from anathema-default-widgets/src/layout/spacers.rs rename to _throw/anathema-default-widgets/src/layout/spacers.rs diff --git a/anathema-default-widgets/src/lib.rs b/_throw/anathema-default-widgets/src/lib.rs similarity index 100% rename from anathema-default-widgets/src/lib.rs rename to _throw/anathema-default-widgets/src/lib.rs diff --git a/anathema-default-widgets/src/overflow.rs b/_throw/anathema-default-widgets/src/overflow.rs similarity index 100% rename from anathema-default-widgets/src/overflow.rs rename to _throw/anathema-default-widgets/src/overflow.rs diff --git a/anathema-default-widgets/src/padding.rs b/_throw/anathema-default-widgets/src/padding.rs similarity index 100% rename from anathema-default-widgets/src/padding.rs rename to _throw/anathema-default-widgets/src/padding.rs diff --git a/anathema-default-widgets/src/position.rs b/_throw/anathema-default-widgets/src/position.rs similarity index 100% rename from anathema-default-widgets/src/position.rs rename to _throw/anathema-default-widgets/src/position.rs diff --git a/anathema-default-widgets/src/spacer.rs b/_throw/anathema-default-widgets/src/spacer.rs similarity index 100% rename from anathema-default-widgets/src/spacer.rs rename to _throw/anathema-default-widgets/src/spacer.rs diff --git a/anathema-default-widgets/src/stacks/column.rs b/_throw/anathema-default-widgets/src/stacks/column.rs similarity index 100% rename from anathema-default-widgets/src/stacks/column.rs rename to _throw/anathema-default-widgets/src/stacks/column.rs diff --git a/anathema-default-widgets/src/stacks/hstack.rs b/_throw/anathema-default-widgets/src/stacks/hstack.rs similarity index 100% rename from anathema-default-widgets/src/stacks/hstack.rs rename to _throw/anathema-default-widgets/src/stacks/hstack.rs diff --git a/anathema-default-widgets/src/stacks/mod.rs b/_throw/anathema-default-widgets/src/stacks/mod.rs similarity index 100% rename from anathema-default-widgets/src/stacks/mod.rs rename to _throw/anathema-default-widgets/src/stacks/mod.rs diff --git a/anathema-default-widgets/src/stacks/row.rs b/_throw/anathema-default-widgets/src/stacks/row.rs similarity index 100% rename from anathema-default-widgets/src/stacks/row.rs rename to _throw/anathema-default-widgets/src/stacks/row.rs diff --git a/anathema-default-widgets/src/stacks/vstack.rs b/_throw/anathema-default-widgets/src/stacks/vstack.rs similarity index 100% rename from anathema-default-widgets/src/stacks/vstack.rs rename to _throw/anathema-default-widgets/src/stacks/vstack.rs diff --git a/anathema-default-widgets/src/stacks/zstack.rs b/_throw/anathema-default-widgets/src/stacks/zstack.rs similarity index 100% rename from anathema-default-widgets/src/stacks/zstack.rs rename to _throw/anathema-default-widgets/src/stacks/zstack.rs diff --git a/anathema-default-widgets/src/testing.rs b/_throw/anathema-default-widgets/src/testing.rs similarity index 100% rename from anathema-default-widgets/src/testing.rs rename to _throw/anathema-default-widgets/src/testing.rs diff --git a/anathema-default-widgets/src/text.rs b/_throw/anathema-default-widgets/src/text.rs similarity index 100% rename from anathema-default-widgets/src/text.rs rename to _throw/anathema-default-widgets/src/text.rs diff --git a/anathema-runtime/Cargo.toml b/_throw/anathema-runtime/Cargo.toml similarity index 100% rename from anathema-runtime/Cargo.toml rename to _throw/anathema-runtime/Cargo.toml diff --git a/anathema-runtime/src/builder.rs b/_throw/anathema-runtime/src/builder.rs similarity index 100% rename from anathema-runtime/src/builder.rs rename to _throw/anathema-runtime/src/builder.rs diff --git a/anathema-runtime/src/error.rs b/_throw/anathema-runtime/src/error.rs similarity index 100% rename from anathema-runtime/src/error.rs rename to _throw/anathema-runtime/src/error.rs diff --git a/anathema-runtime/src/events.rs b/_throw/anathema-runtime/src/events.rs similarity index 100% rename from anathema-runtime/src/events.rs rename to _throw/anathema-runtime/src/events.rs diff --git a/anathema-runtime/src/lib.rs b/_throw/anathema-runtime/src/lib.rs similarity index 100% rename from anathema-runtime/src/lib.rs rename to _throw/anathema-runtime/src/lib.rs diff --git a/anathema-runtime/src/renderonly.rs b/_throw/anathema-runtime/src/renderonly.rs similarity index 100% rename from anathema-runtime/src/renderonly.rs rename to _throw/anathema-runtime/src/renderonly.rs diff --git a/anathema-runtime/src/runtime/error.rs b/_throw/anathema-runtime/src/runtime/error.rs similarity index 100% rename from anathema-runtime/src/runtime/error.rs rename to _throw/anathema-runtime/src/runtime/error.rs diff --git a/anathema-runtime/src/runtime/mod.rs b/_throw/anathema-runtime/src/runtime/mod.rs similarity index 100% rename from anathema-runtime/src/runtime/mod.rs rename to _throw/anathema-runtime/src/runtime/mod.rs diff --git a/anathema-runtime/src/runtime/testing.rs b/_throw/anathema-runtime/src/runtime/testing.rs similarity index 100% rename from anathema-runtime/src/runtime/testing.rs rename to _throw/anathema-runtime/src/runtime/testing.rs diff --git a/anathema-templates/Cargo.toml b/_throw/anathema-templates/Cargo.toml similarity index 86% rename from anathema-templates/Cargo.toml rename to _throw/anathema-templates/Cargo.toml index 07efb1ef..de607413 100644 --- a/anathema-templates/Cargo.toml +++ b/_throw/anathema-templates/Cargo.toml @@ -12,8 +12,5 @@ repository = "https://github.com/togglebyte/anathema" anathema-state = { workspace = true } anathema-store = { workspace = true } -[dev-dependencies] -anathema-debug = { path = "../anathema-debug" } - [lints] workspace = true diff --git a/anathema-templates/src/blueprints.rs b/_throw/anathema-templates/src/blueprints.rs similarity index 100% rename from anathema-templates/src/blueprints.rs rename to _throw/anathema-templates/src/blueprints.rs diff --git a/anathema-templates/src/components.rs b/_throw/anathema-templates/src/components.rs similarity index 100% rename from anathema-templates/src/components.rs rename to _throw/anathema-templates/src/components.rs diff --git a/anathema-templates/src/document.rs b/_throw/anathema-templates/src/document.rs similarity index 98% rename from anathema-templates/src/document.rs rename to _throw/anathema-templates/src/document.rs index a87e36f3..fbc9910c 100644 --- a/anathema-templates/src/document.rs +++ b/_throw/anathema-templates/src/document.rs @@ -24,7 +24,6 @@ pub struct Document { pub strings: Strings, pub expressions: Expressions, components: ComponentTemplates, - pub hot_reload: bool, } impl Document { @@ -35,7 +34,6 @@ impl Document { strings: Strings::new(), expressions: Expressions::empty(), components: ComponentTemplates::new(), - hot_reload: true, } } diff --git a/anathema-templates/src/error/mod.rs b/_throw/anathema-templates/src/error/mod.rs similarity index 100% rename from anathema-templates/src/error/mod.rs rename to _throw/anathema-templates/src/error/mod.rs diff --git a/anathema-templates/src/error/parse.rs b/_throw/anathema-templates/src/error/parse.rs similarity index 100% rename from anathema-templates/src/error/parse.rs rename to _throw/anathema-templates/src/error/parse.rs diff --git a/anathema-templates/src/expressions/eval.rs b/_throw/anathema-templates/src/expressions/eval.rs similarity index 100% rename from anathema-templates/src/expressions/eval.rs rename to _throw/anathema-templates/src/expressions/eval.rs diff --git a/anathema-templates/src/expressions/mod.rs b/_throw/anathema-templates/src/expressions/mod.rs similarity index 100% rename from anathema-templates/src/expressions/mod.rs rename to _throw/anathema-templates/src/expressions/mod.rs diff --git a/anathema-templates/src/expressions/parser.rs b/_throw/anathema-templates/src/expressions/parser.rs similarity index 100% rename from anathema-templates/src/expressions/parser.rs rename to _throw/anathema-templates/src/expressions/parser.rs diff --git a/anathema-templates/src/lexer.rs b/_throw/anathema-templates/src/lexer.rs similarity index 100% rename from anathema-templates/src/lexer.rs rename to _throw/anathema-templates/src/lexer.rs diff --git a/anathema-templates/src/lib.rs b/_throw/anathema-templates/src/lib.rs similarity index 100% rename from anathema-templates/src/lib.rs rename to _throw/anathema-templates/src/lib.rs diff --git a/anathema-templates/src/primitives.rs b/_throw/anathema-templates/src/primitives.rs similarity index 100% rename from anathema-templates/src/primitives.rs rename to _throw/anathema-templates/src/primitives.rs diff --git a/anathema-templates/src/statements/const_eval.rs b/_throw/anathema-templates/src/statements/const_eval.rs similarity index 100% rename from anathema-templates/src/statements/const_eval.rs rename to _throw/anathema-templates/src/statements/const_eval.rs diff --git a/anathema-templates/src/statements/eval.rs b/_throw/anathema-templates/src/statements/eval.rs similarity index 100% rename from anathema-templates/src/statements/eval.rs rename to _throw/anathema-templates/src/statements/eval.rs diff --git a/anathema-templates/src/statements/mod.rs b/_throw/anathema-templates/src/statements/mod.rs similarity index 100% rename from anathema-templates/src/statements/mod.rs rename to _throw/anathema-templates/src/statements/mod.rs diff --git a/anathema-templates/src/statements/parser.rs b/_throw/anathema-templates/src/statements/parser.rs similarity index 100% rename from anathema-templates/src/statements/parser.rs rename to _throw/anathema-templates/src/statements/parser.rs diff --git a/anathema-templates/src/strings.rs b/_throw/anathema-templates/src/strings.rs similarity index 100% rename from anathema-templates/src/strings.rs rename to _throw/anathema-templates/src/strings.rs diff --git a/anathema-templates/src/token.rs b/_throw/anathema-templates/src/token.rs similarity index 100% rename from anathema-templates/src/token.rs rename to _throw/anathema-templates/src/token.rs diff --git a/anathema-templates/src/variables.rs b/_throw/anathema-templates/src/variables.rs similarity index 100% rename from anathema-templates/src/variables.rs rename to _throw/anathema-templates/src/variables.rs diff --git a/anathema-testutils/Cargo.toml b/_throw/anathema-testutils/Cargo.toml similarity index 100% rename from anathema-testutils/Cargo.toml rename to _throw/anathema-testutils/Cargo.toml diff --git a/anathema-testutils/src/lib.rs b/_throw/anathema-testutils/src/lib.rs similarity index 100% rename from anathema-testutils/src/lib.rs rename to _throw/anathema-testutils/src/lib.rs diff --git a/_throw/anathema-ui/Cargo.toml b/_throw/anathema-ui/Cargo.toml new file mode 100644 index 00000000..ad654e2e --- /dev/null +++ b/_throw/anathema-ui/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "anathema-ui" +edition.workspace = true +version.workspace = true + +[dependencies] +anathema-state = { workspace = true } +anathema-store = { workspace = true } +anathema-geometry = { workspace = true } + +[lints] +workspace = true diff --git a/_throw/anathema-ui/src/component.rs b/_throw/anathema-ui/src/component.rs new file mode 100644 index 00000000..242f65c3 --- /dev/null +++ b/_throw/anathema-ui/src/component.rs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------------- +// - Component - +// ----------------------------------------------------------------------------- + +use crate::UI; + +pub trait Component { + type State; + type Message; + + fn on_tick(&mut self, ui: &mut UI<'_>); +} + +// ----------------------------------------------------------------------------- +// - Any component (erased type) - +// ----------------------------------------------------------------------------- + +pub trait AnyComponent { + fn on_tick(&mut self, ui: &mut UI<'_>); +} + +impl AnyComponent for T { + fn on_tick(&mut self, ui: &mut UI<'_>) { + T::on_tick(self, ui) + } +} + +#[test] +fn here() { + struct C; + impl Component for C { + type State = (); + type Message = (); + + fn on_tick(&mut self, ui: &mut UI<'_>) { + // Add a new node + vstack().add_child(text("lol")).add_child(text("hello")).to_element(); + let node = text("lol").to_element(); + let node = component("compname", SomeComponent::new(), SomeState::new()).to_node(); + tree.this().add_child(node); + let bb = tree.this().bounding_box(); + } + } +} diff --git a/_throw/anathema-ui/src/components.rs b/_throw/anathema-ui/src/components.rs new file mode 100644 index 00000000..2007b29d --- /dev/null +++ b/_throw/anathema-ui/src/components.rs @@ -0,0 +1,49 @@ +use anathema_state::State; +use anathema_store::slab::{Slab, SlabIndex}; + +use crate::component::AnyComponent; + +// ----------------------------------------------------------------------------- +// - Component Id - +// ----------------------------------------------------------------------------- + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ComponentId(u32); + +impl SlabIndex for ComponentId { + const MAX: usize = u32::MAX as usize; + + fn as_usize(&self) -> usize { + self.0 as usize + } + + fn from_usize(index: usize) -> Self + where + Self: Sized, + { + Self(index as u32) + } +} + +type FnComp = Box Box>; +type FnState = Box Box>; + +enum Entry { + Component(Box, Box), + PrototypeInstance(Box, Box), + Prototype(FnComp, FnState), +} + +// ----------------------------------------------------------------------------- +// - Components - +// ----------------------------------------------------------------------------- + +pub struct Components { + inner: Slab, +} + +impl Components { + pub fn empty() -> Self { + Self { inner: Slab::empty() } + } +} diff --git a/_throw/anathema-ui/src/constraints.rs b/_throw/anathema-ui/src/constraints.rs new file mode 100644 index 00000000..64472dbc --- /dev/null +++ b/_throw/anathema-ui/src/constraints.rs @@ -0,0 +1,2 @@ +pub struct Constraints { +} diff --git a/_throw/anathema-ui/src/elements.rs b/_throw/anathema-ui/src/elements.rs new file mode 100644 index 00000000..ce5c3168 --- /dev/null +++ b/_throw/anathema-ui/src/elements.rs @@ -0,0 +1,67 @@ +use anathema_geometry::{Pos, Size}; + +use crate::nodes::Nodes; + +pub trait Element { + fn layout(&mut self) -> Size; + fn position(&mut self) -> Pos; + fn paint(&mut self); +} + +pub struct ElementBuilder<'a, T> { + element: T, + nodes: &'a mut Nodes +} + +impl<'a, T> ElementBuilder<'a, T> { + fn add_child(self, child: impl Element) -> Self { + self + } + + // fn to_element(self) -> +} + +// ----------------------------------------------------------------------------- +// - The following code should be moved / thrown out - +// ----------------------------------------------------------------------------- + +pub struct Text { + text: String, +} + +impl Element for Text { + fn layout(&mut self) -> Size { + todo!() + } + + fn position(&mut self) -> Pos { + todo!() + } + + fn paint(&mut self) { + todo!() + } +} + +pub fn text(text: String) -> Text { + Text { + text + } +} + +pub struct VStack { +} + +impl Element for VStack { + fn layout(&mut self) -> Size { + todo!() + } + + fn position(&mut self) -> Pos { + todo!() + } + + fn paint(&mut self) { + todo!() + } +} diff --git a/_throw/anathema-ui/src/lib.rs b/_throw/anathema-ui/src/lib.rs new file mode 100644 index 00000000..aea66361 --- /dev/null +++ b/_throw/anathema-ui/src/lib.rs @@ -0,0 +1,33 @@ +pub use query::Queryable; + +use crate::components::{ComponentId, Components}; +use crate::nodes::Nodes; + +mod component; +mod components; +mod constraints; +mod elements; +mod nodes; +mod query; + +pub struct UI<'a> { + nodes: Nodes, + components: Components, + meh: &'a (), +} + +impl<'a> UI<'a> { + /// Dispatch events + fn dispatch(&mut self, component: ComponentId) {} + + fn this(&self) -> () {} + + // This should really be by_tag, by_name, by_id etc. + fn query(&self) -> () {} +} + +impl<'a> Queryable for &mut UI<'a> { + fn by_tag(self) -> impl Queryable { + self + } +} diff --git a/_throw/anathema-ui/src/nodes.rs b/_throw/anathema-ui/src/nodes.rs new file mode 100644 index 00000000..999270b6 --- /dev/null +++ b/_throw/anathema-ui/src/nodes.rs @@ -0,0 +1,15 @@ +use anathema_store::slab::Slab; + +use crate::elements::Element; + +pub struct NodeId(u32); + +pub struct Nodes { + nodes: Slab +} + +pub struct Node { + parent: Option, + pub children: Vec, + element: Box, +} diff --git a/_throw/anathema-ui/src/query.rs b/_throw/anathema-ui/src/query.rs new file mode 100644 index 00000000..dde489f8 --- /dev/null +++ b/_throw/anathema-ui/src/query.rs @@ -0,0 +1,13 @@ +pub trait Queryable { + fn by_tag(self) -> impl Queryable; +} + +pub struct QueryBuilder { + inner: T +} + +impl Queryable for QueryBuilder { + fn by_tag(self) -> impl Queryable { + self + } +} diff --git a/anathema-value-resolver/Cargo.toml b/_throw/anathema-value-resolver/Cargo.toml similarity index 100% rename from anathema-value-resolver/Cargo.toml rename to _throw/anathema-value-resolver/Cargo.toml diff --git a/anathema-value-resolver/src/attributes.rs b/_throw/anathema-value-resolver/src/attributes.rs similarity index 100% rename from anathema-value-resolver/src/attributes.rs rename to _throw/anathema-value-resolver/src/attributes.rs diff --git a/anathema-value-resolver/src/context.rs b/_throw/anathema-value-resolver/src/context.rs similarity index 100% rename from anathema-value-resolver/src/context.rs rename to _throw/anathema-value-resolver/src/context.rs diff --git a/anathema-value-resolver/src/expression.rs b/_throw/anathema-value-resolver/src/expression.rs similarity index 100% rename from anathema-value-resolver/src/expression.rs rename to _throw/anathema-value-resolver/src/expression.rs diff --git a/anathema-value-resolver/src/functions/list.rs b/_throw/anathema-value-resolver/src/functions/list.rs similarity index 100% rename from anathema-value-resolver/src/functions/list.rs rename to _throw/anathema-value-resolver/src/functions/list.rs diff --git a/anathema-value-resolver/src/functions/mod.rs b/_throw/anathema-value-resolver/src/functions/mod.rs similarity index 100% rename from anathema-value-resolver/src/functions/mod.rs rename to _throw/anathema-value-resolver/src/functions/mod.rs diff --git a/anathema-value-resolver/src/functions/number.rs b/_throw/anathema-value-resolver/src/functions/number.rs similarity index 100% rename from anathema-value-resolver/src/functions/number.rs rename to _throw/anathema-value-resolver/src/functions/number.rs diff --git a/anathema-value-resolver/src/functions/string.rs b/_throw/anathema-value-resolver/src/functions/string.rs similarity index 100% rename from anathema-value-resolver/src/functions/string.rs rename to _throw/anathema-value-resolver/src/functions/string.rs diff --git a/anathema-value-resolver/src/immediate.rs b/_throw/anathema-value-resolver/src/immediate.rs similarity index 100% rename from anathema-value-resolver/src/immediate.rs rename to _throw/anathema-value-resolver/src/immediate.rs diff --git a/anathema-value-resolver/src/lib.rs b/_throw/anathema-value-resolver/src/lib.rs similarity index 100% rename from anathema-value-resolver/src/lib.rs rename to _throw/anathema-value-resolver/src/lib.rs diff --git a/anathema-value-resolver/src/scope.rs b/_throw/anathema-value-resolver/src/scope.rs similarity index 100% rename from anathema-value-resolver/src/scope.rs rename to _throw/anathema-value-resolver/src/scope.rs diff --git a/anathema-value-resolver/src/testing.rs b/_throw/anathema-value-resolver/src/testing.rs similarity index 100% rename from anathema-value-resolver/src/testing.rs rename to _throw/anathema-value-resolver/src/testing.rs diff --git a/anathema-value-resolver/src/value.rs b/_throw/anathema-value-resolver/src/value.rs similarity index 100% rename from anathema-value-resolver/src/value.rs rename to _throw/anathema-value-resolver/src/value.rs diff --git a/anathema-backend/Cargo.toml b/_throw/anathema-widgets/Cargo.toml similarity index 71% rename from anathema-backend/Cargo.toml rename to _throw/anathema-widgets/Cargo.toml index 7721c76a..64c1bf7b 100644 --- a/anathema-backend/Cargo.toml +++ b/_throw/anathema-widgets/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "anathema-backend" +name = "anathema-widgets" license = "MIT" -description = "Various backends for Anathema" +description = "Anathema widget base" version.workspace = true edition.workspace = true documentation = "https://togglebyte.github.io/anathema-guide/" @@ -12,17 +12,19 @@ repository = "https://github.com/togglebyte/anathema" anathema-geometry = { workspace = true } anathema-state = { workspace = true } anathema-store = { workspace = true } +# anathema-strings = { workspace = true } anathema-templates = { workspace = true } anathema-value-resolver = { workspace = true } -anathema-widgets = { workspace = true } -crossterm = { workspace = true } -unicode-width = { workspace = true } +flume = { workspace = true } bitflags = { workspace = true } -puffin = { version = "0.19.1", optional = true } +unicode-width = { workspace = true } +unicode-segmentation = "1.11.0" +puffin = { version = "0.19.1", features = ["web"], optional = true } [features] default = [] profile = ["puffin"] +filelog = [] [lints] workspace = true diff --git a/anathema-widgets/src/components/deferred.rs b/_throw/anathema-widgets/src/components/deferred.rs similarity index 100% rename from anathema-widgets/src/components/deferred.rs rename to _throw/anathema-widgets/src/components/deferred.rs diff --git a/anathema-widgets/src/components/events/key.rs b/_throw/anathema-widgets/src/components/events/key.rs similarity index 100% rename from anathema-widgets/src/components/events/key.rs rename to _throw/anathema-widgets/src/components/events/key.rs diff --git a/anathema-widgets/src/components/events/mod.rs b/_throw/anathema-widgets/src/components/events/mod.rs similarity index 100% rename from anathema-widgets/src/components/events/mod.rs rename to _throw/anathema-widgets/src/components/events/mod.rs diff --git a/anathema-widgets/src/components/events/mouse.rs b/_throw/anathema-widgets/src/components/events/mouse.rs similarity index 100% rename from anathema-widgets/src/components/events/mouse.rs rename to _throw/anathema-widgets/src/components/events/mouse.rs diff --git a/anathema-widgets/src/components/mod.rs b/_throw/anathema-widgets/src/components/mod.rs similarity index 100% rename from anathema-widgets/src/components/mod.rs rename to _throw/anathema-widgets/src/components/mod.rs diff --git a/anathema-widgets/src/container.rs b/_throw/anathema-widgets/src/container.rs similarity index 100% rename from anathema-widgets/src/container.rs rename to _throw/anathema-widgets/src/container.rs diff --git a/anathema-widgets/src/error.rs b/_throw/anathema-widgets/src/error.rs similarity index 100% rename from anathema-widgets/src/error.rs rename to _throw/anathema-widgets/src/error.rs diff --git a/anathema-widgets/src/layout/constraints.rs b/_throw/anathema-widgets/src/layout/constraints.rs similarity index 100% rename from anathema-widgets/src/layout/constraints.rs rename to _throw/anathema-widgets/src/layout/constraints.rs diff --git a/anathema-widgets/src/layout/display.rs b/_throw/anathema-widgets/src/layout/display.rs similarity index 100% rename from anathema-widgets/src/layout/display.rs rename to _throw/anathema-widgets/src/layout/display.rs diff --git a/anathema-widgets/src/layout/mod.rs b/_throw/anathema-widgets/src/layout/mod.rs similarity index 100% rename from anathema-widgets/src/layout/mod.rs rename to _throw/anathema-widgets/src/layout/mod.rs diff --git a/anathema-widgets/src/layout/text.rs b/_throw/anathema-widgets/src/layout/text.rs similarity index 100% rename from anathema-widgets/src/layout/text.rs rename to _throw/anathema-widgets/src/layout/text.rs diff --git a/_throw/anathema-widgets/src/lib.rs b/_throw/anathema-widgets/src/lib.rs new file mode 100644 index 00000000..1aa280e7 --- /dev/null +++ b/_throw/anathema-widgets/src/lib.rs @@ -0,0 +1,27 @@ +use anathema_state::Subscriber; + +pub use crate::nodes::component::Component; +pub use crate::nodes::element::Layout; +pub use crate::nodes::{Element, WidgetContainer, WidgetGenerator, WidgetKind, eval_blueprint, update_widget}; +pub use crate::paint::{GlyphMap, WidgetRenderer}; +pub use crate::widget::{ + AnyWidget, Attributes, ComponentParents, Components, DirtyWidgets, Factory, FloatingWidgets, ForEach, + LayoutChildren, LayoutForEach, PaintChildren, PositionChildren, Style, Widget, WidgetId, WidgetTree, + WidgetTreeView, +}; + +pub type ChangeList = anathema_store::regionlist::RegionList<32, WidgetId, Subscriber>; + +pub mod components; +mod container; +pub mod error; +pub mod layout; +mod nodes; +pub mod paint; +pub mod query; +pub mod tabindex; +pub mod tree; +mod widget; + +#[cfg(test)] +mod testing; diff --git a/anathema-widgets/src/nodes/component.rs b/_throw/anathema-widgets/src/nodes/component.rs similarity index 100% rename from anathema-widgets/src/nodes/component.rs rename to _throw/anathema-widgets/src/nodes/component.rs diff --git a/anathema-widgets/src/nodes/controlflow.rs b/_throw/anathema-widgets/src/nodes/controlflow.rs similarity index 100% rename from anathema-widgets/src/nodes/controlflow.rs rename to _throw/anathema-widgets/src/nodes/controlflow.rs diff --git a/anathema-widgets/src/nodes/element.rs b/_throw/anathema-widgets/src/nodes/element.rs similarity index 100% rename from anathema-widgets/src/nodes/element.rs rename to _throw/anathema-widgets/src/nodes/element.rs diff --git a/anathema-widgets/src/nodes/eval.rs b/_throw/anathema-widgets/src/nodes/eval.rs similarity index 100% rename from anathema-widgets/src/nodes/eval.rs rename to _throw/anathema-widgets/src/nodes/eval.rs diff --git a/anathema-widgets/src/nodes/loops.rs b/_throw/anathema-widgets/src/nodes/loops.rs similarity index 100% rename from anathema-widgets/src/nodes/loops.rs rename to _throw/anathema-widgets/src/nodes/loops.rs diff --git a/anathema-widgets/src/nodes/mod.rs b/_throw/anathema-widgets/src/nodes/mod.rs similarity index 100% rename from anathema-widgets/src/nodes/mod.rs rename to _throw/anathema-widgets/src/nodes/mod.rs diff --git a/anathema-widgets/src/nodes/stringify.rs b/_throw/anathema-widgets/src/nodes/stringify.rs similarity index 100% rename from anathema-widgets/src/nodes/stringify.rs rename to _throw/anathema-widgets/src/nodes/stringify.rs diff --git a/anathema-widgets/src/nodes/update.rs b/_throw/anathema-widgets/src/nodes/update.rs similarity index 100% rename from anathema-widgets/src/nodes/update.rs rename to _throw/anathema-widgets/src/nodes/update.rs diff --git a/anathema-widgets/src/nodes/with.rs b/_throw/anathema-widgets/src/nodes/with.rs similarity index 100% rename from anathema-widgets/src/nodes/with.rs rename to _throw/anathema-widgets/src/nodes/with.rs diff --git a/anathema-widgets/src/paint.rs b/_throw/anathema-widgets/src/paint.rs similarity index 100% rename from anathema-widgets/src/paint.rs rename to _throw/anathema-widgets/src/paint.rs diff --git a/anathema-widgets/src/query/components.rs b/_throw/anathema-widgets/src/query/components.rs similarity index 100% rename from anathema-widgets/src/query/components.rs rename to _throw/anathema-widgets/src/query/components.rs diff --git a/anathema-widgets/src/query/deferred.rs b/_throw/anathema-widgets/src/query/deferred.rs similarity index 100% rename from anathema-widgets/src/query/deferred.rs rename to _throw/anathema-widgets/src/query/deferred.rs diff --git a/anathema-widgets/src/query/elements.rs b/_throw/anathema-widgets/src/query/elements.rs similarity index 100% rename from anathema-widgets/src/query/elements.rs rename to _throw/anathema-widgets/src/query/elements.rs diff --git a/anathema-widgets/src/query/mod.rs b/_throw/anathema-widgets/src/query/mod.rs similarity index 100% rename from anathema-widgets/src/query/mod.rs rename to _throw/anathema-widgets/src/query/mod.rs diff --git a/anathema-widgets/src/tabindex.rs b/_throw/anathema-widgets/src/tabindex.rs similarity index 100% rename from anathema-widgets/src/tabindex.rs rename to _throw/anathema-widgets/src/tabindex.rs diff --git a/anathema-widgets/src/testing.rs b/_throw/anathema-widgets/src/testing.rs similarity index 100% rename from anathema-widgets/src/testing.rs rename to _throw/anathema-widgets/src/testing.rs diff --git a/anathema-widgets/src/tree/debug.rs b/_throw/anathema-widgets/src/tree/debug.rs similarity index 100% rename from anathema-widgets/src/tree/debug.rs rename to _throw/anathema-widgets/src/tree/debug.rs diff --git a/anathema-widgets/src/tree/mod.rs b/_throw/anathema-widgets/src/tree/mod.rs similarity index 100% rename from anathema-widgets/src/tree/mod.rs rename to _throw/anathema-widgets/src/tree/mod.rs diff --git a/anathema-widgets/src/widget/attributes.rs b/_throw/anathema-widgets/src/widget/attributes.rs similarity index 100% rename from anathema-widgets/src/widget/attributes.rs rename to _throw/anathema-widgets/src/widget/attributes.rs diff --git a/anathema-widgets/src/widget/factory.rs b/_throw/anathema-widgets/src/widget/factory.rs similarity index 100% rename from anathema-widgets/src/widget/factory.rs rename to _throw/anathema-widgets/src/widget/factory.rs diff --git a/anathema-widgets/src/widget/mod.rs b/_throw/anathema-widgets/src/widget/mod.rs similarity index 100% rename from anathema-widgets/src/widget/mod.rs rename to _throw/anathema-widgets/src/widget/mod.rs diff --git a/anathema-widgets/src/widget/style.rs b/_throw/anathema-widgets/src/widget/style.rs similarity index 100% rename from anathema-widgets/src/widget/style.rs rename to _throw/anathema-widgets/src/widget/style.rs diff --git a/anathema-backend/src/lib.rs b/anathema-backend/src/lib.rs deleted file mode 100644 index 6fe3cd0b..00000000 --- a/anathema-backend/src/lib.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::ops::ControlFlow; -use std::time::Duration; - -use anathema_geometry::{Pos, Size}; -use anathema_value_resolver::{AttributeStorage, Scope}; -use anathema_widgets::components::events::Event; -use anathema_widgets::error::Result; -use anathema_widgets::layout::{Constraints, LayoutCtx, LayoutFilter, PositionFilter, Viewport}; -use anathema_widgets::paint::PaintFilter; -use anathema_widgets::{ - DirtyWidgets, GlyphMap, Layout, LayoutForEach, PaintChildren, PositionChildren, WidgetTreeView, -}; - -pub mod testing; -pub mod tui; - -pub trait Backend { - fn size(&self) -> Size; - - fn next_event(&mut self, timeout: Duration) -> Option; - - fn resize(&mut self, new_size: Size, glyph_map: &mut GlyphMap); - - /// Paint the widgets - fn paint<'bp>( - &mut self, - glyph_map: &mut GlyphMap, - widgets: PaintChildren<'_, 'bp>, - attribute_storage: &AttributeStorage<'bp>, - ); - - /// Called by the runtime at the end of the frame. - fn render(&mut self, glyph_map: &mut GlyphMap); - - /// Clear is called immediately after `render` is called. - fn clear(&mut self); - - /// Finalizes the backend. This is called when the runtime starts. - fn finalize(&mut self) {} -} - -// TODO: rename this. -// This does layout, position and paint and should have -// a less silly name -pub struct WidgetCycle<'rt, 'bp, T> { - backend: &'rt mut T, - tree: WidgetTreeView<'rt, 'bp>, - constraints: Constraints, -} - -impl<'rt, 'bp, T: Backend> WidgetCycle<'rt, 'bp, T> { - pub fn new(backend: &'rt mut T, tree: WidgetTreeView<'rt, 'bp>, constraints: Constraints) -> Self { - Self { - backend, - tree, - constraints, - } - } - - fn fixed(&mut self, ctx: &mut LayoutCtx<'_, 'bp>) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Position - - // ----------------------------------------------------------------------------- - self.position(ctx.attribute_storage, *ctx.viewport, PositionFilter::fixed()); - - // ----------------------------------------------------------------------------- - // - Paint - - // ----------------------------------------------------------------------------- - self.paint(ctx, PaintFilter::fixed()); - - Ok(()) - } - - fn floating(&mut self, ctx: &mut LayoutCtx<'_, 'bp>) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Position - - // ----------------------------------------------------------------------------- - self.position(ctx.attribute_storage, *ctx.viewport, PositionFilter::floating()); - - // ----------------------------------------------------------------------------- - // - Paint - - // ----------------------------------------------------------------------------- - self.paint(ctx, PaintFilter::floating()); - - Ok(()) - } - - pub fn run( - &mut self, - ctx: &mut LayoutCtx<'_, 'bp>, - force_layout: bool, - dirty_widgets: &mut DirtyWidgets, - ) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Layout - - // ----------------------------------------------------------------------------- - self.layout(ctx, LayoutFilter, dirty_widgets, force_layout)?; - - // ----------------------------------------------------------------------------- - // - Position and paint - - // ----------------------------------------------------------------------------- - self.fixed(ctx)?; - self.floating(ctx)?; - Ok(()) - } - - fn layout( - &mut self, - ctx: &mut LayoutCtx<'_, 'bp>, - filter: LayoutFilter, - dirty_widgets: &mut DirtyWidgets, - force_layout: bool, - ) -> Result<()> { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let mut tree = self.tree.view(); - - if force_layout { - // Perform a layout across the entire tree - let scope = Scope::root(); - let mut for_each = LayoutForEach::new(tree, &scope, filter); - let constraints = self.constraints; - _ = for_each.each(ctx, |ctx, widget, children| { - _ = widget.layout(children, constraints, ctx)?; - Ok(ControlFlow::Break(())) - })?; - return Ok(()); - } - - // If a widget has changed, mark the parent as dirty - - // Layout only changed widgets. - // These are the parents of changed widgets. - // - // Investigate the possibility of attaching an offset as existing widgets don't need - // to reflow unless the constraint has changed. - // - // This means `additional_widgets` needs to store (key, offset) where offset can be None - // - // If this is going to work we need to consider `expand` and `spacer` - // - // Since widgets can be made by anyone and they are always guaranteed to give - // access to all their children this might not be a possibility. - // - // parent - // widget 0 - // widget 1 - // widget 2 | <- if this changes, only reflow this, three and four - // widget 3 |-- reflow - // widget 4 | - - // TODO: make `additional_widgets` a scratch buffer part of `DirtyWidgets`. - // Also ensure that it tracks last id as well - // ... and removes it when done! - let mut additional_widgets = vec![]; - - loop { - for widget_id in dirty_widgets.drain() { - if !tree.contains(widget_id) { - continue; - } - tree.with_value_mut(widget_id, |_, widget, children| { - let scope = Scope::root(); - let mut children = LayoutForEach::new(children, &scope, filter); - children.parent_element = Some(widget_id); - let parent_id = widget.parent_widget; - let anathema_widgets::WidgetKind::Element(widget) = &mut widget.kind else { return }; - - let constraints = widget.constraints(); - if let Ok(Layout::Changed(_)) = widget.layout(children, constraints, ctx) { - // write into scratch buffer - if let Some(id) = parent_id { - additional_widgets.push(id); - } - } - }); - } - - // merge the scratch if it's not empty - - if additional_widgets.is_empty() { - break; - } - - dirty_widgets.inner.append(&mut additional_widgets); - } - - Ok(()) - } - - fn position(&mut self, attributes: &AttributeStorage<'bp>, viewport: Viewport, filter: PositionFilter) { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let mut for_each = PositionChildren::new(self.tree.view(), attributes, filter); - _ = for_each.each(|widget, children| { - widget.position(children, Pos::ZERO, attributes, viewport); - ControlFlow::Break(()) - }); - } - - fn paint(&mut self, ctx: &mut LayoutCtx<'_, 'bp>, filter: PaintFilter) { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let for_each = PaintChildren::new(self.tree.view(), ctx.attribute_storage, filter); - self.backend.paint(ctx.glyph_map, for_each, ctx.attribute_storage); - } -} diff --git a/anathema-backend/src/testing/events.rs b/anathema-backend/src/testing/events.rs deleted file mode 100644 index 44971ad2..00000000 --- a/anathema-backend/src/testing/events.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::VecDeque; - -use anathema_geometry::Size; -use anathema_widgets::components::events::{Event, KeyEvent}; - -pub struct EventsMut<'a> { - event_queue: &'a mut VecDeque>, -} - -impl EventsMut<'_> { - pub fn next(self) -> Self { - self.event_queue.push_back(None); - self - } - - pub fn next_frames(self, count: usize) -> Self { - for _ in 0..count { - self.event_queue.push_back(None); - } - self - } - - pub fn resize(self, new_size: impl Into) -> Self { - let size = new_size.into(); - self.event_queue.push_back(Some(Event::Resize(size))); - self - } - - pub fn stop(self) -> Self { - self.event_queue.push_back(Some(Event::Stop)); - self - } - - pub fn press(self, event: KeyEvent) -> Self { - self.event_queue.push_back(Some(Event::Key(event))); - self - } -} - -#[derive(Debug)] -pub struct Events { - event_queue: VecDeque>, -} - -impl Events { - pub(super) fn new() -> Self { - Self { - event_queue: VecDeque::new(), - } - } - - pub(super) fn mut_ref(&mut self) -> EventsMut<'_> { - EventsMut { - event_queue: &mut self.event_queue, - } - } - - pub(super) fn pop(&mut self) -> Option { - self.event_queue.pop_front().flatten() - } -} diff --git a/anathema-backend/src/testing/mod.rs b/anathema-backend/src/testing/mod.rs deleted file mode 100644 index d4f566ae..00000000 --- a/anathema-backend/src/testing/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::time::Duration; - -use anathema_geometry::Size; -use anathema_value_resolver::AttributeStorage; -use anathema_widgets::components::events::Event; -use anathema_widgets::paint::{Glyph, paint}; -use anathema_widgets::{GlyphMap, PaintChildren}; -use surface::TestSurface; - -use crate::Backend; - -mod events; -mod surface; - -#[derive(Debug)] -/// This is used for testing -pub struct GlyphRef<'a> { - inner: Option<&'a Glyph>, -} - -impl<'a> GlyphRef<'a> { - pub fn is_char(&self, rhs: char) -> bool { - let Some(glyph) = self.inner else { return false }; - match glyph { - Glyph::Single(lhs, _) => *lhs == rhs, - Glyph::Cluster(_glyph_index, _) => todo!(), - } - } -} - -#[derive(Debug)] -pub struct TestBackend { - surface: TestSurface, - events: events::Events, -} - -impl TestBackend { - pub fn new(size: impl Into) -> Self { - let size = size.into(); - Self { - surface: TestSurface::new(size), - events: events::Events::new(), - } - } - - pub fn at(&self, x: usize, y: usize) -> GlyphRef<'_> { - GlyphRef { - inner: self.surface.get(x, y), - } - } - - pub fn line(&self, index: usize) -> String { - let glyphs = self.surface.line(index); - glyphs - .iter() - .filter_map(|g| match g { - Glyph::Single(c, _) => Some(c), - Glyph::Cluster(_, _) => None, - }) - .collect::() - .trim() - .to_string() - } - - pub fn events(&mut self) -> events::EventsMut<'_> { - self.events.mut_ref() - } -} - -impl Backend for TestBackend { - fn size(&self) -> Size { - self.surface.size - } - - fn next_event(&mut self, _timeout: Duration) -> Option { - self.events.pop() - } - - fn resize(&mut self, new_size: Size, glyph_map: &mut GlyphMap) { - self.surface.resize(new_size, glyph_map); - } - - fn paint<'bp>( - &mut self, - glyph_map: &mut GlyphMap, - widgets: PaintChildren<'_, 'bp>, - attribute_storage: &AttributeStorage<'bp>, - ) { - self.surface.clear(); - paint(&mut self.surface, glyph_map, widgets, attribute_storage); - } - - fn render(&mut self, _glyph_map: &mut GlyphMap) { - // this does nothing here as everything written to the test buffer - } - - fn clear(&mut self) {} -} diff --git a/anathema-backend/src/testing/surface.rs b/anathema-backend/src/testing/surface.rs deleted file mode 100644 index 045f95e8..00000000 --- a/anathema-backend/src/testing/surface.rs +++ /dev/null @@ -1,69 +0,0 @@ -use anathema_geometry::{Pos, Size}; -use anathema_value_resolver::Attributes; -use anathema_widgets::paint::Glyph; -use anathema_widgets::{GlyphMap, Style, WidgetRenderer}; - -#[derive(Debug)] -pub struct TestSurface { - pub(super) size: Size, - lines: Vec, - prev_lines: Vec, -} - -impl TestSurface { - pub fn new(size: Size) -> Self { - let len = size.width * size.height; - let lines = vec![Glyph::space(); len as usize]; - Self { - size, - lines, - prev_lines: vec![], - } - } - - pub(crate) fn resize(&mut self, new_size: Size, _glyph_map: &mut GlyphMap) { - self.size = new_size; - // TODO: this should pop and truncate the lines to fit the new size - } - - // Get from previous lines as the clear function has most likely - // been called by the time this is relevant - pub(crate) fn get(&self, x: usize, y: usize) -> Option<&Glyph> { - let index = y * self.size.width as usize + x; - self.prev_lines.get(index) - } - - // Look at the old lines as the surface has been cleared - // by the time we can inspect the lines - pub(crate) fn line(&self, index: usize) -> &[Glyph] { - let from = index * self.size.width as usize; - let to = from + self.size.width as usize; - &self.prev_lines[from..to] - } - - pub(crate) fn clear(&mut self) { - let len = self.size.width * self.size.height; - let mut lines = vec![Glyph::space(); len as usize]; - std::mem::swap(&mut lines, &mut self.lines); - std::mem::swap(&mut lines, &mut self.prev_lines); - } -} - -impl WidgetRenderer for TestSurface { - fn draw_glyph(&mut self, mut glyph: Glyph, local_pos: Pos) { - let index = self.size.width as i32 * local_pos.y + local_pos.x; - let index = index as usize; - std::mem::swap(&mut glyph, &mut self.lines[index]); - } - - fn set_attributes(&mut self, attribs: &Attributes<'_>, local_pos: Pos) { - let style = Style::from_cell_attribs(attribs); - self.set_style(style, local_pos) - } - - fn set_style(&mut self, _style: Style, _local_pos: Pos) {} - - fn size(&self) -> Size { - self.size - } -} diff --git a/anathema-backend/src/tui/buffer.rs b/anathema-backend/src/tui/buffer.rs deleted file mode 100644 index 3d4773ff..00000000 --- a/anathema-backend/src/tui/buffer.rs +++ /dev/null @@ -1,453 +0,0 @@ -#![deny(missing_docs)] -use std::io::{Result, Write}; -use std::ops::Index; - -use anathema_geometry::Size; -use anathema_widgets::Style; -use anathema_widgets::paint::{Glyph, GlyphMap}; -use crossterm::style::Print; -use crossterm::{QueueableCommand, cursor}; - -use super::LocalPos; -use super::style::write_style; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Cell { - pub(crate) style: Style, - pub(crate) state: CellState, -} - -impl Cell { - pub(crate) fn empty() -> Self { - // It's important to reset the colours as there - // might be residual colors from previous draw. - Self { - style: Style::reset(), - state: CellState::Empty, - } - } - - pub(crate) fn reset() -> Self { - Self { - style: Style::reset(), - state: CellState::Occupied(Glyph::space()), - } - } - - fn continuation(style: Style) -> Self { - Self { - style, - state: CellState::Continuation, - } - } - - pub(crate) fn new(glyph: Glyph, style: Style) -> Self { - Self { - style, - state: CellState::Occupied(glyph), - } - } -} - -/// Represent the state of a cell inside a [`Buffer`]. -#[derive(Copy, Clone, PartialEq)] -pub(crate) enum CellState { - /// Empty - Empty, - /// Occupied by a certain character - Occupied(Glyph), - /// A continuation means this cell is part of another cell - /// representing a value that spans more than two chars, e.g 💖 - Continuation, -} - -impl std::fmt::Debug for CellState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CellState::Empty => write!(f, ""), - CellState::Occupied(Glyph::Single(glyph, _width)) => write!(f, "{glyph}"), - CellState::Occupied(Glyph::Cluster(index, width)) => write!(f, ""), - CellState::Continuation => write!(f, ""), - } - } -} - -/// A buffer contains a list of cells representing characters that can be rendered. -/// This doesn't necessarily have to be `stdout`, it can be anything that implements -/// [`std::io::Write`] -/// -/// The [`crate::Screen`] holds two `Buffer`s, used to only render the difference between frames. -/// -/// The [`crate::Screen`] writes all the chars and their styles to the buffer, which works like a -/// grid. -#[derive(Debug, Clone)] -pub struct Buffer { - size: Size, - pub(crate) inner: Box<[Cell]>, -} - -impl Buffer { - /// Crate a new `Buffer` with a given size. - pub fn new(size: impl Into) -> Self { - let size = size.into(); - Self { - inner: vec![Cell::empty(); (size.width * size.height) as usize].into_boxed_slice(), - size, - } - } - - /// Create a new buffer with reset cells - pub(crate) fn reset(size: impl Into) -> Self { - let size = size.into(); - Self { - inner: vec![Cell::reset(); (size.width * size.height) as usize].into_boxed_slice(), - size, - } - } - - /// The size of the `Buffer` - pub fn size(&self) -> Size { - self.size - } - - /// Resize the buffer, truncating what doesn't fit but keeps what does. - pub fn resize(&mut self, size: Size) { - let mut new_buf = Buffer::new(size); - for (y, line) in self.cell_lines().enumerate() { - if y >= size.height as usize { - break; - } - - for (x, cell) in line.iter().enumerate() { - if x >= size.width as usize { - break; - } - - let pos = LocalPos::new(x as u16, y as u16); - new_buf.put(*cell, pos); - } - } - - self.size = size; - self.inner = new_buf.inner; - } - - /// Put a character with a style at a given position. - pub fn put_glyph(&mut self, glyph: Glyph, pos: LocalPos) { - assert!( - pos.x < self.size.width && pos.y < self.size.height, - "position out of bounds" - ); - - let style = match self.get(pos) { - Some((_, style)) => *style, - None => Style::new(), - }; - - let cell = Cell::new(glyph, style); - self.put(cell, pos); - } - - /// Update the attributes at a given cell. - /// If there is no character at that cell, then write an empty space into it - pub fn update_cell(&mut self, style: Style, pos: LocalPos) { - if pos.x >= self.size.width || pos.y >= self.size.height { - return; - } - - let index = pos.to_index(self.size.width); - let cell = &mut self.inner[index]; - - if let fg @ Some(_) = style.fg { - cell.style.fg = fg; - } - - if let bg @ Some(_) = style.bg { - cell.style.bg = bg; - } - - cell.style.attributes |= style.attributes; - } - - /// Get a reference to a `char` and [`Style`] at a given position inside the buffer. - pub fn get(&self, pos: LocalPos) -> Option<(&Glyph, &Style)> { - if pos.x >= self.size.width || pos.y >= self.size.height { - return None; - } - - let index = pos.to_index(self.size.width); - let cell = self.inner.get(index)?; - match &cell.state { - CellState::Occupied(c) => Some((c, &cell.style)), - _ => None, - } - } - - /// Get a mutable reference to a `char` and [`Style`] at a given position inside the buffer. - pub fn get_mut(&mut self, pos: LocalPos) -> Option<(&mut Glyph, &mut Style)> { - if pos.x >= self.size.width || pos.y >= self.size.height { - return None; - } - - let index = pos.to_index(self.size.width); - let cell = self.inner.get_mut(index)?; - match &mut cell.state { - CellState::Occupied(c) => Some((c, &mut cell.style)), - _ => None, - } - } - - pub(super) fn reset_cell(&mut self, pos: LocalPos) { - let index = pos.to_index(self.size.width); - self.inner[index] = Cell::empty(); - } - - fn put(&mut self, mut cell: Cell, pos: LocalPos) { - let index = pos.to_index(self.size.width); - - if let CellState::Occupied(c) = cell.state { - // If this is a unicode char that is wider than one cell, - // add a continuation cell if it fits, this way if we overwrite it - // we can set the continuation cell to `Empty`. - if pos.x < self.size.width && c.width() >= 2 { - self.put(Cell::continuation(cell.style), LocalPos::new(pos.x + 1, pos.y)); - } - } - - let current = &mut self.inner[index]; - cell.style.merge(current.style); - - match (&mut current.state, cell.state) { - // Merge the styles - (CellState::Occupied(current_char), CellState::Occupied(new_char)) => { - *current_char = new_char; - current.style.attributes |= cell.style.attributes; - - if let Some(col) = cell.style.fg { - current.style.fg = Some(col); - } - - if let Some(col) = cell.style.bg { - current.style.bg = Some(col); - } - } - _ => *current = cell, - } - } - - pub(super) fn cell_lines(&mut self) -> impl Iterator { - self.inner.chunks_mut(self.size.width as usize) - } -} - -impl Index<(usize, usize)> for Buffer { - type Output = Cell; - - fn index(&self, pos: (usize, usize)) -> &Self::Output { - let pos = LocalPos::from(pos); - let index = pos.to_index(self.size.width); - &self.inner[index] - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum Change { - Remove, - Continuation, - Insert(Glyph), -} - -impl Change { - fn width(self) -> usize { - match self { - Self::Remove => 1, - Self::Continuation => 0, - Self::Insert(c) => c.width(), - } - } -} - -pub(crate) fn diff( - old: &mut Buffer, - new: &mut Buffer, - changes: &mut Vec<(LocalPos, Option