formualizer_eval/engine/
debug_views.rs

1use super::vertex::{VertexId, VertexKind};
2use super::vertex_store::VertexStore;
3use std::fmt;
4
5/// Immutable view into a vertex's data in the columnar store
6///
7/// Provides zero-cost abstraction for ergonomic access to vertex fields
8pub struct VertexView<'s> {
9    store: &'s VertexStore,
10    id: VertexId,
11}
12
13impl<'s> VertexView<'s> {
14    #[inline]
15    pub fn new(store: &'s VertexStore, id: VertexId) -> Self {
16        Self { store, id }
17    }
18
19    #[inline]
20    pub fn id(&self) -> VertexId {
21        self.id
22    }
23
24    #[inline]
25    pub fn row(&self) -> u32 {
26        self.store.coord(self.id).row()
27    }
28
29    #[inline]
30    pub fn col(&self) -> u32 {
31        self.store.coord(self.id).col()
32    }
33
34    #[inline]
35    pub fn sheet_id(&self) -> u16 {
36        self.store.sheet_id(self.id)
37    }
38
39    #[inline]
40    pub fn is_dirty(&self) -> bool {
41        self.store.is_dirty(self.id)
42    }
43
44    #[inline]
45    pub fn is_volatile(&self) -> bool {
46        self.store.is_volatile(self.id)
47    }
48
49    #[inline]
50    pub fn is_deleted(&self) -> bool {
51        self.store.is_deleted(self.id)
52    }
53
54    #[inline]
55    pub fn kind(&self) -> VertexKind {
56        self.store.kind(self.id)
57    }
58
59    #[inline]
60    pub fn value_ref(&self) -> u32 {
61        self.store.value_ref(self.id)
62    }
63
64    #[inline]
65    pub fn edge_offset(&self) -> u32 {
66        self.store.edge_offset(self.id)
67    }
68
69    #[inline]
70    pub fn flags(&self) -> u8 {
71        self.store.flags(self.id)
72    }
73}
74
75impl<'s> fmt::Debug for VertexView<'s> {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.debug_struct("VertexView")
78            .field("id", &self.id)
79            .field("row", &self.row())
80            .field("col", &self.col())
81            .field("sheet_id", &self.sheet_id())
82            .field("kind", &self.kind())
83            .field("dirty", &self.is_dirty())
84            .field("volatile", &self.is_volatile())
85            .field("deleted", &self.is_deleted())
86            .field("value_ref", &format!("0x{:08x}", self.value_ref()))
87            .field("edge_offset", &self.edge_offset())
88            .finish()
89    }
90}
91
92impl<'s> fmt::Display for VertexView<'s> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        // Format as Excel-style reference
95        use crate::reference::Coord;
96        let col_letters = Coord::col_to_letters(self.col());
97        let row_1based = self.row() + 1;
98
99        if self.sheet_id() == 0 {
100            write!(f, "{col_letters}{row_1based}")
101        } else {
102            write!(f, "Sheet{}!{}{}", self.sheet_id(), col_letters, row_1based)
103        }
104    }
105}
106
107/// Mutable view into a vertex's data in the columnar store
108pub struct VertexViewMut<'s> {
109    store: &'s mut VertexStore,
110    id: VertexId,
111}
112
113impl<'s> VertexViewMut<'s> {
114    #[inline]
115    pub fn new(store: &'s mut VertexStore, id: VertexId) -> Self {
116        Self { store, id }
117    }
118
119    #[inline]
120    pub fn id(&self) -> VertexId {
121        self.id
122    }
123
124    // Read accessors (same as immutable view)
125    #[inline]
126    pub fn row(&self) -> u32 {
127        self.store.coord(self.id).row()
128    }
129
130    #[inline]
131    pub fn col(&self) -> u32 {
132        self.store.coord(self.id).col()
133    }
134
135    #[inline]
136    pub fn sheet_id(&self) -> u16 {
137        self.store.sheet_id(self.id)
138    }
139
140    #[inline]
141    pub fn is_dirty(&self) -> bool {
142        self.store.is_dirty(self.id)
143    }
144
145    #[inline]
146    pub fn is_volatile(&self) -> bool {
147        self.store.is_volatile(self.id)
148    }
149
150    #[inline]
151    pub fn kind(&self) -> VertexKind {
152        self.store.kind(self.id)
153    }
154
155    // Write accessors
156    #[inline]
157    pub fn set_kind(&mut self, kind: VertexKind) {
158        self.store.set_kind(self.id, kind);
159    }
160
161    #[inline]
162    pub fn set_dirty(&mut self, dirty: bool) {
163        self.store.set_dirty(self.id, dirty);
164    }
165
166    #[inline]
167    pub fn set_volatile(&mut self, volatile: bool) {
168        self.store.set_volatile(self.id, volatile);
169    }
170
171    #[inline]
172    pub fn set_value_ref(&mut self, value_ref: u32) {
173        self.store.set_value_ref(self.id, value_ref);
174    }
175
176    #[inline]
177    pub fn set_edge_offset(&mut self, offset: u32) {
178        self.store.set_edge_offset(self.id, offset);
179    }
180}
181
182impl<'s> fmt::Debug for VertexViewMut<'s> {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        f.debug_struct("VertexViewMut")
185            .field("id", &self.id)
186            .field("row", &self.row())
187            .field("col", &self.col())
188            .field("sheet_id", &self.sheet_id())
189            .field("kind", &self.kind())
190            .field("dirty", &self.is_dirty())
191            .field("volatile", &self.is_volatile())
192            .finish()
193    }
194}
195
196// Extension methods for VertexStore
197impl VertexStore {
198    /// Create an immutable view for a vertex
199    #[inline]
200    pub fn view(&self, id: VertexId) -> VertexView {
201        VertexView::new(self, id)
202    }
203
204    /// Create a mutable view for a vertex
205    #[inline]
206    pub fn view_mut(&mut self, id: VertexId) -> VertexViewMut {
207        VertexViewMut::new(self, id)
208    }
209
210    /// Debug helper to dump vertex info
211    pub fn debug_vertex(&self, id: VertexId) -> String {
212        let view = self.view(id);
213        format!("{view:?}")
214    }
215
216    /// Debug helper to dump a range of vertices
217    pub fn debug_range(&self, start: VertexId, count: usize) -> Vec<String> {
218        (0..count)
219            .map(|i| {
220                let id = VertexId(start.0 + i as u32);
221                self.debug_vertex(id)
222            })
223            .collect()
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::engine::packed_coord::PackedCoord;
231
232    #[test]
233    fn test_vertex_view_access() {
234        let mut store = VertexStore::new();
235        let id = store.allocate(PackedCoord::new(5, 10), 2, 0x03);
236
237        let view = store.view(id);
238        assert_eq!(view.row(), 5);
239        assert_eq!(view.col(), 10);
240        assert_eq!(view.sheet_id(), 2);
241        assert!(view.is_dirty());
242        assert!(view.is_volatile());
243    }
244
245    #[test]
246    fn test_debug_output() {
247        let mut store = VertexStore::new();
248        let id = store.allocate(PackedCoord::new(1, 1), 0, 0x01);
249        store.set_kind(id, VertexKind::Cell);
250
251        let view = store.view(id);
252        let debug = format!("{view:?}");
253        assert!(debug.contains("row: 1"));
254        assert!(debug.contains("col: 1"));
255        assert!(debug.contains("Cell"));
256    }
257
258    #[test]
259    fn test_mutable_view() {
260        let mut store = VertexStore::new();
261        let id = store.allocate(PackedCoord::new(0, 0), 0, 0);
262
263        {
264            let mut view = store.view_mut(id);
265            view.set_dirty(true);
266            view.set_volatile(true);
267            view.set_value_ref(42);
268        }
269
270        assert!(store.is_dirty(id));
271        assert!(store.is_volatile(id));
272        assert_eq!(store.value_ref(id), 42);
273    }
274
275    #[test]
276    fn test_view_lifetime() {
277        let mut store = VertexStore::new();
278        let id1 = store.allocate(PackedCoord::new(0, 0), 0, 0);
279        let id2 = store.allocate(PackedCoord::new(1, 1), 0, 0);
280
281        // Multiple immutable views should work
282        let view1 = store.view(id1);
283        let view2 = store.view(id2);
284
285        assert_eq!(view1.row(), 0);
286        assert_eq!(view2.row(), 1);
287    }
288
289    #[test]
290    fn test_view_display() {
291        let mut store = VertexStore::new();
292        let id = store.allocate(PackedCoord::new(0, 5), 1, 0x01);
293        store.set_kind(id, VertexKind::Cell);
294
295        let view = store.view(id);
296        let display = format!("{view}");
297        assert!(display.contains("Sheet1!F1")); // col 5 = F, row 0 = 1 (1-based)
298    }
299
300    #[test]
301    fn test_zero_cost_abstraction() {
302        // Verify that view methods are truly zero-cost
303        // This test ensures inlining happens correctly
304        let mut store = VertexStore::new();
305        let id = store.allocate(PackedCoord::new(100, 200), 5, 0x07);
306
307        // These should compile to direct array access
308        let view = store.view(id);
309
310        // Multiple accesses should be optimized
311        let row1 = view.row();
312        let row2 = view.row();
313        assert_eq!(row1, row2);
314        assert_eq!(row1, 100);
315
316        // Verify all accessors work efficiently
317        assert_eq!(view.col(), 200);
318        assert_eq!(view.sheet_id(), 5);
319        assert!(view.is_dirty());
320        assert!(view.is_volatile());
321        assert!(view.is_deleted());
322    }
323}