logicaffeine_base/
arena.rs

1//! Arena allocation for stable AST references.
2//!
3//! This module provides bump allocation for AST nodes, ensuring references
4//! remain valid throughout parsing and semantic analysis. The arena pattern
5//! eliminates reference counting overhead and enables zero-copy parsing.
6//!
7//! ## Example
8//!
9//! ```
10//! use logicaffeine_base::Arena;
11//!
12//! let arena: Arena<String> = Arena::new();
13//! let s1 = arena.alloc("hello".to_string());
14//! let s2 = arena.alloc("world".to_string());
15//!
16//! // Both references remain valid as long as arena is alive
17//! assert_eq!(s1, "hello");
18//! assert_eq!(s2, "world");
19//! ```
20//!
21//! ## REPL Reuse
22//!
23//! For interactive use, call [`Arena::reset`] between evaluations to
24//! reclaim memory while keeping allocated capacity.
25
26use bumpalo::Bump;
27
28/// A bump allocator for stable, arena-allocated references.
29///
30/// Values allocated in an arena live until the arena is dropped or reset.
31/// References remain valid across subsequent allocations, making this ideal
32/// for AST nodes that reference each other.
33pub struct Arena<T> {
34    bump: Bump,
35    _marker: std::marker::PhantomData<T>,
36}
37
38impl<T> Arena<T> {
39    /// Creates an empty arena.
40    pub fn new() -> Self {
41        Arena {
42            bump: Bump::new(),
43            _marker: std::marker::PhantomData,
44        }
45    }
46
47    /// Allocates a value and returns a reference valid for the arena's lifetime.
48    pub fn alloc(&self, value: T) -> &T {
49        self.bump.alloc(value)
50    }
51
52    /// Allocates a slice from an iterator.
53    ///
54    /// The iterator must implement [`ExactSizeIterator`] so the arena can
55    /// pre-allocate the correct amount of space.
56    pub fn alloc_slice<I>(&self, items: I) -> &[T]
57    where
58        I: IntoIterator<Item = T>,
59        I::IntoIter: ExactSizeIterator,
60    {
61        self.bump.alloc_slice_fill_iter(items)
62    }
63
64    /// Resets the arena, invalidating all references but keeping allocated capacity.
65    ///
66    /// This enables zero-allocation REPL loops by reusing memory.
67    pub fn reset(&mut self) {
68        self.bump.reset();
69    }
70}
71
72impl<T> Default for Arena<T> {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn alloc_returns_stable_reference() {
84        let arena: Arena<i32> = Arena::new();
85        let r1 = arena.alloc(42);
86        let r2 = arena.alloc(100);
87        assert_eq!(*r1, 42);
88        assert_eq!(*r2, 100);
89    }
90
91    #[test]
92    fn references_remain_valid_after_many_allocations() {
93        let arena: Arena<i32> = Arena::new();
94        let refs: Vec<&i32> = (0..10000).map(|i| arena.alloc(i)).collect();
95        for (i, r) in refs.iter().enumerate() {
96            assert_eq!(**r, i as i32);
97        }
98    }
99
100    #[test]
101    fn works_with_structs() {
102        #[derive(Debug, PartialEq)]
103        struct Point {
104            x: i32,
105            y: i32,
106        }
107
108        let arena: Arena<Point> = Arena::new();
109        let p1 = arena.alloc(Point { x: 1, y: 2 });
110        let p2 = arena.alloc(Point { x: 3, y: 4 });
111        assert_eq!(p1, &Point { x: 1, y: 2 });
112        assert_eq!(p2, &Point { x: 3, y: 4 });
113    }
114
115    #[test]
116    fn alloc_slice_works() {
117        let arena: Arena<i32> = Arena::new();
118        let slice = arena.alloc_slice([1, 2, 3]);
119        assert_eq!(slice, &[1, 2, 3]);
120    }
121
122    #[test]
123    fn alloc_slice_from_vec() {
124        let arena: Arena<i32> = Arena::new();
125        let vec = vec![10, 20, 30];
126        let slice = arena.alloc_slice(vec);
127        assert_eq!(slice, &[10, 20, 30]);
128    }
129
130    #[test]
131    fn alloc_empty_slice() {
132        let arena: Arena<i32> = Arena::new();
133        let empty: Vec<i32> = vec![];
134        let slice = arena.alloc_slice(empty);
135        assert!(slice.is_empty());
136    }
137}