lean_agentic/
arena.rs

1//! Arena allocator for term hash-consing
2//!
3//! Provides fast allocation and deduplication of terms through
4//! hash-consing, making equality checks O(1).
5
6use crate::term::{Term, TermId, TermKind};
7use std::collections::HashMap;
8
9/// Arena for interning terms with hash-consing
10pub struct Arena {
11    /// Storage for all terms
12    terms: Vec<Term>,
13
14    /// Hash-cons cache for deduplication
15    cache: HashMap<u64, Vec<TermId>>,
16
17    /// Statistics
18    stats: ArenaStats,
19}
20
21/// Statistics for the arena
22#[derive(Debug, Default, Clone)]
23pub struct ArenaStats {
24    /// Total terms allocated
25    pub allocated: usize,
26
27    /// Cache hits (deduplicated terms)
28    pub cache_hits: usize,
29
30    /// Cache misses
31    pub cache_misses: usize,
32}
33
34impl Arena {
35    /// Create a new term arena
36    pub fn new() -> Self {
37        Self {
38            terms: Vec::new(),
39            cache: HashMap::new(),
40            stats: ArenaStats::default(),
41        }
42    }
43
44    /// Intern a term and return its ID
45    pub fn intern(&mut self, kind: TermKind) -> TermId {
46        let term = Term::new(kind);
47        let hash = term.hash();
48
49        // Check cache for existing term
50        if let Some(candidates) = self.cache.get(&hash) {
51            for &id in candidates {
52                if let Some(existing) = self.terms.get(id.raw() as usize) {
53                    if existing.kind == term.kind {
54                        self.stats.cache_hits += 1;
55                        return id;
56                    }
57                }
58            }
59        }
60
61        // Not found, allocate new term
62        self.stats.cache_misses += 1;
63        self.stats.allocated += 1;
64
65        let id = TermId::new(self.terms.len() as u32);
66        self.terms.push(term);
67
68        // Add to cache
69        self.cache.entry(hash).or_insert_with(Vec::new).push(id);
70
71        id
72    }
73
74    /// Get a term by its ID
75    pub fn get(&self, id: TermId) -> Option<&Term> {
76        self.terms.get(id.raw() as usize)
77    }
78
79    /// Get a term by its ID (alias for compatibility)
80    pub fn get_term(&self, id: TermId) -> Option<&Term> {
81        self.get(id)
82    }
83
84    /// Get the kind of a term by its ID
85    pub fn kind(&self, id: TermId) -> Option<&TermKind> {
86        self.get(id).map(|t| &t.kind)
87    }
88
89    /// Get the number of terms in the arena
90    pub fn terms(&self) -> usize {
91        self.terms.len()
92    }
93
94    /// Get arena statistics
95    pub fn stats(&self) -> &ArenaStats {
96        &self.stats
97    }
98
99    /// Get cache efficiency (hit rate)
100    pub fn cache_hit_rate(&self) -> f64 {
101        if self.stats.cache_hits + self.stats.cache_misses == 0 {
102            return 0.0;
103        }
104        self.stats.cache_hits as f64
105            / (self.stats.cache_hits + self.stats.cache_misses) as f64
106    }
107
108    /// Clear all statistics
109    pub fn clear_stats(&mut self) {
110        self.stats = ArenaStats::default();
111    }
112
113    // Helper methods for creating common terms
114
115    /// Create a sort term
116    pub fn mk_sort(&mut self, level: crate::level::LevelId) -> TermId {
117        self.intern(TermKind::Sort(level))
118    }
119
120    /// Create a constant term
121    pub fn mk_const(
122        &mut self,
123        name: crate::symbol::SymbolId,
124        levels: Vec<crate::level::LevelId>,
125    ) -> TermId {
126        self.intern(TermKind::Const(name, levels))
127    }
128
129    /// Create a variable term
130    pub fn mk_var(&mut self, index: u32) -> TermId {
131        self.intern(TermKind::Var(index))
132    }
133
134    /// Create an application term
135    pub fn mk_app(&mut self, func: TermId, arg: TermId) -> TermId {
136        self.intern(TermKind::App(func, arg))
137    }
138
139    /// Create a lambda term
140    pub fn mk_lam(&mut self, binder: crate::term::Binder, body: TermId) -> TermId {
141        self.intern(TermKind::Lam(binder, body))
142    }
143
144    /// Create a Pi term
145    pub fn mk_pi(&mut self, binder: crate::term::Binder, body: TermId) -> TermId {
146        self.intern(TermKind::Pi(binder, body))
147    }
148
149    /// Create a let term
150    pub fn mk_let(
151        &mut self,
152        binder: crate::term::Binder,
153        value: TermId,
154        body: TermId,
155    ) -> TermId {
156        self.intern(TermKind::Let(binder, value, body))
157    }
158
159    /// Create a metavariable term
160    pub fn mk_mvar(&mut self, id: crate::term::MetaVarId) -> TermId {
161        self.intern(TermKind::MVar(id))
162    }
163
164    /// Create a natural number literal
165    pub fn mk_nat(&mut self, n: u64) -> TermId {
166        self.intern(TermKind::Lit(crate::term::Literal::Nat(n)))
167    }
168
169    /// Create a spine of applications (f x y z)
170    pub fn mk_app_spine(&mut self, func: TermId, args: &[TermId]) -> TermId {
171        args.iter().fold(func, |acc, &arg| self.mk_app(acc, arg))
172    }
173
174    /// Create a zero universe level
175    pub fn mk_level_zero(&mut self) -> crate::level::LevelId {
176        // This is a placeholder - in production this would use LevelArena
177        crate::level::LevelId::new(0)
178    }
179}
180
181impl Default for Arena {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::level::LevelId;
191
192    #[test]
193    fn test_hash_consing() {
194        let mut arena = Arena::new();
195
196        let var0_1 = arena.mk_var(0);
197        let var0_2 = arena.mk_var(0);
198
199        // Should be the same due to hash-consing
200        assert_eq!(var0_1, var0_2);
201        assert_eq!(arena.terms(), 1);
202        assert!(arena.stats().cache_hits > 0);
203    }
204
205    #[test]
206    fn test_different_terms() {
207        let mut arena = Arena::new();
208
209        let var0 = arena.mk_var(0);
210        let var1 = arena.mk_var(1);
211
212        assert_ne!(var0, var1);
213        assert_eq!(arena.terms(), 2);
214    }
215
216    #[test]
217    fn test_app_spine() {
218        let mut arena = Arena::new();
219
220        let f = arena.mk_var(0);
221        let x = arena.mk_var(1);
222        let y = arena.mk_var(2);
223
224        let app = arena.mk_app_spine(f, &[x, y]);
225
226        // Should create nested applications
227        if let Some(TermKind::App(left, _)) = arena.kind(app) {
228            if let Some(TermKind::App(_, _)) = arena.kind(*left) {
229                // Correct structure: ((f x) y)
230            } else {
231                panic!("Expected nested application");
232            }
233        } else {
234            panic!("Expected application");
235        }
236    }
237
238    #[test]
239    fn test_cache_efficiency() {
240        let mut arena = Arena::new();
241
242        // Create many duplicate terms
243        for _ in 0..100 {
244            arena.mk_var(0);
245        }
246
247        // Should have high cache hit rate
248        assert!(arena.cache_hit_rate() > 0.95);
249        assert_eq!(arena.terms(), 1);
250    }
251}