1use crate::arena::{Arena, Idx};
4use crate::{BinderInfo, Expr, FVarId, Level, Literal, Name};
5use std::collections::HashMap;
6
7use super::key::ExprKey;
8use super::stats::HashConsStats;
9
10pub struct HashConsArena {
26 arena: Arena<Expr>,
28 cache: HashMap<ExprKey, Idx<Expr>>,
30 hits: u64,
32 misses: u64,
34}
35
36impl HashConsArena {
37 pub fn new() -> Self {
39 Self {
40 arena: Arena::new(),
41 cache: HashMap::new(),
42 hits: 0,
43 misses: 0,
44 }
45 }
46
47 pub fn with_capacity(cap: usize) -> Self {
49 Self {
50 arena: Arena::with_capacity(cap),
51 cache: HashMap::with_capacity(cap),
52 hits: 0,
53 misses: 0,
54 }
55 }
56
57 fn get_or_insert(&mut self, key: ExprKey, expr: Expr) -> Idx<Expr> {
67 if let Some(idx) = self.cache.get(&key) {
68 self.hits += 1;
69 return idx.clone();
70 }
71 self.misses += 1;
72 let idx = self.arena.alloc(expr);
73 self.cache.insert(key, idx.clone());
74 idx
75 }
76
77 pub fn get(&self, idx: Idx<Expr>) -> &Expr {
83 self.arena.get(idx)
84 }
85
86 pub fn len(&self) -> usize {
88 self.arena.len()
89 }
90
91 pub fn is_empty(&self) -> bool {
93 self.arena.is_empty()
94 }
95
96 pub fn stats(&self) -> HashConsStats {
98 HashConsStats {
99 hits: self.hits,
100 misses: self.misses,
101 total: self.hits + self.misses,
102 unique_nodes: self.arena.len(),
103 }
104 }
105
106 pub fn arena(&self) -> &Arena<Expr> {
108 &self.arena
109 }
110
111 pub fn mk_sort(&mut self, level: Level) -> Idx<Expr> {
117 let key = ExprKey::sort(level.clone());
118 let expr = Expr::Sort(level);
119 self.get_or_insert(key, expr)
120 }
121
122 pub fn mk_bvar(&mut self, idx: u32) -> Idx<Expr> {
124 let key = ExprKey::bvar(idx);
125 let expr = Expr::BVar(idx);
126 self.get_or_insert(key, expr)
127 }
128
129 pub fn mk_fvar(&mut self, id: FVarId) -> Idx<Expr> {
131 let key = ExprKey::fvar(id);
132 let expr = Expr::FVar(id);
133 self.get_or_insert(key, expr)
134 }
135
136 pub fn mk_const(&mut self, name: Name, levels: Vec<Level>) -> Idx<Expr> {
138 let key = ExprKey::const_(name.clone(), levels.clone());
139 let expr = Expr::Const(name, levels);
140 self.get_or_insert(key, expr)
141 }
142
143 pub fn mk_app(&mut self, f_expr: Expr, a_expr: Expr) -> Idx<Expr> {
148 let key = ExprKey::app(f_expr.clone(), a_expr.clone());
149 let expr = Expr::App(Box::new(f_expr), Box::new(a_expr));
150 self.get_or_insert(key, expr)
151 }
152
153 pub fn mk_lam(&mut self, bi: BinderInfo, name: Name, dom: Expr, body: Expr) -> Idx<Expr> {
155 let key = ExprKey::lam(bi, name.clone(), dom.clone(), body.clone());
156 let expr = Expr::Lam(bi, name, Box::new(dom), Box::new(body));
157 self.get_or_insert(key, expr)
158 }
159
160 pub fn mk_pi(&mut self, bi: BinderInfo, name: Name, dom: Expr, cod: Expr) -> Idx<Expr> {
162 let key = ExprKey::pi(bi, name.clone(), dom.clone(), cod.clone());
163 let expr = Expr::Pi(bi, name, Box::new(dom), Box::new(cod));
164 self.get_or_insert(key, expr)
165 }
166
167 pub fn mk_let(&mut self, name: Name, ty: Expr, val: Expr, body: Expr) -> Idx<Expr> {
169 let key = ExprKey::let_(name.clone(), ty.clone(), val.clone(), body.clone());
170 let expr = Expr::Let(name, Box::new(ty), Box::new(val), Box::new(body));
171 self.get_or_insert(key, expr)
172 }
173
174 pub fn mk_lit(&mut self, lit: Literal) -> Idx<Expr> {
176 let key = ExprKey::lit(lit.clone());
177 let expr = Expr::Lit(lit);
178 self.get_or_insert(key, expr)
179 }
180
181 pub fn mk_proj(&mut self, name: Name, field_idx: u32, struct_expr: Expr) -> Idx<Expr> {
183 let key = ExprKey::proj(name.clone(), field_idx, struct_expr.clone());
184 let expr = Expr::Proj(name, field_idx, Box::new(struct_expr));
185 self.get_or_insert(key, expr)
186 }
187}
188
189impl Default for HashConsArena {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::{Level, Name};
203
204 #[test]
207 fn test_mk_sort_deduplicates() {
208 let mut hc = HashConsArena::new();
209 let i1 = hc.mk_sort(Level::Zero);
210 let i2 = hc.mk_sort(Level::Zero);
211 assert_eq!(i1, i2);
212 assert_eq!(hc.len(), 1);
213 }
214
215 #[test]
216 fn test_mk_sort_distinct_levels() {
217 let mut hc = HashConsArena::new();
218 let i0 = hc.mk_sort(Level::Zero);
219 let i1 = hc.mk_sort(Level::succ(Level::Zero));
220 assert_ne!(i0, i1);
221 assert_eq!(hc.len(), 2);
222 }
223
224 #[test]
227 fn test_mk_bvar_deduplicates() {
228 let mut hc = HashConsArena::new();
229 let i1 = hc.mk_bvar(0);
230 let i2 = hc.mk_bvar(0);
231 assert_eq!(i1, i2);
232 }
233
234 #[test]
235 fn test_mk_bvar_distinct_indices() {
236 let mut hc = HashConsArena::new();
237 let i0 = hc.mk_bvar(0);
238 let i1 = hc.mk_bvar(1);
239 assert_ne!(i0, i1);
240 }
241
242 #[test]
245 fn test_mk_fvar_deduplicates() {
246 let mut hc = HashConsArena::new();
247 let id = FVarId::new(7);
248 let i1 = hc.mk_fvar(id);
249 let i2 = hc.mk_fvar(id);
250 assert_eq!(i1, i2);
251 }
252
253 #[test]
256 fn test_mk_const_deduplicates() {
257 let mut hc = HashConsArena::new();
258 let name = Name::str("Nat");
259 let i1 = hc.mk_const(name.clone(), vec![]);
260 let i2 = hc.mk_const(name, vec![]);
261 assert_eq!(i1, i2);
262 }
263
264 #[test]
265 fn test_mk_const_different_names_distinct() {
266 let mut hc = HashConsArena::new();
267 let i1 = hc.mk_const(Name::str("Nat"), vec![]);
268 let i2 = hc.mk_const(Name::str("Int"), vec![]);
269 assert_ne!(i1, i2);
270 }
271
272 #[test]
275 fn test_mk_app_deduplicates() {
276 let mut hc = HashConsArena::new();
277 let f = Expr::BVar(0);
278 let a = Expr::BVar(1);
279 let i1 = hc.mk_app(f.clone(), a.clone());
280 let i2 = hc.mk_app(f, a);
281 assert_eq!(i1, i2);
282 }
283
284 #[test]
285 fn test_mk_app_different_args_distinct() {
286 let mut hc = HashConsArena::new();
287 let i1 = hc.mk_app(Expr::BVar(0), Expr::BVar(1));
288 let i2 = hc.mk_app(Expr::BVar(0), Expr::BVar(2));
289 assert_ne!(i1, i2);
290 }
291
292 #[test]
295 fn test_mk_lam_deduplicates() {
296 let mut hc = HashConsArena::new();
297 let name = Name::str("x");
298 let dom = Expr::Sort(Level::Zero);
299 let body = Expr::BVar(0);
300 let i1 = hc.mk_lam(BinderInfo::Default, name.clone(), dom.clone(), body.clone());
301 let i2 = hc.mk_lam(BinderInfo::Default, name, dom, body);
302 assert_eq!(i1, i2);
303 }
304
305 #[test]
308 fn test_mk_pi_deduplicates() {
309 let mut hc = HashConsArena::new();
310 let name = Name::str("A");
311 let dom = Expr::Sort(Level::Zero);
312 let cod = Expr::BVar(0);
313 let i1 = hc.mk_pi(BinderInfo::Default, name.clone(), dom.clone(), cod.clone());
314 let i2 = hc.mk_pi(BinderInfo::Default, name, dom, cod);
315 assert_eq!(i1, i2);
316 }
317
318 #[test]
321 fn test_mk_let_deduplicates() {
322 let mut hc = HashConsArena::new();
323 let name = Name::str("n");
324 let ty = Expr::Sort(Level::Zero);
325 let val = Expr::BVar(0);
326 let body = Expr::BVar(1);
327 let i1 = hc.mk_let(name.clone(), ty.clone(), val.clone(), body.clone());
328 let i2 = hc.mk_let(name, ty, val, body);
329 assert_eq!(i1, i2);
330 }
331
332 #[test]
335 fn test_mk_lit_nat_deduplicates() {
336 let mut hc = HashConsArena::new();
337 let i1 = hc.mk_lit(Literal::Nat(42));
338 let i2 = hc.mk_lit(Literal::Nat(42));
339 assert_eq!(i1, i2);
340 }
341
342 #[test]
343 fn test_mk_lit_str_deduplicates() {
344 let mut hc = HashConsArena::new();
345 let i1 = hc.mk_lit(Literal::Str("hello".to_string()));
346 let i2 = hc.mk_lit(Literal::Str("hello".to_string()));
347 assert_eq!(i1, i2);
348 }
349
350 #[test]
351 fn test_mk_proj_deduplicates() {
352 let mut hc = HashConsArena::new();
353 let name = Name::str("fst");
354 let se = Expr::BVar(0);
355 let i1 = hc.mk_proj(name.clone(), 0, se.clone());
356 let i2 = hc.mk_proj(name, 0, se);
357 assert_eq!(i1, i2);
358 }
359
360 #[test]
363 fn test_stats_hit_miss_tracking() {
364 let mut hc = HashConsArena::new();
365 hc.mk_sort(Level::Zero); hc.mk_sort(Level::Zero); hc.mk_sort(Level::Zero); hc.mk_bvar(0); let s = hc.stats();
370 assert_eq!(s.misses, 2);
371 assert_eq!(s.hits, 2);
372 assert_eq!(s.total, 4);
373 assert_eq!(s.unique_nodes, 2);
374 }
375
376 #[test]
377 fn test_stats_dedup_ratio() {
378 let mut hc = HashConsArena::new();
379 hc.mk_bvar(0); hc.mk_bvar(0); hc.mk_bvar(0); hc.mk_bvar(0); let s = hc.stats();
384 assert!((s.dedup_ratio() - 0.25).abs() < 1e-10);
386 }
387
388 #[test]
389 fn test_get_returns_correct_expr() {
390 let mut hc = HashConsArena::new();
391 let idx = hc.mk_bvar(5);
392 assert_eq!(hc.get(idx), &Expr::BVar(5));
393 }
394
395 #[test]
396 fn test_is_empty_initially() {
397 let hc = HashConsArena::new();
398 assert!(hc.is_empty());
399 }
400
401 #[test]
402 fn test_with_capacity_works() {
403 let mut hc = HashConsArena::with_capacity(64);
404 hc.mk_sort(Level::Zero);
405 assert_eq!(hc.len(), 1);
406 }
407
408 #[test]
409 fn test_default_is_empty() {
410 let hc = HashConsArena::default();
411 assert!(hc.is_empty());
412 }
413
414 #[test]
415 fn test_cross_variant_no_collision() {
416 let mut hc = HashConsArena::new();
417 let bv = hc.mk_bvar(0);
419 let sv = hc.mk_sort(Level::Zero);
420 assert_ne!(bv, sv);
421 }
422
423 #[test]
424 fn test_binder_info_distinguishes_lam() {
425 let mut hc = HashConsArena::new();
426 let name = Name::str("x");
427 let dom = Expr::Sort(Level::Zero);
428 let body = Expr::BVar(0);
429 let explicit = hc.mk_lam(BinderInfo::Default, name.clone(), dom.clone(), body.clone());
430 let implicit = hc.mk_lam(BinderInfo::Implicit, name, dom, body);
431 assert_ne!(explicit, implicit);
432 }
433
434 #[test]
435 fn test_complex_expression_sharing() {
436 let mut hc = HashConsArena::new();
437 let prop = Expr::Sort(Level::Zero);
439 let bv0 = Expr::BVar(0);
440 let arr = Expr::Pi(
442 BinderInfo::Default,
443 Name::Anonymous,
444 Box::new(bv0.clone()),
445 Box::new(bv0.clone()),
446 );
447 let id_type_1 = hc.mk_pi(
448 BinderInfo::Default,
449 Name::str("A"),
450 prop.clone(),
451 arr.clone(),
452 );
453 let id_type_2 = hc.mk_pi(
454 BinderInfo::Default,
455 Name::str("A"),
456 prop.clone(),
457 arr.clone(),
458 );
459 assert_eq!(
460 id_type_1, id_type_2,
461 "identical Pi types must be deduplicated"
462 );
463 assert_eq!(hc.len(), 1, "only one node in the arena");
464 }
465}