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