1use crate::{Expr, Reducer};
6
7use super::types::{
8 ConfigNode, DecisionNode, Either2, Fixture, FlatSubstitution, FocusStack, HeadForm, LabelSet,
9 MinHeap, NonEmptyVec, PathBuf, PrefixCounter, RedexInfo, RedexKind, ReductionBound,
10 ReductionMemo, ReductionResult, ReductionStats, ReductionStep, ReductionStrategy,
11 ReductionTrace, RewriteRule, RewriteRuleSet, SimpleDag, SlidingSum, SmallMap, SparseVec,
12 StackCalc, StatSummary, Stopwatch, StringPool, TokenBucket, TransformStat, TransitiveClosure,
13 VersionedRecord, WindowIterator, WriteOnce,
14};
15
16pub fn reduce_with_strategy(expr: &Expr, strategy: ReductionStrategy) -> Expr {
21 let mut reducer = Reducer::new();
22 match strategy {
23 ReductionStrategy::WHNF => reducer.whnf(expr),
24 ReductionStrategy::NF => reduce_to_nf(expr, &mut reducer, 500),
25 ReductionStrategy::OneStep => one_step_reduce(expr).unwrap_or_else(|| expr.clone()),
26 ReductionStrategy::CBV => reduce_cbv(expr, &mut reducer),
27 ReductionStrategy::CBN => reduce_cbn(expr, &mut reducer),
28 ReductionStrategy::HeadOnly => reduce_head(expr),
29 }
30}
31pub(super) fn reduce_to_nf(expr: &Expr, reducer: &mut Reducer, fuel: u32) -> Expr {
36 if fuel == 0 {
37 return expr.clone();
38 }
39 let whnf = reducer.whnf(expr);
40 match whnf {
41 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Lit(_) | Expr::Const(_, _) => whnf,
42 Expr::Lam(bi, name, ty, body) => {
43 let ty_nf = reduce_to_nf(&ty, reducer, fuel - 1);
44 let body_nf = reduce_to_nf(&body, reducer, fuel - 1);
45 Expr::Lam(bi, name, Box::new(ty_nf), Box::new(body_nf))
46 }
47 Expr::Pi(bi, name, ty, body) => {
48 let ty_nf = reduce_to_nf(&ty, reducer, fuel - 1);
49 let body_nf = reduce_to_nf(&body, reducer, fuel - 1);
50 Expr::Pi(bi, name, Box::new(ty_nf), Box::new(body_nf))
51 }
52 Expr::App(f, a) => {
53 let f_nf = reduce_to_nf(&f, reducer, fuel - 1);
54 let a_nf = reduce_to_nf(&a, reducer, fuel - 1);
55 let rebuilt = Expr::App(Box::new(f_nf), Box::new(a_nf));
56 let whnf2 = reducer.whnf(&rebuilt);
57 if whnf2 == rebuilt {
58 rebuilt
59 } else {
60 reduce_to_nf(&whnf2, reducer, fuel.saturating_sub(1))
61 }
62 }
63 Expr::Let(_name, _ty, val, body) => {
64 let reduced = crate::subst::instantiate(&body, &val);
65 reduce_to_nf(&reduced, reducer, fuel - 1)
66 }
67 Expr::Proj(struct_name, idx, e) => {
68 let e_nf = reduce_to_nf(&e, reducer, fuel - 1);
69 Expr::Proj(struct_name, idx, Box::new(e_nf))
70 }
71 }
72}
73#[allow(clippy::only_used_in_recursion)]
77pub(super) fn one_step_reduce(expr: &Expr) -> Option<Expr> {
78 match expr {
79 Expr::App(f, a) => {
80 if let Expr::Lam(_, _, _, body) = f.as_ref() {
81 let reduced = crate::subst::instantiate(body, a);
82 return Some(reduced);
83 }
84 if let Some(f_reduced) = one_step_reduce(f) {
85 return Some(Expr::App(Box::new(f_reduced), a.clone()));
86 }
87 if let Some(a_reduced) = one_step_reduce(a) {
88 return Some(Expr::App(f.clone(), Box::new(a_reduced)));
89 }
90 None
91 }
92 Expr::Let(_, _, val, body) => {
93 let reduced = crate::subst::instantiate(body, val);
94 Some(reduced)
95 }
96 Expr::Lam(bi, name, ty, body) => {
97 if let Some(ty_r) = one_step_reduce(ty) {
98 return Some(Expr::Lam(*bi, name.clone(), Box::new(ty_r), body.clone()));
99 }
100 if let Some(body_r) = one_step_reduce(body) {
101 return Some(Expr::Lam(*bi, name.clone(), ty.clone(), Box::new(body_r)));
102 }
103 None
104 }
105 Expr::Pi(bi, name, ty, body) => {
106 if let Some(ty_r) = one_step_reduce(ty) {
107 return Some(Expr::Pi(*bi, name.clone(), Box::new(ty_r), body.clone()));
108 }
109 if let Some(body_r) = one_step_reduce(body) {
110 return Some(Expr::Pi(*bi, name.clone(), ty.clone(), Box::new(body_r)));
111 }
112 None
113 }
114 Expr::Proj(struct_name, idx, e) => {
115 if let Some(e_r) = one_step_reduce(e) {
116 return Some(Expr::Proj(struct_name.clone(), *idx, Box::new(e_r)));
117 }
118 None
119 }
120 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Lit(_) | Expr::Const(_, _) => None,
121 }
122}
123pub(super) fn reduce_cbv(expr: &Expr, reducer: &mut Reducer) -> Expr {
128 reduce_cbv_impl(expr, reducer, 500)
129}
130#[allow(clippy::only_used_in_recursion)]
131pub(super) fn reduce_cbv_impl(expr: &Expr, reducer: &mut Reducer, fuel: u32) -> Expr {
132 if fuel == 0 {
133 return expr.clone();
134 }
135 match expr {
136 Expr::App(f, a) => {
137 let a_val = reducer.whnf(a);
138 let f_val = reduce_cbv_impl(f, reducer, fuel - 1);
139 match f_val {
140 Expr::Lam(_, _, _, body) => {
141 let reduced = crate::subst::instantiate(&body, &a_val);
142 reduce_cbv_impl(&reduced, reducer, fuel - 1)
143 }
144 _ => Expr::App(Box::new(f_val), Box::new(a_val)),
145 }
146 }
147 Expr::Let(_, _, val, body) => {
148 let val_whnf = reducer.whnf(val);
149 let reduced = crate::subst::instantiate(body, &val_whnf);
150 reduce_cbv_impl(&reduced, reducer, fuel - 1)
151 }
152 Expr::Lam(_, _, _, _) | Expr::Pi(_, _, _, _) => reducer.whnf(expr),
153 _ => reducer.whnf(expr),
154 }
155}
156pub(super) fn reduce_cbn(expr: &Expr, reducer: &mut Reducer) -> Expr {
162 reduce_cbn_impl(expr, reducer, 500)
163}
164#[allow(clippy::only_used_in_recursion)]
165pub(super) fn reduce_cbn_impl(expr: &Expr, reducer: &mut Reducer, fuel: u32) -> Expr {
166 if fuel == 0 {
167 return expr.clone();
168 }
169 match expr {
170 Expr::App(f, a) => {
171 let f_val = reduce_cbn_impl(f, reducer, fuel - 1);
172 match f_val {
173 Expr::Lam(_, _, _, body) => {
174 let reduced = crate::subst::instantiate(&body, a);
175 reduce_cbn_impl(&reduced, reducer, fuel - 1)
176 }
177 _ => Expr::App(Box::new(f_val), a.clone()),
178 }
179 }
180 Expr::Let(_, _, val, body) => {
181 let reduced = crate::subst::instantiate(body, val);
182 reduce_cbn_impl(&reduced, reducer, fuel - 1)
183 }
184 _ => reducer.whnf(expr),
185 }
186}
187pub fn reduce_head(expr: &Expr) -> Expr {
192 let mut reducer = Reducer::new();
193 reducer.whnf(expr)
194}
195pub fn is_normal_form(expr: &Expr) -> bool {
200 match expr {
201 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) => true,
202 Expr::Const(_, _) => true,
203 Expr::Lit(_) => true,
204 Expr::Lam(_, _, ty, body) => is_normal_form(ty) && is_normal_form(body),
205 Expr::Pi(_, _, ty, body) => is_normal_form(ty) && is_normal_form(body),
206 Expr::App(f, a) => {
207 if matches!(f.as_ref(), Expr::Lam(_, _, _, _)) {
208 return false;
209 }
210 is_normal_form(f) && is_normal_form(a)
211 }
212 Expr::Let(_, _, _, _) => false,
213 Expr::Proj(_, _, e) => is_normal_form(e),
214 }
215}
216pub fn count_reduction_steps(expr: &Expr, _strategy: ReductionStrategy) -> usize {
221 let mut steps = 0;
222 let mut current = expr.clone();
223 loop {
224 let next = reduce_with_strategy(¤t, ReductionStrategy::OneStep);
225 if next == current {
226 break;
227 }
228 steps += 1;
229 current = next;
230 if steps > 10000 {
231 break;
232 }
233 }
234 steps
235}
236pub fn are_convertible(e1: &Expr, e2: &Expr) -> bool {
242 let nf1 = reduce_with_strategy(e1, ReductionStrategy::NF);
243 let nf2 = reduce_with_strategy(e2, ReductionStrategy::NF);
244 nf1 == nf2
245}
246pub fn try_reduce_step(expr: &Expr) -> ReductionResult {
251 let reduced = reduce_with_strategy(expr, ReductionStrategy::OneStep);
252 if reduced == *expr {
253 ReductionResult::Normal(reduced)
254 } else {
255 ReductionResult::Reduced(reduced)
256 }
257}
258pub fn build_reduction_trace(expr: &Expr, max_steps: usize) -> ReductionTrace {
263 let mut trace = ReductionTrace::new();
264 let mut current = expr.clone();
265 for _ in 0..max_steps {
266 let next = reduce_with_strategy(¤t, ReductionStrategy::OneStep);
267 if next == current {
268 trace.reached_normal = true;
269 break;
270 }
271 trace.steps.push(ReductionStep {
272 before: current.clone(),
273 after: next.clone(),
274 rule: "beta/delta".to_string(),
275 });
276 current = next;
277 }
278 if !trace.reached_normal && trace.len() >= max_steps {
279 trace.truncated = true;
280 }
281 trace
282}
283pub fn expr_size(expr: &Expr) -> usize {
287 match expr {
288 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 1,
289 Expr::App(f, a) => 1 + expr_size(f) + expr_size(a),
290 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => 1 + expr_size(ty) + expr_size(body),
291 Expr::Let(_, ty, val, body) => 1 + expr_size(ty) + expr_size(val) + expr_size(body),
292 Expr::Proj(_, _, e) => 1 + expr_size(e),
293 }
294}
295pub fn expr_depth(expr: &Expr) -> usize {
297 match expr {
298 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 1,
299 Expr::App(f, a) => 1 + expr_depth(f).max(expr_depth(a)),
300 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
301 1 + expr_depth(ty).max(expr_depth(body))
302 }
303 Expr::Let(_, ty, val, body) => {
304 1 + expr_depth(ty).max(expr_depth(val)).max(expr_depth(body))
305 }
306 Expr::Proj(_, _, e) => 1 + expr_depth(e),
307 }
308}
309pub fn count_beta_redexes(expr: &Expr) -> usize {
313 match expr {
314 Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 0,
315 Expr::App(f, a) => {
316 let is_redex = matches!(f.as_ref(), Expr::Lam(_, _, _, _));
317 let base = if is_redex { 1 } else { 0 };
318 base + count_beta_redexes(f) + count_beta_redexes(a)
319 }
320 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
321 count_beta_redexes(ty) + count_beta_redexes(body)
322 }
323 Expr::Let(_, ty, val, body) => {
324 1 + count_beta_redexes(ty) + count_beta_redexes(val) + count_beta_redexes(body)
325 }
326 Expr::Proj(_, _, e) => count_beta_redexes(e),
327 }
328}
329pub fn classify_head(expr: &Expr) -> HeadForm {
331 match expr {
332 Expr::BVar(i) => HeadForm::BVar(*i),
333 Expr::FVar(_) => HeadForm::FVar,
334 Expr::Const(name, _) => HeadForm::Const(name.clone()),
335 Expr::Sort(_) => HeadForm::Sort,
336 Expr::Lam(_, _, _, _) => HeadForm::Lambda,
337 Expr::Pi(_, _, _, _) => HeadForm::Pi,
338 Expr::Lit(_) => HeadForm::Lit,
339 Expr::Let(_, _, _, _) => HeadForm::Let,
340 Expr::Proj(_, _, _) => HeadForm::Proj,
341 Expr::App(f, _) => {
342 if matches!(f.as_ref(), Expr::Lam(_, _, _, _)) {
343 HeadForm::BetaRedex
344 } else {
345 classify_head(f)
346 }
347 }
348 }
349}
350pub fn find_redexes(expr: &Expr) -> Vec<RedexInfo> {
354 let mut redexes = Vec::new();
355 find_redexes_rec(expr, 0, &mut redexes);
356 redexes
357}
358pub(super) fn find_redexes_rec(expr: &Expr, depth: usize, redexes: &mut Vec<RedexInfo>) {
359 match expr {
360 Expr::App(f, a) => {
361 if matches!(f.as_ref(), Expr::Lam(_, _, _, _)) {
362 redexes.push(RedexInfo {
363 kind: RedexKind::Beta,
364 depth,
365 size: expr_size(expr),
366 });
367 }
368 find_redexes_rec(f, depth + 1, redexes);
369 find_redexes_rec(a, depth + 1, redexes);
370 }
371 Expr::Let(_, ty, val, body) => {
372 redexes.push(RedexInfo {
373 kind: RedexKind::Let,
374 depth,
375 size: expr_size(expr),
376 });
377 find_redexes_rec(ty, depth + 1, redexes);
378 find_redexes_rec(val, depth + 1, redexes);
379 find_redexes_rec(body, depth + 1, redexes);
380 }
381 Expr::Proj(_, _, e) => {
382 find_redexes_rec(e, depth + 1, redexes);
383 }
384 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
385 find_redexes_rec(ty, depth + 1, redexes);
386 find_redexes_rec(body, depth + 1, redexes);
387 }
388 _ => {}
389 }
390}
391pub fn reduce_bounded(
395 expr: &Expr,
396 strategy: ReductionStrategy,
397 bound: ReductionBound,
398) -> (Expr, ReductionStats) {
399 let mut stats = ReductionStats::new();
400 let mut current = expr.clone();
401 loop {
402 if bound.exceeded(stats.total_steps, expr_size(¤t)) {
403 stats.aborted = true;
404 break;
405 }
406 let next = reduce_with_strategy(¤t, strategy);
407 if next == current {
408 break;
409 }
410 stats.total_steps += 1;
411 let head = classify_head(¤t);
412 match head {
413 HeadForm::BetaRedex => stats.beta_steps += 1,
414 HeadForm::Let => stats.let_steps += 1,
415 _ => stats.delta_steps += 1,
416 }
417 current = next;
418 }
419 (current, stats)
420}
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use crate::{BinderInfo, Level, Literal, Name};
425 fn mk_nat_lit(n: u64) -> Expr {
426 Expr::Lit(Literal::Nat(n))
427 }
428 fn mk_lam(body: Expr) -> Expr {
429 Expr::Lam(
430 BinderInfo::Default,
431 Name::str("x"),
432 Box::new(Expr::Sort(Level::zero())),
433 Box::new(body),
434 )
435 }
436 fn mk_app(f: Expr, a: Expr) -> Expr {
437 Expr::App(Box::new(f), Box::new(a))
438 }
439 fn mk_let(val: Expr, body: Expr) -> Expr {
440 Expr::Let(
441 Name::str("x"),
442 Box::new(Expr::Sort(Level::zero())),
443 Box::new(val),
444 Box::new(body),
445 )
446 }
447 #[test]
448 fn test_reduce_whnf() {
449 let expr = Expr::Sort(Level::zero());
450 let result = reduce_with_strategy(&expr, ReductionStrategy::WHNF);
451 assert_eq!(result, expr);
452 }
453 #[test]
454 fn test_is_normal_form_sort() {
455 let expr = Expr::Sort(Level::zero());
456 assert!(is_normal_form(&expr));
457 }
458 #[test]
459 fn test_is_normal_form_lit() {
460 let expr = Expr::Lit(Literal::Nat(42));
461 assert!(is_normal_form(&expr));
462 }
463 #[test]
464 fn test_not_normal_form_beta() {
465 let lam = mk_lam(Expr::BVar(0));
466 let app = mk_app(lam, mk_nat_lit(42));
467 assert!(!is_normal_form(&app));
468 }
469 #[test]
470 fn test_not_normal_form_let() {
471 let let_expr = mk_let(mk_nat_lit(42), Expr::BVar(0));
472 assert!(!is_normal_form(&let_expr));
473 }
474 #[test]
475 fn test_are_convertible_same() {
476 let e1 = mk_nat_lit(42);
477 let e2 = mk_nat_lit(42);
478 assert!(are_convertible(&e1, &e2));
479 }
480 #[test]
481 fn test_count_reduction_steps_zero() {
482 let expr = mk_nat_lit(42);
483 let steps = count_reduction_steps(&expr, ReductionStrategy::WHNF);
484 assert_eq!(steps, 0);
485 }
486 #[test]
487 fn test_reduction_strategy_name() {
488 assert_eq!(ReductionStrategy::WHNF.name(), "whnf");
489 assert_eq!(ReductionStrategy::NF.name(), "nf");
490 assert_eq!(ReductionStrategy::CBV.name(), "cbv");
491 assert_eq!(ReductionStrategy::HeadOnly.name(), "head-only");
492 }
493 #[test]
494 fn test_reduction_strategy_lazy_eager() {
495 assert!(ReductionStrategy::WHNF.is_lazy());
496 assert!(ReductionStrategy::CBV.is_eager());
497 assert!(!ReductionStrategy::WHNF.is_eager());
498 }
499 #[test]
500 fn test_reduction_strategy_complete() {
501 assert!(ReductionStrategy::NF.is_complete());
502 assert!(!ReductionStrategy::WHNF.is_complete());
503 }
504 #[test]
505 fn test_try_reduce_step_normal() {
506 let expr = mk_nat_lit(5);
507 let result = try_reduce_step(&expr);
508 assert!(result.is_normal());
509 }
510 #[test]
511 fn test_try_reduce_step_reduces() {
512 let lam = mk_lam(Expr::BVar(0));
513 let app = mk_app(lam, mk_nat_lit(0));
514 let result = try_reduce_step(&app);
515 assert!(result.was_reduced());
516 }
517 #[test]
518 fn test_classify_head_sort() {
519 let e = Expr::Sort(Level::zero());
520 assert_eq!(classify_head(&e), HeadForm::Sort);
521 }
522 #[test]
523 fn test_classify_head_lambda() {
524 let e = mk_lam(Expr::BVar(0));
525 assert_eq!(classify_head(&e), HeadForm::Lambda);
526 }
527 #[test]
528 fn test_classify_head_beta_redex() {
529 let lam = mk_lam(Expr::BVar(0));
530 let app = mk_app(lam, mk_nat_lit(42));
531 assert_eq!(classify_head(&app), HeadForm::BetaRedex);
532 }
533 #[test]
534 fn test_classify_head_let() {
535 let e = mk_let(mk_nat_lit(1), Expr::BVar(0));
536 assert_eq!(classify_head(&e), HeadForm::Let);
537 }
538 #[test]
539 fn test_expr_size_lit() {
540 assert_eq!(expr_size(&mk_nat_lit(0)), 1);
541 }
542 #[test]
543 fn test_expr_size_app() {
544 let app = mk_app(mk_nat_lit(1), mk_nat_lit(2));
545 assert_eq!(expr_size(&app), 3);
546 }
547 #[test]
548 fn test_expr_depth() {
549 let lit = mk_nat_lit(0);
550 assert_eq!(expr_depth(&lit), 1);
551 let app = mk_app(lit.clone(), lit.clone());
552 assert_eq!(expr_depth(&app), 2);
553 }
554 #[test]
555 fn test_count_beta_redexes_none() {
556 let expr = mk_nat_lit(42);
557 assert_eq!(count_beta_redexes(&expr), 0);
558 }
559 #[test]
560 fn test_count_beta_redexes_one() {
561 let lam = mk_lam(Expr::BVar(0));
562 let app = mk_app(lam, mk_nat_lit(5));
563 assert_eq!(count_beta_redexes(&app), 1);
564 }
565 #[test]
566 fn test_count_beta_redexes_let() {
567 let e = mk_let(mk_nat_lit(1), Expr::BVar(0));
568 assert_eq!(count_beta_redexes(&e), 1);
569 }
570 #[test]
571 fn test_find_redexes_empty() {
572 let expr = mk_nat_lit(42);
573 let redexes = find_redexes(&expr);
574 assert!(redexes.is_empty());
575 }
576 #[test]
577 fn test_find_redexes_beta() {
578 let lam = mk_lam(Expr::BVar(0));
579 let app = mk_app(lam, mk_nat_lit(5));
580 let redexes = find_redexes(&app);
581 assert!(!redexes.is_empty());
582 assert!(redexes.iter().any(|r| r.kind == RedexKind::Beta));
583 }
584 #[test]
585 fn test_find_redexes_let() {
586 let e = mk_let(mk_nat_lit(1), Expr::BVar(0));
587 let redexes = find_redexes(&e);
588 assert!(redexes.iter().any(|r| r.kind == RedexKind::Let));
589 }
590 #[test]
591 fn test_reduction_stats_summary() {
592 let stats = ReductionStats {
593 beta_steps: 3,
594 delta_steps: 1,
595 let_steps: 0,
596 proj_steps: 0,
597 total_steps: 4,
598 aborted: false,
599 };
600 let s = stats.summary();
601 assert!(s.contains("β:3"));
602 assert!(s.contains("total:4"));
603 }
604 #[test]
605 fn test_reduction_bound_exceeded() {
606 let bound = ReductionBound::Steps(5);
607 assert!(!bound.exceeded(4, 100));
608 assert!(bound.exceeded(5, 100));
609 }
610 #[test]
611 fn test_reduce_bounded_normal() {
612 let expr = mk_nat_lit(42);
613 let (result, stats) =
614 reduce_bounded(&expr, ReductionStrategy::WHNF, ReductionBound::Steps(100));
615 assert_eq!(result, expr);
616 assert!(!stats.any_reductions());
617 }
618 #[test]
619 fn test_build_reduction_trace_normal() {
620 let expr = mk_nat_lit(1);
621 let trace = build_reduction_trace(&expr, 10);
622 assert!(trace.reached_normal);
623 assert!(trace.is_empty());
624 }
625 #[test]
626 fn test_head_form_is_neutral() {
627 assert!(HeadForm::BVar(0).is_neutral());
628 assert!(HeadForm::FVar.is_neutral());
629 assert!(HeadForm::Const(Name::str("foo")).is_neutral());
630 assert!(!HeadForm::Lambda.is_neutral());
631 }
632 #[test]
633 fn test_redex_kind_description() {
634 assert!(RedexKind::Beta.description().contains('β'));
635 assert!(RedexKind::Let.description().contains("let"));
636 let delta = RedexKind::Delta(Name::str("foo"));
637 assert!(delta.description().contains("foo"));
638 }
639 #[test]
640 fn test_reduction_result_accessors() {
641 let e = mk_nat_lit(0);
642 let r = ReductionResult::Normal(e.clone());
643 assert!(r.is_normal());
644 assert!(!r.was_reduced());
645 assert_eq!(r.into_expr(), e);
646 }
647}
648pub fn alpha_equiv(e1: &Expr, e2: &Expr) -> bool {
653 match (e1, e2) {
654 (Expr::BVar(i), Expr::BVar(j)) => i == j,
655 (Expr::FVar(a), Expr::FVar(b)) => a == b,
656 (Expr::Sort(l1), Expr::Sort(l2)) => l1 == l2,
657 (Expr::Lit(l1), Expr::Lit(l2)) => l1 == l2,
658 (Expr::Const(n1, _), Expr::Const(n2, _)) => n1 == n2,
659 (Expr::App(f1, a1), Expr::App(f2, a2)) => alpha_equiv(f1, f2) && alpha_equiv(a1, a2),
660 (Expr::Lam(_, _, t1, b1), Expr::Lam(_, _, t2, b2)) => {
661 alpha_equiv(t1, t2) && alpha_equiv(b1, b2)
662 }
663 (Expr::Pi(_, _, t1, b1), Expr::Pi(_, _, t2, b2)) => {
664 alpha_equiv(t1, t2) && alpha_equiv(b1, b2)
665 }
666 (Expr::Let(_, ty1, v1, b1), Expr::Let(_, ty2, v2, b2)) => {
667 alpha_equiv(ty1, ty2) && alpha_equiv(v1, v2) && alpha_equiv(b1, b2)
668 }
669 _ => false,
670 }
671}
672pub fn expr_fingerprint(expr: &Expr) -> u64 {
676 use std::collections::hash_map::DefaultHasher;
677 use std::hash::{Hash, Hasher};
678 let mut h = DefaultHasher::new();
679 format!("{:?}", expr).hash(&mut h);
680 h.finish()
681}
682#[cfg(test)]
683mod extra_reduction_tests {
684 use super::*;
685 use crate::{Level, Literal, Name};
686 fn mk_nat_lit(n: u64) -> Expr {
687 Expr::Lit(Literal::Nat(n))
688 }
689 fn mk_sort() -> Expr {
690 Expr::Sort(Level::zero())
691 }
692 #[test]
693 fn test_reduction_memo_insert_get() {
694 let mut memo = ReductionMemo::new();
695 let expr = mk_nat_lit(5);
696 let result = mk_nat_lit(5);
697 memo.insert(ReductionStrategy::WHNF, &expr, result.clone());
698 let got = memo.get(ReductionStrategy::WHNF, &expr);
699 assert_eq!(got, Some(&result));
700 }
701 #[test]
702 fn test_reduction_memo_miss() {
703 let mut memo = ReductionMemo::new();
704 let expr = mk_nat_lit(42);
705 assert_eq!(memo.get(ReductionStrategy::NF, &expr), None);
706 }
707 #[test]
708 fn test_reduction_memo_hit_rate() {
709 let mut memo = ReductionMemo::new();
710 let expr = mk_nat_lit(1);
711 memo.insert(ReductionStrategy::WHNF, &expr, expr.clone());
712 let _ = memo.get(ReductionStrategy::WHNF, &expr);
713 let _ = memo.get(ReductionStrategy::NF, &expr);
714 let rate = memo.hit_rate();
715 assert!(rate > 0.0 && rate <= 1.0);
716 }
717 #[test]
718 fn test_reduction_memo_clear() {
719 let mut memo = ReductionMemo::new();
720 let expr = mk_nat_lit(1);
721 memo.insert(ReductionStrategy::WHNF, &expr, expr.clone());
722 memo.clear();
723 assert!(memo.is_empty());
724 }
725 #[test]
726 fn test_alpha_equiv_bvar() {
727 assert!(alpha_equiv(&Expr::BVar(0), &Expr::BVar(0)));
728 assert!(!alpha_equiv(&Expr::BVar(0), &Expr::BVar(1)));
729 }
730 #[test]
731 fn test_alpha_equiv_sort() {
732 assert!(alpha_equiv(&mk_sort(), &mk_sort()));
733 }
734 #[test]
735 fn test_alpha_equiv_app() {
736 let f = Expr::Const(Name::str("f"), vec![]);
737 let a = mk_nat_lit(1);
738 let e1 = Expr::App(Box::new(f.clone()), Box::new(a.clone()));
739 let e2 = Expr::App(Box::new(f), Box::new(a));
740 assert!(alpha_equiv(&e1, &e2));
741 }
742 #[test]
743 fn test_alpha_equiv_different_constructors() {
744 let e1 = mk_nat_lit(1);
745 let e2 = mk_sort();
746 assert!(!alpha_equiv(&e1, &e2));
747 }
748 #[test]
749 fn test_expr_fingerprint_same() {
750 let e = mk_nat_lit(7);
751 assert_eq!(expr_fingerprint(&e), expr_fingerprint(&e));
752 }
753 #[test]
754 fn test_expr_fingerprint_different() {
755 let e1 = mk_nat_lit(1);
756 let e2 = mk_nat_lit(2);
757 assert_ne!(expr_fingerprint(&e1), expr_fingerprint(&e2));
758 }
759 #[test]
760 fn test_reduction_memo_strategy_separation() {
761 let mut memo = ReductionMemo::new();
762 let expr = mk_nat_lit(3);
763 memo.insert(ReductionStrategy::WHNF, &expr, mk_nat_lit(10));
764 memo.insert(ReductionStrategy::NF, &expr, mk_nat_lit(20));
765 assert_eq!(
766 memo.get(ReductionStrategy::WHNF, &expr),
767 Some(&mk_nat_lit(10))
768 );
769 assert_eq!(
770 memo.get(ReductionStrategy::NF, &expr),
771 Some(&mk_nat_lit(20))
772 );
773 }
774 #[test]
775 fn test_reduction_memo_len() {
776 let mut memo = ReductionMemo::new();
777 let e = mk_nat_lit(0);
778 memo.insert(ReductionStrategy::WHNF, &e, e.clone());
779 assert_eq!(memo.len(), 1);
780 }
781}
782#[cfg(test)]
783mod tests_padding_infra {
784 use super::*;
785 #[test]
786 fn test_stat_summary() {
787 let mut ss = StatSummary::new();
788 ss.record(10.0);
789 ss.record(20.0);
790 ss.record(30.0);
791 assert_eq!(ss.count(), 3);
792 assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
793 assert_eq!(ss.min().expect("min should succeed") as i64, 10);
794 assert_eq!(ss.max().expect("max should succeed") as i64, 30);
795 }
796 #[test]
797 fn test_transform_stat() {
798 let mut ts = TransformStat::new();
799 ts.record_before(100.0);
800 ts.record_after(80.0);
801 let ratio = ts.mean_ratio().expect("ratio should be present");
802 assert!((ratio - 0.8).abs() < 1e-9);
803 }
804 #[test]
805 fn test_small_map() {
806 let mut m: SmallMap<u32, &str> = SmallMap::new();
807 m.insert(3, "three");
808 m.insert(1, "one");
809 m.insert(2, "two");
810 assert_eq!(m.get(&2), Some(&"two"));
811 assert_eq!(m.len(), 3);
812 let keys = m.keys();
813 assert_eq!(*keys[0], 1);
814 assert_eq!(*keys[2], 3);
815 }
816 #[test]
817 fn test_label_set() {
818 let mut ls = LabelSet::new();
819 ls.add("foo");
820 ls.add("bar");
821 ls.add("foo");
822 assert_eq!(ls.count(), 2);
823 assert!(ls.has("bar"));
824 assert!(!ls.has("baz"));
825 }
826 #[test]
827 fn test_config_node() {
828 let mut root = ConfigNode::section("root");
829 let child = ConfigNode::leaf("key", "value");
830 root.add_child(child);
831 assert_eq!(root.num_children(), 1);
832 }
833 #[test]
834 fn test_versioned_record() {
835 let mut vr = VersionedRecord::new(0u32);
836 vr.update(1);
837 vr.update(2);
838 assert_eq!(*vr.current(), 2);
839 assert_eq!(vr.version(), 2);
840 assert!(vr.has_history());
841 assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
842 }
843 #[test]
844 fn test_simple_dag() {
845 let mut dag = SimpleDag::new(4);
846 dag.add_edge(0, 1);
847 dag.add_edge(1, 2);
848 dag.add_edge(2, 3);
849 assert!(dag.can_reach(0, 3));
850 assert!(!dag.can_reach(3, 0));
851 let order = dag.topological_sort().expect("order should be present");
852 assert_eq!(order, vec![0, 1, 2, 3]);
853 }
854 #[test]
855 fn test_focus_stack() {
856 let mut fs: FocusStack<&str> = FocusStack::new();
857 fs.focus("a");
858 fs.focus("b");
859 assert_eq!(fs.current(), Some(&"b"));
860 assert_eq!(fs.depth(), 2);
861 fs.blur();
862 assert_eq!(fs.current(), Some(&"a"));
863 }
864}
865#[cfg(test)]
866mod tests_extra_iterators {
867 use super::*;
868 #[test]
869 fn test_window_iterator() {
870 let data = vec![1u32, 2, 3, 4, 5];
871 let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
872 assert_eq!(windows.len(), 3);
873 assert_eq!(windows[0], &[1, 2, 3]);
874 assert_eq!(windows[2], &[3, 4, 5]);
875 }
876 #[test]
877 fn test_non_empty_vec() {
878 let mut nev = NonEmptyVec::singleton(10u32);
879 nev.push(20);
880 nev.push(30);
881 assert_eq!(nev.len(), 3);
882 assert_eq!(*nev.first(), 10);
883 assert_eq!(*nev.last(), 30);
884 }
885}
886#[cfg(test)]
887mod tests_padding2 {
888 use super::*;
889 #[test]
890 fn test_sliding_sum() {
891 let mut ss = SlidingSum::new(3);
892 ss.push(1.0);
893 ss.push(2.0);
894 ss.push(3.0);
895 assert!((ss.sum() - 6.0).abs() < 1e-9);
896 ss.push(4.0);
897 assert!((ss.sum() - 9.0).abs() < 1e-9);
898 assert_eq!(ss.count(), 3);
899 }
900 #[test]
901 fn test_path_buf() {
902 let mut pb = PathBuf::new();
903 pb.push("src");
904 pb.push("main");
905 assert_eq!(pb.as_str(), "src/main");
906 assert_eq!(pb.depth(), 2);
907 pb.pop();
908 assert_eq!(pb.as_str(), "src");
909 }
910 #[test]
911 fn test_string_pool() {
912 let mut pool = StringPool::new();
913 let s = pool.take();
914 assert!(s.is_empty());
915 pool.give("hello".to_string());
916 let s2 = pool.take();
917 assert!(s2.is_empty());
918 assert_eq!(pool.free_count(), 0);
919 }
920 #[test]
921 fn test_transitive_closure() {
922 let mut tc = TransitiveClosure::new(4);
923 tc.add_edge(0, 1);
924 tc.add_edge(1, 2);
925 tc.add_edge(2, 3);
926 assert!(tc.can_reach(0, 3));
927 assert!(!tc.can_reach(3, 0));
928 let r = tc.reachable_from(0);
929 assert_eq!(r.len(), 4);
930 }
931 #[test]
932 fn test_token_bucket() {
933 let mut tb = TokenBucket::new(100, 10);
934 assert_eq!(tb.available(), 100);
935 assert!(tb.try_consume(50));
936 assert_eq!(tb.available(), 50);
937 assert!(!tb.try_consume(60));
938 assert_eq!(tb.capacity(), 100);
939 }
940 #[test]
941 fn test_rewrite_rule_set() {
942 let mut rrs = RewriteRuleSet::new();
943 rrs.add(RewriteRule::unconditional(
944 "beta",
945 "App(Lam(x, b), v)",
946 "b[x:=v]",
947 ));
948 rrs.add(RewriteRule::conditional("comm", "a + b", "b + a"));
949 assert_eq!(rrs.len(), 2);
950 assert_eq!(rrs.unconditional_rules().len(), 1);
951 assert_eq!(rrs.conditional_rules().len(), 1);
952 assert!(rrs.get("beta").is_some());
953 let disp = rrs
954 .get("beta")
955 .expect("element at \'beta\' should exist")
956 .display();
957 assert!(disp.contains("→"));
958 }
959}
960#[cfg(test)]
961mod tests_padding3 {
962 use super::*;
963 #[test]
964 fn test_decision_node() {
965 let tree = DecisionNode::Branch {
966 key: "x".into(),
967 val: "1".into(),
968 yes_branch: Box::new(DecisionNode::Leaf("yes".into())),
969 no_branch: Box::new(DecisionNode::Leaf("no".into())),
970 };
971 let mut ctx = std::collections::HashMap::new();
972 ctx.insert("x".into(), "1".into());
973 assert_eq!(tree.evaluate(&ctx), "yes");
974 ctx.insert("x".into(), "2".into());
975 assert_eq!(tree.evaluate(&ctx), "no");
976 assert_eq!(tree.depth(), 1);
977 }
978 #[test]
979 fn test_flat_substitution() {
980 let mut sub = FlatSubstitution::new();
981 sub.add("foo", "bar");
982 sub.add("baz", "qux");
983 assert_eq!(sub.apply("foo and baz"), "bar and qux");
984 assert_eq!(sub.len(), 2);
985 }
986 #[test]
987 fn test_stopwatch() {
988 let mut sw = Stopwatch::start();
989 sw.split();
990 sw.split();
991 assert_eq!(sw.num_splits(), 2);
992 assert!(sw.elapsed_ms() >= 0.0);
993 for &s in sw.splits() {
994 assert!(s >= 0.0);
995 }
996 }
997 #[test]
998 fn test_either2() {
999 let e: Either2<i32, &str> = Either2::First(42);
1000 assert!(e.is_first());
1001 let mapped = e.map_first(|x| x * 2);
1002 assert_eq!(mapped.first(), Some(84));
1003 let e2: Either2<i32, &str> = Either2::Second("hello");
1004 assert!(e2.is_second());
1005 assert_eq!(e2.second(), Some("hello"));
1006 }
1007 #[test]
1008 fn test_write_once() {
1009 let wo: WriteOnce<u32> = WriteOnce::new();
1010 assert!(!wo.is_written());
1011 assert!(wo.write(42));
1012 assert!(!wo.write(99));
1013 assert_eq!(wo.read(), Some(42));
1014 }
1015 #[test]
1016 fn test_sparse_vec() {
1017 let mut sv: SparseVec<i32> = SparseVec::new(100);
1018 sv.set(5, 10);
1019 sv.set(50, 20);
1020 assert_eq!(*sv.get(5), 10);
1021 assert_eq!(*sv.get(50), 20);
1022 assert_eq!(*sv.get(0), 0);
1023 assert_eq!(sv.nnz(), 2);
1024 sv.set(5, 0);
1025 assert_eq!(sv.nnz(), 1);
1026 }
1027 #[test]
1028 fn test_stack_calc() {
1029 let mut calc = StackCalc::new();
1030 calc.push(3);
1031 calc.push(4);
1032 calc.add();
1033 assert_eq!(calc.peek(), Some(7));
1034 calc.push(2);
1035 calc.mul();
1036 assert_eq!(calc.peek(), Some(14));
1037 }
1038}
1039#[cfg(test)]
1040mod tests_final_padding {
1041 use super::*;
1042 #[test]
1043 fn test_min_heap() {
1044 let mut h = MinHeap::new();
1045 h.push(5u32);
1046 h.push(1u32);
1047 h.push(3u32);
1048 assert_eq!(h.peek(), Some(&1));
1049 assert_eq!(h.pop(), Some(1));
1050 assert_eq!(h.pop(), Some(3));
1051 assert_eq!(h.pop(), Some(5));
1052 assert!(h.is_empty());
1053 }
1054 #[test]
1055 fn test_prefix_counter() {
1056 let mut pc = PrefixCounter::new();
1057 pc.record("hello");
1058 pc.record("help");
1059 pc.record("world");
1060 assert_eq!(pc.count_with_prefix("hel"), 2);
1061 assert_eq!(pc.count_with_prefix("wor"), 1);
1062 assert_eq!(pc.count_with_prefix("xyz"), 0);
1063 }
1064 #[test]
1065 fn test_fixture() {
1066 let mut f = Fixture::new();
1067 f.set("key1", "val1");
1068 f.set("key2", "val2");
1069 assert_eq!(f.get("key1"), Some("val1"));
1070 assert_eq!(f.get("key3"), None);
1071 assert_eq!(f.len(), 2);
1072 }
1073}