Skip to main content

shuck_ast/
arena.rs

1use std::marker::PhantomData;
2
3/// A compact typed index into an arena-backed store.
4#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub struct Idx<T> {
6    raw: u32,
7    marker: PhantomData<fn() -> T>,
8}
9
10impl<T> Idx<T> {
11    /// Creates an index from a `usize`, panicking when it does not fit in `u32`.
12    pub fn new(index: usize) -> Self {
13        let raw =
14            u32::try_from(index).unwrap_or_else(|err| panic!("arena index must fit in u32: {err}"));
15        Self::from_raw(raw)
16    }
17
18    /// Creates an index from its raw representation.
19    pub const fn from_raw(raw: u32) -> Self {
20        Self {
21            raw,
22            marker: PhantomData,
23        }
24    }
25
26    /// Returns this index as a `usize` for slice access.
27    pub const fn index(self) -> usize {
28        self.raw as usize
29    }
30
31    /// Returns this index as its packed raw value.
32    pub const fn raw(self) -> u32 {
33        self.raw
34    }
35}
36
37impl<T> Clone for Idx<T> {
38    fn clone(&self) -> Self {
39        *self
40    }
41}
42
43impl<T> Copy for Idx<T> {}
44
45/// A compact typed contiguous range into an arena-backed store.
46#[derive(Debug, PartialEq, Eq, Hash)]
47pub struct IdRange<T> {
48    start: u32,
49    len: u32,
50    marker: PhantomData<fn() -> T>,
51}
52
53impl<T> IdRange<T> {
54    /// Returns an empty range.
55    pub const fn empty() -> Self {
56        Self {
57            start: 0,
58            len: 0,
59            marker: PhantomData,
60        }
61    }
62
63    /// Creates a range from a typed start index and length.
64    pub fn new(start: Idx<T>, len: usize) -> Self {
65        Self::from_start_len(start.index(), len)
66    }
67
68    /// Creates a range from untyped start and length values.
69    pub fn from_start_len(start: usize, len: usize) -> Self {
70        let end = start
71            .checked_add(len)
72            .unwrap_or_else(|| panic!("arena range end must not overflow usize"));
73        if end > u32::MAX as usize {
74            panic!("arena range end must fit in u32");
75        }
76        let start = u32::try_from(start)
77            .unwrap_or_else(|err| panic!("arena range start must fit in u32: {err}"));
78        let len = u32::try_from(len)
79            .unwrap_or_else(|err| panic!("arena range length must fit in u32: {err}"));
80        Self {
81            start,
82            len,
83            marker: PhantomData,
84        }
85    }
86
87    /// Returns the first index in the range.
88    pub const fn start(self) -> Idx<T> {
89        Idx::from_raw(self.start)
90    }
91
92    /// Returns the start offset as a `usize`.
93    pub const fn start_index(self) -> usize {
94        self.start as usize
95    }
96
97    /// Returns the end offset as a `usize`.
98    pub const fn end_index(self) -> usize {
99        self.start as usize + self.len as usize
100    }
101
102    /// Returns the number of items in the range.
103    pub const fn len(self) -> usize {
104        self.len as usize
105    }
106
107    /// Returns `true` when the range contains no items.
108    pub const fn is_empty(self) -> bool {
109        self.len == 0
110    }
111
112    /// Returns the slice covered by this range.
113    pub fn slice(self, store: &[T]) -> &[T] {
114        &store[self.start_index()..self.end_index()]
115    }
116
117    /// Returns the mutable slice covered by this range.
118    pub fn slice_mut(self, store: &mut [T]) -> &mut [T] {
119        &mut store[self.start_index()..self.end_index()]
120    }
121}
122
123impl<T> Clone for IdRange<T> {
124    fn clone(&self) -> Self {
125        *self
126    }
127}
128
129impl<T> Copy for IdRange<T> {}
130
131impl<T> Default for IdRange<T> {
132    fn default() -> Self {
133        Self::empty()
134    }
135}
136
137/// A simple append-only store for variable-length typed child lists.
138#[derive(Debug, Clone, Default)]
139pub struct ListArena<T> {
140    items: Vec<T>,
141}
142
143impl<T> ListArena<T> {
144    /// Creates an empty list arena.
145    pub const fn new() -> Self {
146        Self { items: Vec::new() }
147    }
148
149    /// Creates an empty list arena with capacity for `capacity` items.
150    pub fn with_capacity(capacity: usize) -> Self {
151        Self {
152            items: Vec::with_capacity(capacity),
153        }
154    }
155
156    /// Appends one item and returns its typed index.
157    pub fn push(&mut self, item: T) -> Idx<T> {
158        let id = Idx::new(self.items.len());
159        self.items.push(item);
160        self.check_len();
161        id
162    }
163
164    /// Appends a variable-length list and returns the typed range for it.
165    pub fn push_many<I>(&mut self, items: I) -> IdRange<T>
166    where
167        I: IntoIterator<Item = T>,
168    {
169        let start = self.items.len();
170        self.items.extend(items);
171        self.check_len();
172        IdRange::from_start_len(start, self.items.len() - start)
173    }
174
175    /// Returns all arena items as a slice.
176    pub fn as_slice(&self) -> &[T] {
177        &self.items
178    }
179
180    /// Returns all arena items as a mutable slice.
181    pub fn as_mut_slice(&mut self) -> &mut [T] {
182        &mut self.items
183    }
184
185    /// Returns the slice covered by `range`.
186    pub fn get(&self, range: IdRange<T>) -> &[T] {
187        range.slice(&self.items)
188    }
189
190    /// Returns the mutable slice covered by `range`.
191    pub fn get_mut(&mut self, range: IdRange<T>) -> &mut [T] {
192        range.slice_mut(&mut self.items)
193    }
194
195    /// Returns the number of items in the arena.
196    pub fn len(&self) -> usize {
197        self.items.len()
198    }
199
200    /// Returns `true` when the arena is empty.
201    pub fn is_empty(&self) -> bool {
202        self.items.is_empty()
203    }
204
205    /// Consumes the arena and returns the packed items.
206    pub fn into_vec(self) -> Vec<T> {
207        self.items
208    }
209
210    /// Consumes the arena and returns the packed items as a boxed slice.
211    pub fn into_boxed_slice(self) -> Box<[T]> {
212        self.items.into_boxed_slice()
213    }
214
215    fn check_len(&self) {
216        if self.items.len() > u32::MAX as usize {
217            panic!("arena length must fit in u32");
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::{IdRange, Idx, ListArena};
225
226    #[test]
227    fn typed_index_round_trips_raw_values() {
228        let id = Idx::<String>::new(42);
229        assert_eq!(id.index(), 42);
230        assert_eq!(id.raw(), 42);
231        assert_eq!(Idx::<String>::from_raw(7).index(), 7);
232    }
233
234    #[test]
235    #[should_panic(expected = "arena index must fit in u32")]
236    fn typed_index_panics_when_out_of_bounds() {
237        let _ = Idx::<()>::new(u32::MAX as usize + 1);
238    }
239
240    #[test]
241    fn typed_ranges_slice_storage() {
242        let values = [10, 20, 30, 40];
243        let range = IdRange::<i32>::from_start_len(1, 2);
244        assert_eq!(range.start().index(), 1);
245        assert_eq!(range.len(), 2);
246        assert_eq!(range.slice(&values), &[20, 30]);
247    }
248
249    #[test]
250    fn empty_ranges_are_zero_length() {
251        let range = IdRange::<i32>::empty();
252        let values = [10, 20];
253        assert!(range.is_empty());
254        assert_eq!(range.slice(&values), &[]);
255    }
256
257    #[test]
258    #[should_panic(expected = "arena range end must fit in u32")]
259    fn typed_range_panics_when_end_is_out_of_bounds() {
260        let _ = IdRange::<()>::from_start_len(u32::MAX as usize, 1);
261    }
262
263    #[test]
264    fn list_arena_packs_variable_length_lists() {
265        let mut arena = ListArena::new();
266        let first = arena.push_many([1, 2]);
267        let empty = arena.push_many([]);
268        let second = arena.push_many([3, 4, 5]);
269
270        assert_eq!(arena.get(first), &[1, 2]);
271        assert_eq!(arena.get(empty), &[]);
272        assert_eq!(arena.get(second), &[3, 4, 5]);
273        assert_eq!(arena.as_slice(), &[1, 2, 3, 4, 5]);
274    }
275
276    #[test]
277    fn list_arena_mutates_ranges_in_place() {
278        let mut arena = ListArena::new();
279        let range = arena.push_many([1, 2, 3]);
280        arena.get_mut(range)[1] = 9;
281        assert_eq!(arena.get(range), &[1, 9, 3]);
282    }
283}