1use crate::{BinderInfo, Expr, Name};
6
7use super::types::{
8 ConfigNode, EtaChecker, EtaJob, EtaJobQueue, EtaLog, EtaNormInfo, EtaNormalCache, EtaOpCounter,
9 EtaOutcome, EtaReductionStats, EtaStatCounter, EtaStructure, FocusStack, LabelSet, LambdaStats,
10 NonEmptyVec, RewriteRule, SimpleDag, SmallMap, StatSummary, TransformStat, VersionedRecord,
11 WindowIterator,
12};
13
14pub fn eta_expand(expr: &Expr, arg_name: Name, arg_ty: Expr) -> Expr {
16 Expr::Lam(
17 BinderInfo::Default,
18 arg_name,
19 Box::new(arg_ty),
20 Box::new(Expr::App(Box::new(expr.clone()), Box::new(Expr::BVar(0)))),
21 )
22}
23pub fn eta_expand_implicit(expr: &Expr, arg_name: Name, arg_ty: Expr) -> Expr {
25 Expr::Lam(
26 BinderInfo::Implicit,
27 arg_name,
28 Box::new(arg_ty),
29 Box::new(Expr::App(Box::new(expr.clone()), Box::new(Expr::BVar(0)))),
30 )
31}
32pub fn is_eta_expandable(expr: &Expr) -> bool {
34 !matches!(expr, Expr::Lam(..))
35}
36pub fn eta_contract(expr: &Expr) -> Option<Expr> {
40 if let Expr::Lam(_, _, _, body) = expr {
41 if let Expr::App(f, a) = &**body {
42 if let Expr::BVar(0) = **a {
43 if !contains_bvar(f, 0) {
44 return Some(shift_down(f, 1));
45 }
46 }
47 }
48 }
49 None
50}
51pub fn eta_contract_full(expr: &Expr) -> Expr {
53 if let Some(c) = eta_contract(expr) {
54 return eta_contract_full(&c);
55 }
56 match expr {
57 Expr::App(func, arg) => Expr::App(
58 Box::new(eta_contract_full(func)),
59 Box::new(eta_contract_full(arg)),
60 ),
61 Expr::Lam(i, n, ty, body) => Expr::Lam(
62 *i,
63 n.clone(),
64 Box::new(eta_contract_full(ty)),
65 Box::new(eta_contract_full(body)),
66 ),
67 Expr::Pi(i, n, ty, body) => Expr::Pi(
68 *i,
69 n.clone(),
70 Box::new(eta_contract_full(ty)),
71 Box::new(eta_contract_full(body)),
72 ),
73 Expr::Let(n, ty, val, body) => Expr::Let(
74 n.clone(),
75 Box::new(eta_contract_full(ty)),
76 Box::new(eta_contract_full(val)),
77 Box::new(eta_contract_full(body)),
78 ),
79 _ => expr.clone(),
80 }
81}
82pub fn eta_equiv(e1: &Expr, e2: &Expr) -> bool {
84 eta_normalize(e1) == eta_normalize(e2)
85}
86pub fn eta_normalize(expr: &Expr) -> Expr {
88 let inner = eta_normalize_inner(expr);
89 match eta_contract(&inner) {
90 Some(c) => eta_normalize(&c),
91 None => inner,
92 }
93}
94fn eta_normalize_inner(expr: &Expr) -> Expr {
95 match expr {
96 Expr::App(f, a) => Expr::App(Box::new(eta_normalize(f)), Box::new(eta_normalize(a))),
97 Expr::Lam(i, n, ty, body) => Expr::Lam(
98 *i,
99 n.clone(),
100 Box::new(eta_normalize(ty)),
101 Box::new(eta_normalize(body)),
102 ),
103 Expr::Pi(i, n, ty, body) => Expr::Pi(
104 *i,
105 n.clone(),
106 Box::new(eta_normalize(ty)),
107 Box::new(eta_normalize(body)),
108 ),
109 Expr::Let(n, ty, val, body) => Expr::Let(
110 n.clone(),
111 Box::new(eta_normalize(ty)),
112 Box::new(eta_normalize(val)),
113 Box::new(eta_normalize(body)),
114 ),
115 _ => expr.clone(),
116 }
117}
118pub fn contains_bvar(expr: &Expr, idx: u32) -> bool {
120 match expr {
121 Expr::BVar(i) => *i == idx,
122 Expr::App(f, a) => contains_bvar(f, idx) || contains_bvar(a, idx),
123 Expr::Lam(_, _, ty, body) => contains_bvar(ty, idx) || contains_bvar(body, idx + 1),
124 Expr::Pi(_, _, ty, body) => contains_bvar(ty, idx) || contains_bvar(body, idx + 1),
125 Expr::Let(_, ty, val, body) => {
126 contains_bvar(ty, idx) || contains_bvar(val, idx) || contains_bvar(body, idx + 1)
127 }
128 _ => false,
129 }
130}
131pub fn count_bvar(expr: &Expr, idx: u32) -> usize {
133 match expr {
134 Expr::BVar(i) => {
135 if *i == idx {
136 1
137 } else {
138 0
139 }
140 }
141 Expr::App(f, a) => count_bvar(f, idx) + count_bvar(a, idx),
142 Expr::Lam(_, _, ty, body) => count_bvar(ty, idx) + count_bvar(body, idx + 1),
143 Expr::Pi(_, _, ty, body) => count_bvar(ty, idx) + count_bvar(body, idx + 1),
144 Expr::Let(_, ty, val, body) => {
145 count_bvar(ty, idx) + count_bvar(val, idx) + count_bvar(body, idx + 1)
146 }
147 _ => 0,
148 }
149}
150pub fn shift_down(expr: &Expr, amount: u32) -> Expr {
152 shift_helper(expr, amount, 0)
153}
154pub fn shift_up(expr: &Expr, amount: u32) -> Expr {
156 shift_up_helper(expr, amount, 0)
157}
158fn shift_helper(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
159 match expr {
160 Expr::BVar(i) => {
161 if *i >= cutoff {
162 Expr::BVar(i.saturating_sub(amount))
163 } else {
164 Expr::BVar(*i)
165 }
166 }
167 Expr::App(f, a) => Expr::App(
168 Box::new(shift_helper(f, amount, cutoff)),
169 Box::new(shift_helper(a, amount, cutoff)),
170 ),
171 Expr::Lam(i, n, ty, body) => Expr::Lam(
172 *i,
173 n.clone(),
174 Box::new(shift_helper(ty, amount, cutoff)),
175 Box::new(shift_helper(body, amount, cutoff + 1)),
176 ),
177 Expr::Pi(i, n, ty, body) => Expr::Pi(
178 *i,
179 n.clone(),
180 Box::new(shift_helper(ty, amount, cutoff)),
181 Box::new(shift_helper(body, amount, cutoff + 1)),
182 ),
183 Expr::Let(n, ty, val, body) => Expr::Let(
184 n.clone(),
185 Box::new(shift_helper(ty, amount, cutoff)),
186 Box::new(shift_helper(val, amount, cutoff)),
187 Box::new(shift_helper(body, amount, cutoff + 1)),
188 ),
189 _ => expr.clone(),
190 }
191}
192fn shift_up_helper(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
193 match expr {
194 Expr::BVar(i) => {
195 if *i >= cutoff {
196 Expr::BVar(*i + amount)
197 } else {
198 Expr::BVar(*i)
199 }
200 }
201 Expr::App(f, a) => Expr::App(
202 Box::new(shift_up_helper(f, amount, cutoff)),
203 Box::new(shift_up_helper(a, amount, cutoff)),
204 ),
205 Expr::Lam(i, n, ty, body) => Expr::Lam(
206 *i,
207 n.clone(),
208 Box::new(shift_up_helper(ty, amount, cutoff)),
209 Box::new(shift_up_helper(body, amount, cutoff + 1)),
210 ),
211 Expr::Pi(i, n, ty, body) => Expr::Pi(
212 *i,
213 n.clone(),
214 Box::new(shift_up_helper(ty, amount, cutoff)),
215 Box::new(shift_up_helper(body, amount, cutoff + 1)),
216 ),
217 Expr::Let(n, ty, val, body) => Expr::Let(
218 n.clone(),
219 Box::new(shift_up_helper(ty, amount, cutoff)),
220 Box::new(shift_up_helper(val, amount, cutoff)),
221 Box::new(shift_up_helper(body, amount, cutoff + 1)),
222 ),
223 _ => expr.clone(),
224 }
225}
226pub fn eta_expand_pi(expr: &Expr, binder_info: BinderInfo, arg_name: Name, arg_ty: Expr) -> Expr {
228 Expr::Lam(
229 binder_info,
230 arg_name,
231 Box::new(arg_ty),
232 Box::new(Expr::App(
233 Box::new(shift_up(expr, 1)),
234 Box::new(Expr::BVar(0)),
235 )),
236 )
237}
238pub fn is_eta_long(expr: &Expr) -> bool {
240 match expr {
241 Expr::Lam(_, _, _, body) => is_eta_long(body),
242 Expr::App(f, a) => is_eta_long(f) && is_eta_long(a),
243 Expr::Pi(_, _, ty, body) => is_eta_long(ty) && is_eta_long(body),
244 Expr::Let(_, ty, val, body) => is_eta_long(ty) && is_eta_long(val) && is_eta_long(body),
245 _ => true,
246 }
247}
248pub fn binder_depth(expr: &Expr) -> usize {
250 match expr {
251 Expr::Lam(_, _, _, body) => 1 + binder_depth(body),
252 _ => 0,
253 }
254}
255pub fn peel_lambdas(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
257 let mut binders = Vec::new();
258 let mut current = expr;
259 while let Expr::Lam(info, name, ty, body) = current {
260 binders.push((*info, name.clone(), *ty.clone()));
261 current = body;
262 }
263 (binders, current)
264}
265pub fn subst_bvar(expr: &Expr, depth: u32, replacement: &Expr) -> Expr {
267 match expr {
268 Expr::BVar(i) => {
269 if *i == depth {
270 replacement.clone()
271 } else if *i > depth {
272 Expr::BVar(*i - 1)
273 } else {
274 Expr::BVar(*i)
275 }
276 }
277 Expr::App(f, a) => Expr::App(
278 Box::new(subst_bvar(f, depth, replacement)),
279 Box::new(subst_bvar(a, depth, replacement)),
280 ),
281 Expr::Lam(i, n, ty, body) => Expr::Lam(
282 *i,
283 n.clone(),
284 Box::new(subst_bvar(ty, depth, replacement)),
285 Box::new(subst_bvar(body, depth + 1, replacement)),
286 ),
287 Expr::Pi(i, n, ty, body) => Expr::Pi(
288 *i,
289 n.clone(),
290 Box::new(subst_bvar(ty, depth, replacement)),
291 Box::new(subst_bvar(body, depth + 1, replacement)),
292 ),
293 Expr::Let(n, ty, val, body) => Expr::Let(
294 n.clone(),
295 Box::new(subst_bvar(ty, depth, replacement)),
296 Box::new(subst_bvar(val, depth, replacement)),
297 Box::new(subst_bvar(body, depth + 1, replacement)),
298 ),
299 _ => expr.clone(),
300 }
301}
302pub fn is_identity_lambda(expr: &Expr) -> bool {
304 if let Expr::Lam(_, _, _, body) = expr {
305 matches!(body.as_ref(), Expr::BVar(0))
306 } else {
307 false
308 }
309}
310pub fn app_depth(expr: &Expr) -> usize {
312 match expr {
313 Expr::App(f, _) => 1 + app_depth(f),
314 _ => 0,
315 }
316}
317pub fn collect_app_spine(expr: &Expr) -> (&Expr, Vec<&Expr>) {
319 let mut args = Vec::new();
320 let mut current = expr;
321 while let Expr::App(f, a) = current {
322 args.push(a.as_ref());
323 current = f;
324 }
325 args.reverse();
326 (current, args)
327}
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::Level;
332 fn sort() -> Expr {
333 Expr::Sort(Level::zero())
334 }
335 fn mk_const(s: &str) -> Expr {
336 Expr::Const(Name::str(s), vec![])
337 }
338 fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
339 Expr::Lam(
340 BinderInfo::Default,
341 Name::str(name),
342 Box::new(ty),
343 Box::new(body),
344 )
345 }
346 fn mk_app(f: Expr, a: Expr) -> Expr {
347 Expr::App(Box::new(f), Box::new(a))
348 }
349 #[test]
350 fn test_eta_expand() {
351 let f = mk_const("f");
352 let expanded = eta_expand(&f, Name::str("x"), sort());
353 if let Expr::Lam(_, name, _, body) = expanded {
354 assert_eq!(name, Name::str("x"));
355 if let Expr::App(fn_expr, arg_expr) = *body {
356 assert_eq!(*fn_expr, f);
357 assert_eq!(*arg_expr, Expr::BVar(0));
358 } else {
359 panic!("Expected App in body");
360 }
361 } else {
362 panic!("Expected Lam");
363 }
364 }
365 #[test]
366 fn test_is_eta_expandable() {
367 assert!(is_eta_expandable(&mk_const("f")));
368 assert!(!is_eta_expandable(&mk_lam("x", sort(), Expr::BVar(0))));
369 }
370 #[test]
371 fn test_eta_contract() {
372 let f = mk_const("f");
373 let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
374 let contracted = eta_contract(&lam);
375 assert!(contracted.is_some());
376 assert_eq!(contracted.expect("contracted should be valid"), f);
377 }
378 #[test]
379 fn test_eta_contract_none_identity() {
380 let lam = mk_lam("x", sort(), Expr::BVar(0));
381 assert!(eta_contract(&lam).is_none());
382 }
383 #[test]
384 fn test_eta_contract_full_recurses_into_app() {
385 let f = mk_const("f");
386 let g = mk_const("g");
387 let inner_lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
388 let expr = mk_app(inner_lam, g.clone());
389 let contracted = eta_contract_full(&expr);
390 assert_eq!(contracted, mk_app(f, g));
391 }
392 #[test]
393 fn test_eta_contract_full_recurses_into_lam_body() {
394 let f = mk_const("f");
395 let inner = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
396 let outer = mk_lam("y", sort(), inner);
397 let contracted = eta_contract_full(&outer);
398 assert_eq!(contracted, mk_lam("y", sort(), f));
399 }
400 #[test]
401 fn test_contains_bvar() {
402 let expr = mk_app(Expr::BVar(2), Expr::BVar(0));
403 assert!(contains_bvar(&expr, 2));
404 assert!(contains_bvar(&expr, 0));
405 assert!(!contains_bvar(&expr, 1));
406 }
407 #[test]
408 fn test_count_bvar() {
409 let expr = mk_app(Expr::BVar(0), Expr::BVar(0));
410 assert_eq!(count_bvar(&expr, 0), 2);
411 assert_eq!(count_bvar(&expr, 1), 0);
412 }
413 #[test]
414 fn test_shift_down() {
415 assert_eq!(shift_down(&Expr::BVar(3), 1), Expr::BVar(2));
416 }
417 #[test]
418 fn test_shift_up() {
419 assert_eq!(shift_up(&Expr::BVar(1), 2), Expr::BVar(3));
420 }
421 #[test]
422 fn test_binder_depth() {
423 let lam = mk_lam("x", sort(), mk_lam("y", sort(), Expr::BVar(0)));
424 assert_eq!(binder_depth(&lam), 2);
425 assert_eq!(binder_depth(&mk_const("f")), 0);
426 }
427 #[test]
428 fn test_peel_lambdas() {
429 let inner = mk_const("body");
430 let lam = mk_lam("x", sort(), mk_lam("y", sort(), inner.clone()));
431 let (binders, body) = peel_lambdas(&lam);
432 assert_eq!(binders.len(), 2);
433 assert_eq!(*body, inner);
434 }
435 #[test]
436 fn test_eta_normalize_idempotent() {
437 let f = mk_const("f");
438 let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
439 let normalized = eta_normalize(&lam);
440 assert_eq!(normalized, f);
441 assert_eq!(eta_normalize(&normalized), normalized);
442 }
443 #[test]
444 fn test_eta_expand_pi() {
445 let f = mk_const("f");
446 let expanded = eta_expand_pi(&f, BinderInfo::Default, Name::str("x"), sort());
447 assert!(matches!(expanded, Expr::Lam(BinderInfo::Default, _, _, _)));
448 }
449 #[test]
450 fn test_is_identity_lambda() {
451 assert!(is_identity_lambda(&mk_lam("x", sort(), Expr::BVar(0))));
452 assert!(!is_identity_lambda(&mk_lam("x", sort(), mk_const("y"))));
453 }
454 #[test]
455 fn test_collect_app_spine() {
456 let f = mk_const("f");
457 let a = mk_const("a");
458 let b = mk_const("b");
459 let expr = mk_app(mk_app(f.clone(), a.clone()), b.clone());
460 let (head, args) = collect_app_spine(&expr);
461 assert_eq!(*head, f);
462 assert_eq!(args.len(), 2);
463 }
464}
465pub fn eta_equiv_depth(e1: &Expr, e2: &Expr, max_depth: u32) -> bool {
467 if max_depth == 0 {
468 return e1 == e2;
469 }
470 let n1 = eta_normalize(e1);
471 let n2 = eta_normalize(e2);
472 n1 == n2
473}
474pub fn eta_expand_to(expr: &Expr, n: usize) -> Expr {
478 let current = binder_depth(expr);
479 if current >= n {
480 return expr.clone();
481 }
482 let extra = n - current;
483 let mut result = expr.clone();
484 for i in 0..extra {
485 let arg_name = Name::str(format!("_x{}", i));
486 result = eta_expand_pi(&result, BinderInfo::Default, arg_name, Expr::BVar(0));
487 }
488 result
489}
490pub fn mk_app_spine(head: &Expr, args: &[Expr]) -> Expr {
492 args.iter().fold(head.clone(), |acc, arg| {
493 Expr::App(Box::new(acc), Box::new(arg.clone()))
494 })
495}
496pub fn rebuild_lam(binders: &[(BinderInfo, Name, Expr)], body: Expr) -> Expr {
498 binders.iter().rev().fold(body, |acc, (info, name, ty)| {
499 Expr::Lam(*info, name.clone(), Box::new(ty.clone()), Box::new(acc))
500 })
501}
502pub fn rebuild_pi(binders: &[(BinderInfo, Name, Expr)], body: Expr) -> Expr {
504 binders.iter().rev().fold(body, |acc, (info, name, ty)| {
505 Expr::Pi(*info, name.clone(), Box::new(ty.clone()), Box::new(acc))
506 })
507}
508pub fn peel_pis(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
510 let mut binders = Vec::new();
511 let mut current = expr;
512 while let Expr::Pi(info, name, ty, body) = current {
513 binders.push((*info, name.clone(), *ty.clone()));
514 current = body;
515 }
516 (binders, current)
517}
518pub fn pi_depth(expr: &Expr) -> usize {
520 match expr {
521 Expr::Pi(_, _, _, body) => 1 + pi_depth(body),
522 _ => 0,
523 }
524}
525pub fn arity(expr: &Expr) -> usize {
527 pi_depth(expr)
528}
529pub fn is_simple_app(expr: &Expr) -> bool {
531 let (head, _) = collect_app_spine(expr);
532 matches!(head, Expr::Const(..))
533}
534pub fn is_closed_at(expr: &Expr, cutoff: u32) -> bool {
536 !contains_bvar(expr, cutoff)
537}
538pub fn is_closed(expr: &Expr) -> bool {
540 match expr {
541 Expr::BVar(_) => false,
542 Expr::FVar(_) => false,
543 Expr::App(f, a) => is_closed(f) && is_closed(a),
544 Expr::Lam(_, _, ty, body) => is_closed(ty) && is_closed(body),
545 Expr::Pi(_, _, ty, body) => is_closed(ty) && is_closed(body),
546 Expr::Let(_, ty, val, body) => is_closed(ty) && is_closed(val) && is_closed(body),
547 Expr::Proj(_, _, inner) => is_closed(inner),
548 _ => true,
549 }
550}
551pub fn lam_to_pi(expr: &Expr) -> Expr {
553 match expr {
554 Expr::Lam(info, name, ty, body) => Expr::Pi(
555 *info,
556 name.clone(),
557 Box::new(lam_to_pi(ty)),
558 Box::new(lam_to_pi(body)),
559 ),
560 Expr::App(f, a) => Expr::App(Box::new(lam_to_pi(f)), Box::new(lam_to_pi(a))),
561 _ => expr.clone(),
562 }
563}
564pub fn beta_reduce_one(lam: &Expr, arg: &Expr) -> Option<Expr> {
566 if let Expr::Lam(_, _, _, body) = lam {
567 Some(subst_bvar(body, 0, arg))
568 } else {
569 None
570 }
571}
572pub fn beta_reduce_full(expr: &Expr) -> Expr {
574 match expr {
575 Expr::App(f, a) => {
576 let f2 = beta_reduce_full(f);
577 let a2 = beta_reduce_full(a);
578 match beta_reduce_one(&f2, &a2) {
579 Some(reduced) => beta_reduce_full(&reduced),
580 None => Expr::App(Box::new(f2), Box::new(a2)),
581 }
582 }
583 Expr::Lam(i, n, ty, body) => Expr::Lam(
584 *i,
585 n.clone(),
586 Box::new(beta_reduce_full(ty)),
587 Box::new(beta_reduce_full(body)),
588 ),
589 Expr::Pi(i, n, ty, body) => Expr::Pi(
590 *i,
591 n.clone(),
592 Box::new(beta_reduce_full(ty)),
593 Box::new(beta_reduce_full(body)),
594 ),
595 Expr::Let(_n, _ty, val, body) => {
596 let val2 = beta_reduce_full(val);
597 let body2 = subst_bvar(body, 0, &val2);
598 beta_reduce_full(&body2)
599 }
600 _ => expr.clone(),
601 }
602}
603pub fn normalize_full(expr: &Expr) -> Expr {
605 let beta = beta_reduce_full(expr);
606 eta_normalize(&beta)
607}
608pub fn subst_bvar_shifted(expr: &Expr, depth: u32, replacement: &Expr, shift: u32) -> Expr {
610 let shifted = shift_up(replacement, shift);
611 subst_bvar(expr, depth, &shifted)
612}
613pub fn normal_eq(e1: &Expr, e2: &Expr) -> bool {
615 normalize_full(e1) == normalize_full(e2)
616}
617#[cfg(test)]
618mod eta_extended_tests {
619 use super::*;
620 use crate::Level;
621 fn sort() -> Expr {
622 Expr::Sort(Level::zero())
623 }
624 fn mk_const(s: &str) -> Expr {
625 Expr::Const(Name::str(s), vec![])
626 }
627 fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
628 Expr::Lam(
629 BinderInfo::Default,
630 Name::str(name),
631 Box::new(ty),
632 Box::new(body),
633 )
634 }
635 fn mk_app(f: Expr, a: Expr) -> Expr {
636 Expr::App(Box::new(f), Box::new(a))
637 }
638 fn mk_pi(name: &str, ty: Expr, body: Expr) -> Expr {
639 Expr::Pi(
640 BinderInfo::Default,
641 Name::str(name),
642 Box::new(ty),
643 Box::new(body),
644 )
645 }
646 #[test]
647 fn test_lambda_stats() {
648 let expr = mk_lam(
649 "x",
650 sort(),
651 mk_lam("y", sort(), mk_app(Expr::BVar(1), Expr::BVar(0))),
652 );
653 let stats = LambdaStats::compute(&expr);
654 assert_eq!(stats.lambda_count, 2);
655 assert_eq!(stats.app_count, 1);
656 }
657 #[test]
658 fn test_peel_pis() {
659 let body = mk_const("Nat");
660 let pi_expr = mk_pi("x", sort(), mk_pi("y", sort(), body.clone()));
661 let (binders, ret) = peel_pis(&pi_expr);
662 assert_eq!(binders.len(), 2);
663 assert_eq!(*ret, body);
664 }
665 #[test]
666 fn test_pi_depth() {
667 let expr = mk_pi("x", sort(), mk_pi("y", sort(), mk_const("Nat")));
668 assert_eq!(pi_depth(&expr), 2);
669 assert_eq!(pi_depth(&mk_const("Nat")), 0);
670 }
671 #[test]
672 fn test_rebuild_lam() {
673 let binders = vec![(BinderInfo::Default, Name::str("x"), sort())];
674 let body = Expr::BVar(0);
675 let result = rebuild_lam(&binders, body);
676 assert!(matches!(result, Expr::Lam(..)));
677 }
678 #[test]
679 fn test_rebuild_pi() {
680 let binders = vec![(BinderInfo::Default, Name::str("x"), sort())];
681 let body = Expr::BVar(0);
682 let result = rebuild_pi(&binders, body);
683 assert!(matches!(result, Expr::Pi(..)));
684 }
685 #[test]
686 fn test_mk_app_spine() {
687 let f = mk_const("f");
688 let args = vec![mk_const("a"), mk_const("b")];
689 let result = mk_app_spine(&f, &args);
690 let (head, spine) = collect_app_spine(&result);
691 assert_eq!(*head, f);
692 assert_eq!(spine.len(), 2);
693 }
694 #[test]
695 fn test_is_closed() {
696 assert!(is_closed(&mk_const("f")));
697 assert!(!is_closed(&Expr::BVar(0)));
698 assert!(!is_closed(&mk_lam("x", sort(), Expr::BVar(0))));
699 assert!(is_closed(&mk_lam("x", sort(), mk_const("a"))));
700 }
701 #[test]
702 fn test_beta_reduce_one() {
703 let lam = mk_lam("x", sort(), Expr::BVar(0));
704 let arg = mk_const("a");
705 let result = beta_reduce_one(&lam, &arg);
706 assert_eq!(result, Some(arg));
707 }
708 #[test]
709 fn test_beta_reduce_full() {
710 let lam = mk_lam("x", sort(), Expr::BVar(0));
711 let arg = mk_const("a");
712 let app = mk_app(lam, arg.clone());
713 assert_eq!(beta_reduce_full(&app), arg);
714 }
715 #[test]
716 fn test_normal_eq() {
717 let f = mk_const("f");
718 let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
719 assert!(normal_eq(&lam, &f));
720 }
721 #[test]
722 fn test_is_simple_app() {
723 let expr = mk_app(mk_const("f"), mk_const("a"));
724 assert!(is_simple_app(&expr));
725 let lam_app = mk_app(mk_lam("x", sort(), Expr::BVar(0)), mk_const("a"));
726 assert!(!is_simple_app(&lam_app));
727 }
728 #[test]
729 fn test_lam_to_pi() {
730 let lam = mk_lam("x", sort(), Expr::BVar(0));
731 let pi = lam_to_pi(&lam);
732 assert!(matches!(pi, Expr::Pi(..)));
733 }
734 #[test]
735 fn test_arity() {
736 let ty = mk_pi("a", sort(), mk_pi("b", sort(), mk_const("Nat")));
737 assert_eq!(arity(&ty), 2);
738 }
739}
740pub fn rewrite_with_rules(expr: &Expr, rules: &[RewriteRule]) -> Expr {
742 let inner = match expr {
743 Expr::App(f, a) => Expr::App(
744 Box::new(rewrite_with_rules(f, rules)),
745 Box::new(rewrite_with_rules(a, rules)),
746 ),
747 Expr::Lam(i, n, ty, body) => Expr::Lam(
748 *i,
749 n.clone(),
750 Box::new(rewrite_with_rules(ty, rules)),
751 Box::new(rewrite_with_rules(body, rules)),
752 ),
753 Expr::Pi(i, n, ty, body) => Expr::Pi(
754 *i,
755 n.clone(),
756 Box::new(rewrite_with_rules(ty, rules)),
757 Box::new(rewrite_with_rules(body, rules)),
758 ),
759 Expr::Let(n, ty, val, body) => Expr::Let(
760 n.clone(),
761 Box::new(rewrite_with_rules(ty, rules)),
762 Box::new(rewrite_with_rules(val, rules)),
763 Box::new(rewrite_with_rules(body, rules)),
764 ),
765 _ => expr.clone(),
766 };
767 for rule in rules {
768 if let Some(rewritten) = rule.apply_top(&inner) {
769 return rewritten;
770 }
771 }
772 inner
773}
774pub fn is_beta_redex(expr: &Expr) -> bool {
776 matches!(expr, Expr::App(f, _) if matches!(f.as_ref(), Expr::Lam(..)))
777}
778pub fn is_eta_redex(expr: &Expr) -> bool {
780 eta_contract(expr).is_some()
781}
782pub fn node_count(expr: &Expr) -> usize {
784 match expr {
785 Expr::App(f, a) => 1 + node_count(f) + node_count(a),
786 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
787 1 + node_count(ty) + node_count(body)
788 }
789 Expr::Let(_, ty, val, body) => 1 + node_count(ty) + node_count(val) + node_count(body),
790 _ => 1,
791 }
792}
793pub fn is_lambda_free(expr: &Expr) -> bool {
795 match expr {
796 Expr::Lam(..) => false,
797 Expr::App(f, a) => is_lambda_free(f) && is_lambda_free(a),
798 Expr::Pi(_, _, ty, body) => is_lambda_free(ty) && is_lambda_free(body),
799 Expr::Let(_, ty, val, body) => {
800 is_lambda_free(ty) && is_lambda_free(val) && is_lambda_free(body)
801 }
802 _ => true,
803 }
804}
805pub fn structural_hash(expr: &Expr) -> u64 {
807 use std::collections::hash_map::DefaultHasher;
808 use std::hash::{Hash, Hasher};
809 let mut hasher = DefaultHasher::new();
810 format!("{:?}", expr).hash(&mut hasher);
811 hasher.finish()
812}
813pub fn wrap_in_lambdas(expr: &Expr, n: usize) -> Expr {
815 (0..n).fold(expr.clone(), |acc, i| {
816 Expr::Lam(
817 BinderInfo::Default,
818 Name::str(format!("_w{}", i)),
819 Box::new(Expr::Sort(crate::Level::zero())),
820 Box::new(shift_up(&acc, 1)),
821 )
822 })
823}
824pub fn apply_bvars(expr: &Expr, n: u32) -> Expr {
826 (0..n).fold(expr.clone(), |acc, i| {
827 Expr::App(Box::new(acc), Box::new(Expr::BVar(i)))
828 })
829}
830pub fn reduce_let(expr: &Expr) -> Option<Expr> {
832 if let Expr::Let(_, _, val, body) = expr {
833 Some(subst_bvar(body, 0, val))
834 } else {
835 None
836 }
837}
838pub fn reduce_lets(expr: &Expr) -> Expr {
840 match expr {
841 Expr::Let(_, _, val, body) => {
842 let val2 = reduce_lets(val);
843 let body2 = reduce_lets(body);
844 reduce_lets(&subst_bvar(&body2, 0, &val2))
845 }
846 Expr::App(f, a) => Expr::App(Box::new(reduce_lets(f)), Box::new(reduce_lets(a))),
847 Expr::Lam(i, n, ty, body) => Expr::Lam(
848 *i,
849 n.clone(),
850 Box::new(reduce_lets(ty)),
851 Box::new(reduce_lets(body)),
852 ),
853 Expr::Pi(i, n, ty, body) => Expr::Pi(
854 *i,
855 n.clone(),
856 Box::new(reduce_lets(ty)),
857 Box::new(reduce_lets(body)),
858 ),
859 _ => expr.clone(),
860 }
861}
862#[cfg(test)]
863mod eta_further_tests {
864 use super::*;
865 use crate::Level;
866 fn sort() -> Expr {
867 Expr::Sort(Level::zero())
868 }
869 fn mk_const(s: &str) -> Expr {
870 Expr::Const(Name::str(s), vec![])
871 }
872 fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
873 Expr::Lam(
874 BinderInfo::Default,
875 Name::str(name),
876 Box::new(ty),
877 Box::new(body),
878 )
879 }
880 fn mk_app(f: Expr, a: Expr) -> Expr {
881 Expr::App(Box::new(f), Box::new(a))
882 }
883 #[test]
884 fn test_is_beta_redex() {
885 let lam = mk_lam("x", sort(), Expr::BVar(0));
886 let app = mk_app(lam, mk_const("a"));
887 assert!(is_beta_redex(&app));
888 assert!(!is_beta_redex(&mk_const("f")));
889 }
890 #[test]
891 fn test_is_eta_redex() {
892 let f = mk_const("f");
893 let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
894 assert!(is_eta_redex(&lam));
895 assert!(!is_eta_redex(&f));
896 }
897 #[test]
898 fn test_node_count() {
899 let expr = mk_app(mk_const("f"), mk_const("a"));
900 assert_eq!(node_count(&expr), 3);
901 assert_eq!(node_count(&mk_const("f")), 1);
902 }
903 #[test]
904 fn test_is_lambda_free() {
905 assert!(is_lambda_free(&mk_const("f")));
906 assert!(!is_lambda_free(&mk_lam("x", sort(), Expr::BVar(0))));
907 }
908 #[test]
909 fn test_rewrite_rule_apply() {
910 let rule = RewriteRule::new("id", mk_const("x"), mk_const("y"));
911 assert_eq!(rule.apply_top(&mk_const("x")), Some(mk_const("y")));
912 assert_eq!(rule.apply_top(&mk_const("z")), None);
913 }
914 #[test]
915 fn test_rewrite_with_rules() {
916 let rules = vec![RewriteRule::new("r", mk_const("a"), mk_const("b"))];
917 let expr = mk_app(mk_const("f"), mk_const("a"));
918 let result = rewrite_with_rules(&expr, &rules);
919 assert_eq!(result, mk_app(mk_const("f"), mk_const("b")));
920 }
921 #[test]
922 fn test_reduce_let() {
923 let body = Expr::BVar(0);
924 let val = mk_const("v");
925 let let_expr = Expr::Let(
926 Name::str("x"),
927 Box::new(sort()),
928 Box::new(val.clone()),
929 Box::new(body),
930 );
931 assert_eq!(reduce_let(&let_expr), Some(val));
932 }
933 #[test]
934 fn test_wrap_in_lambdas() {
935 let body = mk_const("f");
936 let wrapped = wrap_in_lambdas(&body, 2);
937 assert_eq!(binder_depth(&wrapped), 2);
938 }
939 #[test]
940 fn test_apply_bvars() {
941 let f = mk_const("f");
942 let result = apply_bvars(&f, 2);
943 let (_, args) = collect_app_spine(&result);
944 assert_eq!(args.len(), 2);
945 }
946 #[test]
947 fn test_structural_hash_stable() {
948 let e = mk_const("foo");
949 assert_eq!(structural_hash(&e), structural_hash(&e));
950 }
951}
952#[allow(dead_code)]
954pub fn eta_normalize_tracked(expr: &Expr) -> EtaNormInfo {
955 let mut count = 0;
956 let mut current = expr.clone();
957 while let Some(c) = eta_contract(¤t) {
958 count += 1;
959 current = c;
960 }
961 if count == 0 {
962 EtaNormInfo::already_normal(current)
963 } else {
964 EtaNormInfo::contracted(current, count)
965 }
966}
967#[allow(dead_code)]
971pub fn eta_def_eq(e1: &Expr, e2: &Expr) -> bool {
972 eta_normalize(e1) == eta_normalize(e2)
973}
974#[allow(dead_code)]
979pub fn eta_head(expr: &Expr) -> Expr {
980 let contracted = eta_contract_full(expr);
981 if contracted == *expr {
982 expr.clone()
983 } else {
984 eta_head(&contracted)
985 }
986}
987#[allow(dead_code)]
989pub fn outer_lambda_depth(expr: &Expr) -> usize {
990 match expr {
991 Expr::Lam(_, _, _, body) => 1 + outer_lambda_depth(body),
992 _ => 0,
993 }
994}
995#[allow(dead_code)]
999pub fn eta_expand_with_ty(expr: &Expr, n: usize, arg_ty: &Expr) -> Expr {
1000 let current_depth = outer_lambda_depth(expr);
1001 let mut result = expr.clone();
1002 for _ in current_depth..n {
1003 result = eta_expand(&result, Name::str("_x"), arg_ty.clone());
1004 }
1005 result
1006}
1007#[allow(dead_code)]
1011pub fn expr_is_closed(expr: &Expr) -> bool {
1012 is_closed(expr)
1013}
1014#[allow(dead_code)]
1016pub fn expr_structure_desc(expr: &Expr) -> &'static str {
1017 match expr {
1018 Expr::BVar(_) => "BVar",
1019 Expr::FVar(_) => "FVar",
1020 Expr::Const(_, _) => "Const",
1021 Expr::Sort(_) => "Sort",
1022 Expr::Lam(_, _, _, _) => "Lam",
1023 Expr::Pi(_, _, _, _) => "Pi",
1024 Expr::App(_, _) => "App",
1025 Expr::Let(_, _, _, _) => "Let",
1026 Expr::Lit(_) => "Lit",
1027 Expr::Proj(_, _, _) => "Proj",
1028 }
1029}
1030#[cfg(test)]
1031mod extra_eta_tests {
1032 use super::*;
1033 fn mk_sort_expr() -> Expr {
1034 Expr::Sort(crate::Level::zero())
1035 }
1036 fn mk_const_e(s: &str) -> Expr {
1037 Expr::Const(Name::str(s), vec![])
1038 }
1039 fn mk_app_e(f: Expr, a: Expr) -> Expr {
1040 Expr::App(Box::new(f), Box::new(a))
1041 }
1042 fn mk_lam_e(body: Expr) -> Expr {
1043 Expr::Lam(
1044 crate::BinderInfo::Default,
1045 Name::str("x"),
1046 Box::new(mk_sort_expr()),
1047 Box::new(body),
1048 )
1049 }
1050 #[test]
1051 fn test_eta_normalize_tracked_already_normal() {
1052 let e = mk_const_e("f");
1053 let info = eta_normalize_tracked(&e);
1054 assert!(info.already_normal);
1055 assert_eq!(info.contractions, 0);
1056 }
1057 #[test]
1058 fn test_eta_normalize_tracked_contracts_one() {
1059 let f = mk_const_e("f");
1060 let e = mk_lam_e(mk_app_e(f.clone(), Expr::BVar(0)));
1061 let info = eta_normalize_tracked(&e);
1062 assert!(!info.already_normal);
1063 assert_eq!(info.contractions, 1);
1064 assert_eq!(info.normalized, f);
1065 }
1066 #[test]
1067 fn test_eta_def_eq_same() {
1068 let e = mk_const_e("foo");
1069 assert!(eta_def_eq(&e, &e));
1070 }
1071 #[test]
1072 fn test_eta_def_eq_eta_variants() {
1073 let f = mk_const_e("f");
1074 let e = mk_lam_e(mk_app_e(f.clone(), Expr::BVar(0)));
1075 assert!(eta_def_eq(&e, &f));
1076 }
1077 #[test]
1078 fn test_outer_lambda_depth_zero() {
1079 assert_eq!(outer_lambda_depth(&mk_const_e("x")), 0);
1080 }
1081 #[test]
1082 fn test_outer_lambda_depth_one() {
1083 let e = mk_lam_e(Expr::BVar(0));
1084 assert_eq!(outer_lambda_depth(&e), 1);
1085 }
1086 #[test]
1087 fn test_outer_lambda_depth_two() {
1088 let e = mk_lam_e(mk_lam_e(Expr::BVar(0)));
1089 assert_eq!(outer_lambda_depth(&e), 2);
1090 }
1091 #[test]
1092 fn test_eta_expand_to_already_deep_enough() {
1093 let e = mk_lam_e(Expr::BVar(0));
1094 let expanded = eta_expand_with_ty(&e, 1, &mk_sort_expr());
1095 assert_eq!(outer_lambda_depth(&expanded), 1);
1096 }
1097 #[test]
1098 fn test_is_closed_const() {
1099 assert!(expr_is_closed(&mk_const_e("Nat")));
1100 }
1101 #[test]
1102 fn test_is_closed_fvar() {
1103 let e = Expr::FVar(crate::FVarId::new(1));
1104 assert!(!expr_is_closed(&e));
1105 }
1106 #[test]
1107 fn test_is_closed_app_with_fvar() {
1108 let f = mk_const_e("f");
1109 let a = Expr::FVar(crate::FVarId::new(99));
1110 let e = mk_app_e(f, a);
1111 assert!(!expr_is_closed(&e));
1112 }
1113 #[test]
1114 fn test_is_closed_nested() {
1115 let e = mk_app_e(mk_const_e("f"), mk_const_e("a"));
1116 assert!(expr_is_closed(&e));
1117 }
1118 #[test]
1119 fn test_expr_structure_desc() {
1120 assert_eq!(expr_structure_desc(&mk_const_e("x")), "Const");
1121 assert_eq!(expr_structure_desc(&mk_lam_e(Expr::BVar(0))), "Lam");
1122 assert_eq!(expr_structure_desc(&Expr::BVar(0)), "BVar");
1123 }
1124 #[test]
1125 fn test_eta_head_already_contracted() {
1126 let f = mk_const_e("f");
1127 assert_eq!(eta_head(&f), f);
1128 }
1129 #[test]
1130 fn test_eta_info_contracted_fields() {
1131 let info = EtaNormInfo::contracted(mk_const_e("x"), 3);
1132 assert!(!info.already_normal);
1133 assert_eq!(info.contractions, 3);
1134 }
1135}
1136#[cfg(test)]
1137mod tests_padding_infra {
1138 use super::*;
1139 #[test]
1140 fn test_stat_summary() {
1141 let mut ss = StatSummary::new();
1142 ss.record(10.0);
1143 ss.record(20.0);
1144 ss.record(30.0);
1145 assert_eq!(ss.count(), 3);
1146 assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1147 assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1148 assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1149 }
1150 #[test]
1151 fn test_transform_stat() {
1152 let mut ts = TransformStat::new();
1153 ts.record_before(100.0);
1154 ts.record_after(80.0);
1155 let ratio = ts.mean_ratio().expect("ratio should be present");
1156 assert!((ratio - 0.8).abs() < 1e-9);
1157 }
1158 #[test]
1159 fn test_small_map() {
1160 let mut m: SmallMap<u32, &str> = SmallMap::new();
1161 m.insert(3, "three");
1162 m.insert(1, "one");
1163 m.insert(2, "two");
1164 assert_eq!(m.get(&2), Some(&"two"));
1165 assert_eq!(m.len(), 3);
1166 let keys = m.keys();
1167 assert_eq!(*keys[0], 1);
1168 assert_eq!(*keys[2], 3);
1169 }
1170 #[test]
1171 fn test_label_set() {
1172 let mut ls = LabelSet::new();
1173 ls.add("foo");
1174 ls.add("bar");
1175 ls.add("foo");
1176 assert_eq!(ls.count(), 2);
1177 assert!(ls.has("bar"));
1178 assert!(!ls.has("baz"));
1179 }
1180 #[test]
1181 fn test_config_node() {
1182 let mut root = ConfigNode::section("root");
1183 let child = ConfigNode::leaf("key", "value");
1184 root.add_child(child);
1185 assert_eq!(root.num_children(), 1);
1186 }
1187 #[test]
1188 fn test_versioned_record() {
1189 let mut vr = VersionedRecord::new(0u32);
1190 vr.update(1);
1191 vr.update(2);
1192 assert_eq!(*vr.current(), 2);
1193 assert_eq!(vr.version(), 2);
1194 assert!(vr.has_history());
1195 assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1196 }
1197 #[test]
1198 fn test_simple_dag() {
1199 let mut dag = SimpleDag::new(4);
1200 dag.add_edge(0, 1);
1201 dag.add_edge(1, 2);
1202 dag.add_edge(2, 3);
1203 assert!(dag.can_reach(0, 3));
1204 assert!(!dag.can_reach(3, 0));
1205 let order = dag.topological_sort().expect("order should be present");
1206 assert_eq!(order, vec![0, 1, 2, 3]);
1207 }
1208 #[test]
1209 fn test_focus_stack() {
1210 let mut fs: FocusStack<&str> = FocusStack::new();
1211 fs.focus("a");
1212 fs.focus("b");
1213 assert_eq!(fs.current(), Some(&"b"));
1214 assert_eq!(fs.depth(), 2);
1215 fs.blur();
1216 assert_eq!(fs.current(), Some(&"a"));
1217 }
1218}
1219#[cfg(test)]
1220mod tests_extra_iterators {
1221 use super::*;
1222 #[test]
1223 fn test_window_iterator() {
1224 let data = vec![1u32, 2, 3, 4, 5];
1225 let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1226 assert_eq!(windows.len(), 3);
1227 assert_eq!(windows[0], &[1, 2, 3]);
1228 assert_eq!(windows[2], &[3, 4, 5]);
1229 }
1230 #[test]
1231 fn test_non_empty_vec() {
1232 let mut nev = NonEmptyVec::singleton(10u32);
1233 nev.push(20);
1234 nev.push(30);
1235 assert_eq!(nev.len(), 3);
1236 assert_eq!(*nev.first(), 10);
1237 assert_eq!(*nev.last(), 30);
1238 }
1239}
1240#[cfg(test)]
1241mod tests_eta_padding {
1242 use super::*;
1243 #[test]
1244 fn test_eta_reduction_stats() {
1245 let mut s = EtaReductionStats::new();
1246 s.examined = 10;
1247 s.reductions = 7;
1248 assert!((s.ratio() - 0.7).abs() < 1e-9);
1249 }
1250 #[test]
1251 fn test_eta_op_counter() {
1252 let mut c = EtaOpCounter::new();
1253 c.inc("check");
1254 c.inc("check");
1255 c.inc("reduce");
1256 assert_eq!(c.get("check"), 2);
1257 assert_eq!(c.total(), 3);
1258 }
1259 #[test]
1260 fn test_eta_normal_cache() {
1261 let mut cache = EtaNormalCache::new();
1262 cache.insert(42, true);
1263 assert_eq!(cache.query(42), Some(true));
1264 assert_eq!(cache.query(99), None);
1265 cache.clear();
1266 assert_eq!(cache.len(), 0);
1267 }
1268 #[test]
1269 fn test_eta_job_queue() {
1270 let mut q = EtaJobQueue::new();
1271 q.enqueue(EtaJob::new(1, 100, 5));
1272 q.enqueue(EtaJob::new(2, 200, 10));
1273 q.enqueue(EtaJob::new(3, 300, 1));
1274 assert_eq!(q.len(), 3);
1275 let job = q.dequeue().expect("job should be present");
1276 assert_eq!(job.prio, 10);
1277 }
1278 #[test]
1279 fn test_eta_outcome() {
1280 assert!(EtaOutcome::Reduced.is_success());
1281 assert!(EtaOutcome::AlreadyNormal.is_success());
1282 assert!(!EtaOutcome::NotApplicable.is_success());
1283 assert_eq!(EtaOutcome::Reduced.label(), "reduced");
1284 }
1285 #[test]
1286 fn test_eta_log() {
1287 let mut log = EtaLog::new();
1288 log.record(EtaOutcome::Reduced);
1289 log.record(EtaOutcome::AlreadyNormal);
1290 log.record(EtaOutcome::NotApplicable);
1291 assert_eq!(log.count(EtaOutcome::Reduced), 1);
1292 assert!((log.success_rate() - 2.0 / 3.0).abs() < 1e-9);
1293 }
1294 #[test]
1295 fn test_eta_structure() {
1296 let s = EtaStructure::Lambda(3);
1297 assert_eq!(s.arity(), 3);
1298 let a = EtaStructure::Atomic;
1299 assert_eq!(a.arity(), 0);
1300 }
1301 #[test]
1302 fn test_eta_checker() {
1303 let mut ec = EtaChecker::new();
1304 let outcome = ec.is_eta_normal(42);
1305 assert!(!outcome.is_success());
1306 }
1307}
1308#[cfg(test)]
1309mod tests_eta_stat_counter {
1310 use super::*;
1311 #[test]
1312 fn test_eta_stat_counter() {
1313 let mut c = EtaStatCounter::new();
1314 c.record(10);
1315 c.record(20);
1316 c.record(30);
1317 assert!((c.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1318 assert_eq!(c.count(), 3);
1319 }
1320}