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