Skip to main content

cranpose_foundation/modifier/
chain_node_ref.rs

1//! Read-only handles on the positions of a [`ModifierNodeChain`], and the
2//! iterator over them.
3
4use super::{ModifierNode, ModifierNodeChain, NodeCapabilities, NodePath};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7enum TraversalDirection {
8    Forward,
9    Backward,
10}
11
12/// Iterator walking a modifier chain by indexing into its order.
13pub struct ModifierChainIter<'a> {
14    chain: &'a ModifierNodeChain,
15    cursor: usize,
16    remaining: usize,
17    direction: TraversalDirection,
18}
19
20impl<'a> ModifierChainIter<'a> {
21    pub(super) fn forward(chain: &'a ModifierNodeChain) -> Self {
22        Self {
23            chain,
24            cursor: 0,
25            remaining: chain.order_len(),
26            direction: TraversalDirection::Forward,
27        }
28    }
29
30    pub(super) fn backward(chain: &'a ModifierNodeChain) -> Self {
31        let len = chain.order_len();
32        Self {
33            chain,
34            cursor: len.wrapping_sub(1),
35            remaining: len,
36            direction: TraversalDirection::Backward,
37        }
38    }
39}
40
41impl<'a> Iterator for ModifierChainIter<'a> {
42    type Item = ModifierChainNodeRef<'a>;
43
44    #[inline]
45    fn next(&mut self) -> Option<Self::Item> {
46        if self.remaining == 0 {
47            return None;
48        }
49        let node_ref = self.chain.node_ref(ChainPosition::Node(self.cursor));
50        self.remaining -= 1;
51        match self.direction {
52            TraversalDirection::Forward => self.cursor += 1,
53            TraversalDirection::Backward => self.cursor = self.cursor.wrapping_sub(1),
54        }
55        Some(node_ref)
56    }
57
58    #[inline]
59    fn size_hint(&self) -> (usize, Option<usize>) {
60        (self.remaining, Some(self.remaining))
61    }
62}
63
64impl ExactSizeIterator for ModifierChainIter<'_> {}
65impl std::iter::FusedIterator for ModifierChainIter<'_> {}
66
67/// A cheap read-only handle on one position of a [`ModifierNodeChain`]: a node or the head or tail
68/// marker. Obtain it from `head`, `tail` or the chain iterators and walk the chain with `parent`
69/// and `child`.
70#[derive(Clone)]
71pub struct ModifierChainNodeRef<'a> {
72    pub(super) chain: &'a ModifierNodeChain,
73    pub(super) position: ChainPosition,
74}
75
76// Where a node ref points: the head marker, the node at an index of the
77// chain's order, or the tail marker.
78#[derive(Clone, Copy)]
79pub(super) enum ChainPosition {
80    Head,
81    Node(usize),
82    Tail,
83}
84
85impl ModifierChainNodeRef<'_> {
86    fn path(&self) -> Option<NodePath> {
87        match self.position {
88            ChainPosition::Node(index) => Some(self.chain.order_at(index).0),
89            ChainPosition::Head | ChainPosition::Tail => None,
90        }
91    }
92
93    /// Provides access to the node via a closure, properly handling RefCell borrows.
94    /// Returns None for sentinel nodes.
95    pub fn with_node<R>(&self, f: impl FnOnce(&dyn ModifierNode) -> R) -> Option<R> {
96        self.chain.with_path(self.path()?, f)
97    }
98
99    /// Returns the parent reference, including sentinel head when applicable.
100    #[inline]
101    pub fn parent(&self) -> Option<Self> {
102        let position = match self.position {
103            ChainPosition::Head => return None,
104            ChainPosition::Node(index) => index.checked_sub(1),
105            ChainPosition::Tail => self.chain.order_len().checked_sub(1),
106        }
107        .map_or(ChainPosition::Head, ChainPosition::Node);
108        Some(self.chain.node_ref(position))
109    }
110
111    /// Returns the child reference, including sentinel tail for the last entry.
112    #[inline]
113    pub fn child(&self) -> Option<Self> {
114        let next = match self.position {
115            ChainPosition::Head => 0,
116            ChainPosition::Node(index) => index + 1,
117            ChainPosition::Tail => return None,
118        };
119        let position = if next < self.chain.order_len() {
120            ChainPosition::Node(next)
121        } else {
122            ChainPosition::Tail
123        };
124        Some(self.chain.node_ref(position))
125    }
126
127    /// Returns the capability mask for this specific node.
128    #[inline]
129    pub fn kind_set(&self) -> NodeCapabilities {
130        match self.position {
131            ChainPosition::Node(index) => self.chain.order_at(index).1,
132            ChainPosition::Head | ChainPosition::Tail => NodeCapabilities::empty(),
133        }
134    }
135
136    /// Returns the entry index backing this node when it is part of the chain.
137    pub fn entry_index(&self) -> Option<usize> {
138        self.path().map(|path| path.entry())
139    }
140
141    /// Returns how many delegate hops separate this node from its root element.
142    pub fn delegate_depth(&self) -> usize {
143        self.path().map_or(0, |path| path.delegates().len())
144    }
145
146    /// Returns the aggregated capability mask for the subtree rooted at this node.
147    #[inline]
148    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
149        match self.position {
150            ChainPosition::Head => self.chain.aggregated_capabilities,
151            ChainPosition::Node(index) => self.chain.order_at(index).2,
152            ChainPosition::Tail => NodeCapabilities::empty(),
153        }
154    }
155
156    /// Returns true if this reference targets the sentinel head.
157    pub fn is_head(&self) -> bool {
158        matches!(self.position, ChainPosition::Head)
159    }
160
161    /// Returns true if this reference targets the sentinel tail.
162    pub fn is_tail(&self) -> bool {
163        matches!(self.position, ChainPosition::Tail)
164    }
165
166    /// Returns true if this reference targets either sentinel.
167    pub fn is_sentinel(&self) -> bool {
168        matches!(self.position, ChainPosition::Head | ChainPosition::Tail)
169    }
170
171    /// Returns true if this node has any capability bits present in `mask`.
172    pub fn has_capability(&self, mask: NodeCapabilities) -> bool {
173        !mask.is_empty() && self.kind_set().intersects(mask)
174    }
175
176    /// Visits descendant nodes, optionally including `self`, in insertion order.
177    pub fn visit_descendants<F>(self, include_self: bool, mut f: F)
178    where
179        F: FnMut(Self),
180    {
181        let mut current = if include_self {
182            Some(self)
183        } else {
184            self.child()
185        };
186        while let Some(node) = current {
187            if node.is_tail() {
188                break;
189            }
190            if !node.is_sentinel() {
191                f(node.clone());
192            }
193            current = node.child();
194        }
195    }
196
197    /// Visits descendant nodes that match `mask`, short-circuiting when possible.
198    pub fn visit_descendants_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
199    where
200        F: FnMut(Self),
201    {
202        if mask.is_empty() {
203            self.visit_descendants(include_self, f);
204            return;
205        }
206
207        if !self.aggregate_child_capabilities().intersects(mask) {
208            return;
209        }
210
211        self.visit_descendants(include_self, |node| {
212            if node.kind_set().intersects(mask) {
213                f(node);
214            }
215        });
216    }
217
218    /// Visits ancestor nodes up to (but excluding) the sentinel head.
219    pub fn visit_ancestors<F>(self, include_self: bool, mut f: F)
220    where
221        F: FnMut(Self),
222    {
223        let mut current = if include_self {
224            Some(self)
225        } else {
226            self.parent()
227        };
228        while let Some(node) = current {
229            if node.is_head() {
230                break;
231            }
232            f(node.clone());
233            current = node.parent();
234        }
235    }
236
237    /// Visits ancestor nodes that match `mask`.
238    pub fn visit_ancestors_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
239    where
240        F: FnMut(Self),
241    {
242        if mask.is_empty() {
243            self.visit_ancestors(include_self, f);
244            return;
245        }
246
247        self.visit_ancestors(include_self, |node| {
248            if node.kind_set().intersects(mask) {
249                f(node);
250            }
251        });
252    }
253}