Skip to main content

xsd_schema/document/
page.rs

1use std::cell::Cell;
2
3use bumpalo::Bump;
4
5use super::error::BufferDocumentError;
6use super::node::{page_of, slot_of, Node, PAGE_SIZE};
7
8/// Page-based flat node array using `Cell<Node>` for interior mutability.
9///
10/// Nodes are allocated sequentially and addressed by flat `u32` indices.
11/// Pages of [`PAGE_SIZE`] nodes each are arena-allocated on demand.
12/// `Cell<Node>` allows mutation through a shared reference, which is
13/// needed when the builder holds `&mut` to its own state but only `&`
14/// to the pages for sibling/parent fixup.
15pub struct NodePages<'a> {
16    arena: &'a Bump,
17    pages: Vec<&'a [Cell<Node>]>,
18    len: u32,
19}
20
21impl<'a> NodePages<'a> {
22    /// Creates a new `NodePages` with the first page pre-allocated.
23    pub fn new(arena: &'a Bump) -> Self {
24        let first_page = Self::make_page(arena);
25        Self {
26            arena,
27            pages: vec![first_page],
28            len: 0,
29        }
30    }
31
32    /// Allocates the next node slot and returns its flat index.
33    ///
34    /// A new page is allocated when the current page is full.
35    ///
36    /// # Errors
37    ///
38    /// Returns [`BufferDocumentError::Overflow`] if the index would exceed `u32::MAX - 1`.
39    pub fn alloc(&mut self) -> Result<u32, BufferDocumentError> {
40        let idx = self.len;
41        // Reserve u32::MAX as NULL sentinel
42        if idx == u32::MAX {
43            return Err(BufferDocumentError::Overflow);
44        }
45        self.len = idx + 1;
46        // If we just crossed into a new page (and it's not the very first allocation),
47        // allocate the page.
48        if idx > 0 && slot_of(idx) == 0 {
49            self.allocate_page();
50        }
51        Ok(idx)
52    }
53
54    /// Reads the node at the given flat index.
55    #[inline]
56    pub fn get(&self, node_ref: u32) -> Node {
57        let page = page_of(node_ref) as usize;
58        let slot = slot_of(node_ref) as usize;
59        self.pages[page][slot].get()
60    }
61
62    /// Writes a node at the given flat index (interior mutability via `Cell`).
63    #[inline]
64    pub fn set(&self, node_ref: u32, node: Node) {
65        let page = page_of(node_ref) as usize;
66        let slot = slot_of(node_ref) as usize;
67        self.pages[page][slot].set(node);
68    }
69
70    /// Read-modify-write a node at the given flat index.
71    #[inline]
72    pub fn update<F: FnOnce(&mut Node)>(&self, node_ref: u32, f: F) {
73        let page = page_of(node_ref) as usize;
74        let slot = slot_of(node_ref) as usize;
75        let mut node = self.pages[page][slot].get();
76        f(&mut node);
77        self.pages[page][slot].set(node);
78    }
79
80    /// Returns the total number of allocated nodes.
81    #[inline]
82    pub fn len(&self) -> u32 {
83        self.len
84    }
85
86    /// Returns `true` if no nodes have been allocated.
87    #[inline]
88    pub fn is_empty(&self) -> bool {
89        self.len == 0
90    }
91
92    /// Allocates a new page in the arena and appends it.
93    fn allocate_page(&mut self) {
94        let page = Self::make_page(self.arena);
95        self.pages.push(page);
96    }
97
98    /// Arena-allocates a single page of `Cell<Node>`.
99    fn make_page(arena: &Bump) -> &[Cell<Node>] {
100        arena.alloc_slice_fill_with(PAGE_SIZE as usize, |_| Cell::new(Node::default()))
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::super::node::{NodeType, NULL, PAGE_SIZE};
107    use super::*;
108
109    #[test]
110    fn first_alloc_returns_zero() {
111        let arena = Bump::new();
112        let mut pages = NodePages::new(&arena);
113        assert_eq!(pages.alloc().unwrap(), 0);
114    }
115
116    #[test]
117    fn sequential_alloc() {
118        let arena = Bump::new();
119        let mut pages = NodePages::new(&arena);
120        for expected in 0..10 {
121            assert_eq!(pages.alloc().unwrap(), expected);
122        }
123    }
124
125    #[test]
126    fn set_get_round_trip() {
127        let arena = Bump::new();
128        let mut pages = NodePages::new(&arena);
129        let idx = pages.alloc().unwrap();
130
131        let mut node = Node::default();
132        node.set_node_type(NodeType::Element);
133        node.value = 42;
134        node.parent = NULL;
135        node.next_sibling = NULL;
136
137        pages.set(idx, node);
138        let read = pages.get(idx);
139        assert_eq!(read.node_type(), NodeType::Element);
140        assert_eq!(read.value, 42);
141        assert_eq!(read.parent, NULL);
142        assert_eq!(read.next_sibling, NULL);
143    }
144
145    #[test]
146    fn update_modifies_in_place() {
147        let arena = Bump::new();
148        let mut pages = NodePages::new(&arena);
149        let idx = pages.alloc().unwrap();
150
151        let mut node = Node::default();
152        node.set_node_type(NodeType::Text);
153        node.value = 10;
154        pages.set(idx, node);
155
156        pages.update(idx, |n| {
157            n.value = 99;
158            n.set_flag(Node::HAS_CHILDREN);
159        });
160
161        let read = pages.get(idx);
162        assert_eq!(read.value, 99);
163        assert!(read.has_flag(Node::HAS_CHILDREN));
164        assert_eq!(read.node_type(), NodeType::Text); // unchanged
165    }
166
167    #[test]
168    fn cross_page_allocation() {
169        let arena = Bump::new();
170        let mut pages = NodePages::new(&arena);
171
172        // Allocate PAGE_SIZE + 1 nodes to force a second page
173        let count = PAGE_SIZE + 1;
174        for i in 0..count {
175            let idx = pages.alloc().unwrap();
176            assert_eq!(idx, i);
177            // Write a distinguishing value
178            pages.set(
179                idx,
180                Node {
181                    value: i,
182                    ..Node::default()
183                },
184            );
185        }
186
187        assert_eq!(pages.len(), count);
188
189        // Verify last node on first page
190        let last_first = PAGE_SIZE - 1;
191        assert_eq!(pages.get(last_first).value, last_first);
192
193        // Verify first node on second page
194        assert_eq!(pages.get(PAGE_SIZE).value, PAGE_SIZE);
195    }
196
197    #[test]
198    fn len_tracks_allocations() {
199        let arena = Bump::new();
200        let mut pages = NodePages::new(&arena);
201        assert_eq!(pages.len(), 0);
202        assert!(pages.is_empty());
203
204        pages.alloc().unwrap();
205        assert_eq!(pages.len(), 1);
206        assert!(!pages.is_empty());
207
208        for _ in 0..9 {
209            pages.alloc().unwrap();
210        }
211        assert_eq!(pages.len(), 10);
212    }
213
214    #[test]
215    fn default_node_is_nul() {
216        let arena = Bump::new();
217        let mut pages = NodePages::new(&arena);
218        let idx = pages.alloc().unwrap();
219        let node = pages.get(idx);
220        assert_eq!(node.node_type(), NodeType::Nul);
221        assert_eq!(node.value, 0);
222        assert_eq!(node.parent, 0);
223        assert_eq!(node.next_sibling, 0);
224    }
225
226    #[test]
227    fn set_with_shared_ref() {
228        // Verify that `set` works through `&self` (not `&mut self`).
229        let arena = Bump::new();
230        let mut pages = NodePages::new(&arena);
231        let idx = pages.alloc().unwrap();
232
233        // Use shared reference for set
234        let pages_ref: &NodePages = &pages;
235        let mut node = Node::default();
236        node.set_node_type(NodeType::Comment);
237        node.value = 7;
238        pages_ref.set(idx, node);
239
240        assert_eq!(pages.get(idx).node_type(), NodeType::Comment);
241        assert_eq!(pages.get(idx).value, 7);
242    }
243
244    #[test]
245    fn alloc_overflow_returns_error() {
246        let arena = Bump::new();
247        let mut pages = NodePages::new(&arena);
248        // Force len to u32::MAX so the next alloc hits the overflow guard.
249        pages.len = u32::MAX;
250        assert!(matches!(pages.alloc(), Err(BufferDocumentError::Overflow)));
251    }
252}