Skip to main content

ferroui_base/data/core/expression_nodes/
expression_node.rs

1use crate::data::core::plugins::IPropertyAccessor;
2use crate::data::core::{BindingExpression, ValueType, ValueTypes, WeakValue};
3use crate::data::{BindingError, BindingErrorType, BindingNotification};
4use crate::{BoxedValue, FerroObject, FerroProperty, Ref};
5use std::any::Any;
6use std::cell::{Cell, RefCell};
7use std::rc::{Rc, Weak};
8
9/// A source for an expression node: unset, or a (possibly null) value.
10#[derive(Clone)]
11pub enum NodeSource {
12    Unset,
13    Value(Option<BoxedValue>),
14}
15
16/// The state every expression node has: its place in the binding path, its
17/// source and its current value.
18pub struct NodeState {
19    index: Cell<usize>,
20    owner: RefCell<Option<Weak<BindingExpression>>>,
21    source: RefCell<Option<WeakValue>>,
22    value: RefCell<HeldValue>,
23}
24
25/// The value of a node, as the node holds it.
26enum HeldValue {
27    Value(Option<BoxedValue>),
28    /// The value of a node that locates an element
29    /// ([`NodeState::locating_an_element`]).
30    Element(Option<WeakValue>),
31}
32
33impl Default for NodeState {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl NodeState {
40    pub fn new() -> Self {
41        Self {
42            index: Cell::new(0),
43            owner: RefCell::new(None),
44            source: RefCell::new(None),
45            value: RefCell::new(HeldValue::Value(Some(FerroProperty::unset_value()))),
46        }
47    }
48
49    /// The state of a node whose value is an element it locates from the
50    /// target of the binding: an element of a name scope, an ancestor, the
51    /// templated parent.
52    ///
53    /// Such a node holds its value weakly (DEVIATIONS.md, Bindings). The
54    /// element it finds is kept alive by the tree it was found in, and that
55    /// tree most often owns the target of the binding, which owns the
56    /// binding and its nodes: held strongly, as the managed original holds
57    /// the value of every node, the element and the target keep each other
58    /// alive. The value reads null once the element is gone.
59    pub fn locating_an_element() -> Self {
60        Self { value: RefCell::new(HeldValue::Element(Some(WeakValue::new(&FerroProperty::unset_value())))), ..Self::new() }
61    }
62
63    /// The index of the node in the binding path.
64    pub fn index(&self) -> usize {
65        self.index.get()
66    }
67
68    /// The owning binding expression.
69    pub fn owner(&self) -> Option<Rc<BindingExpression>> {
70        self.owner.borrow().as_ref().and_then(Weak::upgrade)
71    }
72
73    /// The source object from which the node reads its value.
74    pub fn source(&self) -> Option<BoxedValue> {
75        let source = self.source.borrow().clone();
76        source.and_then(|s| s.upgrade())
77    }
78
79    /// The source viewed as an object of the class hierarchy.
80    pub fn source_object(&self) -> Option<Ref<FerroObject>> {
81        self.source().and_then(|s| ValueTypes::as_object(&*s))
82    }
83
84    /// The current value of the node. May be the unset marker.
85    pub fn value(&self) -> Option<BoxedValue> {
86        match &*self.value.borrow() {
87            HeldValue::Value(value) => value.clone(),
88            HeldValue::Element(value) => value.as_ref().and_then(WeakValue::upgrade),
89        }
90    }
91
92    pub(crate) fn set_raw_value(&self, value: Option<BoxedValue>) {
93        let mut held = self.value.borrow_mut();
94        let old = match &*held {
95            HeldValue::Value(_) => std::mem::replace(&mut *held, HeldValue::Value(value)),
96            HeldValue::Element(_) => {
97                std::mem::replace(&mut *held, HeldValue::Element(value.as_ref().map(WeakValue::new)))
98            }
99        };
100        // The old value is dropped after the cell is released: dropping it
101        // may run code that reads the node.
102        drop(held);
103        drop(old);
104    }
105
106    /// Sets the current value to the unset marker, notifying the owner.
107    pub fn clear_value(&self) {
108        self.set_value(Some(FerroProperty::unset_value()), None);
109    }
110
111    /// Called by a node which has a null-conditional operator applied to it
112    /// when its source is null: the value of this and all subsequent nodes
113    /// becomes null and the binding produces null rather than an error.
114    pub fn short_circuit_null(&self) {
115        self.set_raw_value(None);
116        if let Some(owner) = self.owner() {
117            owner.on_node_null_short_circuit(self.index());
118        }
119    }
120
121    /// Notifies the owner of a data validation error.
122    pub fn set_data_validation_error(&self, error: BindingError) {
123        if let Some(owner) = self.owner() {
124            owner.on_data_validation_error(error);
125        }
126    }
127
128    /// Sets the current value to the unset marker and notifies the owner of
129    /// the error.
130    pub fn set_error(&self, message: &str) {
131        self.set_raw_value(Some(FerroProperty::unset_value()));
132        if let Some(owner) = self.owner() {
133            owner.on_node_error(self.index() as isize, message);
134        }
135    }
136
137    /// Sets the current value from a value that may be a boxed
138    /// [`BindingNotification`].
139    pub fn set_value_or_notification(&self, value_or_notification: Option<BoxedValue>) {
140        let Some(notification) = value_or_notification.as_ref().and_then(|v| v.downcast_ref::<BindingNotification>())
141        else {
142            self.set_value(value_or_notification, None);
143            return;
144        };
145        match notification.error_type() {
146            BindingErrorType::Error => {
147                let message = notification.error().map(|e| e.to_string()).unwrap_or_default();
148                self.set_error(&message);
149            }
150            BindingErrorType::DataValidationError => {
151                if notification.has_value() {
152                    let inner = notification.value();
153                    if inner.as_ref().is_some_and(|v| v.is::<BindingNotification>()) {
154                        self.set_value_or_notification(inner);
155                    } else {
156                        self.set_value(inner, notification.error().as_ref());
157                    }
158                } else if let Some(error) = notification.error() {
159                    self.set_data_validation_error(error);
160                }
161            }
162            BindingErrorType::None => self.set_value_or_notification_inner(notification.value()),
163        }
164    }
165
166    fn set_value_or_notification_inner(&self, value: Option<BoxedValue>) {
167        if value.as_ref().is_some_and(|v| v.is::<BindingNotification>()) {
168            self.set_value_or_notification(value);
169        } else {
170            self.set_value(value, None);
171        }
172    }
173
174    /// Sets the current value, notifying the owner.
175    pub fn set_value(&self, value: Option<BoxedValue>, data_validation_error: Option<&BindingError>) {
176        debug_assert!(!value.as_ref().is_some_and(|v| v.is::<BindingNotification>()));
177        self.set_raw_value(value.clone());
178        if let Some(owner) = self.owner() {
179            owner.on_node_value_changed(self.index(), value, data_validation_error);
180        }
181    }
182
183    /// Validates that the source is non-null, raising a node error if it is
184    /// not.
185    pub fn validate_non_null_source(&self, source: Option<&BoxedValue>) -> bool {
186        if source.is_none() {
187            if let Some(owner) = self.owner() {
188                owner.on_node_error(self.index() as isize - 1, "Value is null.");
189            }
190            self.set_raw_value(None);
191            return false;
192        }
193        true
194    }
195}
196
197/// A node of a binding path that can be written to.
198pub trait ISettableNode {
199    /// The type of the value accepted by the node, or `None` if the node is
200    /// not currently settable.
201    fn value_type(&self) -> Option<ValueType>;
202
203    /// Tries to write the specified value to the source.
204    fn write_value_to_source(
205        &self,
206        value: Option<&BoxedValue>,
207        nodes: &[Rc<dyn ExpressionNode>],
208    ) -> Result<bool, BindingError>;
209}
210
211/// A node that reads a property through a property accessor.
212pub trait IPropertyAccessorNode {
213    fn property_name(&self) -> &str;
214    fn accessor(&self) -> Option<Rc<dyn IPropertyAccessor>>;
215    fn enable_data_validation(&self);
216}
217
218/// A node that selects the source of a binding expression.
219pub trait SourceNode {
220    /// Selects the source for the binding expression based on the binding
221    /// source (`None` when no source was given), target and anchor.
222    fn select_source(
223        &self,
224        source: Option<&Option<BoxedValue>>,
225        target: &Ref<FerroObject>,
226        _anchor: Option<&Ref<FerroObject>>,
227    ) -> Option<BoxedValue> {
228        match source {
229            Some(source) => source.clone(),
230            None => Some(Rc::new(target.clone())),
231        }
232    }
233
234    fn should_log_errors(&self, state: &NodeState, _target: &FerroObject) -> bool {
235        state.value().is_some()
236    }
237}
238
239/// A node in a binding path: reads a value from its source and passes it to
240/// the next node (or, for the last node, to the binding expression).
241pub trait ExpressionNode: Any {
242    fn state(&self) -> &NodeState;
243
244    /// Appends a string representation of the node to `builder`.
245    fn build_string(&self, _builder: &mut String) {}
246
247    /// Builds a string representation of the binding path up to this node.
248    fn build_string_with_nodes(&self, builder: &mut String, nodes: &[Rc<dyn ExpressionNode>]) {
249        let index = self.state().index();
250        if index > 0 {
251            nodes[index - 1].build_string_with_nodes(builder, nodes);
252        }
253        self.build_string(builder);
254    }
255
256    /// Subscribes to the new source and updates the current value.
257    fn on_source_changed(&self, source: Option<&BoxedValue>, data_validation_error: Option<&BindingError>);
258
259    /// Unsubscribes from the previous source.
260    fn unsubscribe(&self, _old_source: &BoxedValue) {}
261
262    fn as_settable(&self) -> Option<&dyn ISettableNode> {
263        None
264    }
265
266    fn as_property_accessor_node(&self) -> Option<&dyn IPropertyAccessorNode> {
267        None
268    }
269
270    fn as_source_node(&self) -> Option<&dyn SourceNode> {
271        None
272    }
273
274    /// Whether the node reads a data context.
275    fn is_data_context_node(&self) -> bool {
276        false
277    }
278}
279
280impl dyn ExpressionNode {
281    /// Sets the owner binding. Panics if the node already has an owner.
282    pub fn set_owner(&self, owner: Weak<BindingExpression>, index: usize) {
283        let state = self.state();
284        let mut slot = state.owner.borrow_mut();
285        if slot.is_some() {
286            panic!("The expression node already has an owner.");
287        }
288        *slot = Some(owner);
289        state.index.set(index);
290    }
291
292    /// Sets the source from which the node reads its value and updates the
293    /// current value, notifying the owner if the value changes.
294    pub fn set_source(&self, source: NodeSource, data_validation_error: Option<&BindingError>) {
295        let state = self.state();
296        let old_source = state.source();
297        let unchanged = match (&source, &old_source) {
298            (NodeSource::Unset, None) => true,
299            (NodeSource::Value(Some(new)), Some(old)) => Rc::ptr_eq(new, old) || (**new).value_eq(&**old),
300            _ => false,
301        };
302        if unchanged {
303            return;
304        }
305        if let Some(old) = &old_source {
306            self.unsubscribe(old);
307        }
308        match source {
309            NodeSource::Unset => {
310                // If the source is unset then the value is unset. The source
311                // change is deliberately not processed: errors must not be
312                // raised for subsequent nodes in the binding chain.
313                state.source.replace(None);
314                state.set_raw_value(Some(FerroProperty::unset_value()));
315            }
316            NodeSource::Value(value) => {
317                state.source.replace(value.as_ref().map(WeakValue::new));
318                self.on_source_changed(value.as_ref(), data_validation_error);
319            }
320        }
321    }
322
323    /// Sets the value of the node to null without notifying the owner, and
324    /// unsubscribes from the current source.
325    pub(crate) fn propagate_null_short_circuit_value(&self) {
326        self.set_source(NodeSource::Unset, None);
327        self.state().set_raw_value(None);
328    }
329}
330
331/// Appends a member name to a binding path description.
332pub(crate) fn append_member(builder: &mut String, name: &str) {
333    if !builder.is_empty() && !builder.ends_with('!') {
334        builder.push('.');
335    }
336    builder.push_str(name);
337}