oxilean_kernel/reduce/types.rs
1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::expr_util::{get_app_arg_nodes, get_app_args, get_app_fn, mk_app, mk_app_nodes};
7use crate::instantiate::instantiate_type_lparams;
8use crate::subst::{instantiate, instantiate_rev_nodes};
9use crate::Node;
10use crate::{Environment, Expr, Literal, Name};
11use std::collections::HashMap;
12use std::rc::Rc;
13
14/// A tagged union for representing a simple two-case discriminated union.
15#[allow(dead_code)]
16pub enum Either2<A, B> {
17 /// The first alternative.
18 First(A),
19 /// The second alternative.
20 Second(B),
21}
22#[allow(dead_code)]
23impl<A, B> Either2<A, B> {
24 /// Returns `true` if this is the first alternative.
25 pub fn is_first(&self) -> bool {
26 matches!(self, Either2::First(_))
27 }
28 /// Returns `true` if this is the second alternative.
29 pub fn is_second(&self) -> bool {
30 matches!(self, Either2::Second(_))
31 }
32 /// Returns the first value if present.
33 pub fn first(self) -> Option<A> {
34 match self {
35 Either2::First(a) => Some(a),
36 _ => None,
37 }
38 }
39 /// Returns the second value if present.
40 pub fn second(self) -> Option<B> {
41 match self {
42 Either2::Second(b) => Some(b),
43 _ => None,
44 }
45 }
46 /// Maps over the first alternative.
47 pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
48 match self {
49 Either2::First(a) => Either2::First(f(a)),
50 Either2::Second(b) => Either2::Second(b),
51 }
52 }
53}
54/// A generic counter that tracks min/max/sum for statistical summaries.
55#[allow(dead_code)]
56pub struct StatSummary {
57 count: u64,
58 sum: f64,
59 min: f64,
60 max: f64,
61}
62#[allow(dead_code)]
63impl StatSummary {
64 /// Creates an empty summary.
65 pub fn new() -> Self {
66 Self {
67 count: 0,
68 sum: 0.0,
69 min: f64::INFINITY,
70 max: f64::NEG_INFINITY,
71 }
72 }
73 /// Records a sample.
74 pub fn record(&mut self, val: f64) {
75 self.count += 1;
76 self.sum += val;
77 if val < self.min {
78 self.min = val;
79 }
80 if val > self.max {
81 self.max = val;
82 }
83 }
84 /// Returns the mean, or `None` if no samples.
85 pub fn mean(&self) -> Option<f64> {
86 if self.count == 0 {
87 None
88 } else {
89 Some(self.sum / self.count as f64)
90 }
91 }
92 /// Returns the minimum, or `None` if no samples.
93 pub fn min(&self) -> Option<f64> {
94 if self.count == 0 {
95 None
96 } else {
97 Some(self.min)
98 }
99 }
100 /// Returns the maximum, or `None` if no samples.
101 pub fn max(&self) -> Option<f64> {
102 if self.count == 0 {
103 None
104 } else {
105 Some(self.max)
106 }
107 }
108 /// Returns the count of recorded samples.
109 pub fn count(&self) -> u64 {
110 self.count
111 }
112}
113/// A token bucket rate limiter.
114#[allow(dead_code)]
115pub struct TokenBucket {
116 capacity: u64,
117 tokens: u64,
118 refill_per_ms: u64,
119 last_refill: crate::wall_clock::Instant,
120}
121#[allow(dead_code)]
122impl TokenBucket {
123 /// Creates a new token bucket.
124 pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
125 Self {
126 capacity,
127 tokens: capacity,
128 refill_per_ms,
129 last_refill: crate::wall_clock::Instant::now(),
130 }
131 }
132 /// Attempts to consume `n` tokens. Returns `true` on success.
133 pub fn try_consume(&mut self, n: u64) -> bool {
134 self.refill();
135 if self.tokens >= n {
136 self.tokens -= n;
137 true
138 } else {
139 false
140 }
141 }
142 fn refill(&mut self) {
143 let now = crate::wall_clock::Instant::now();
144 let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
145 if elapsed_ms > 0 {
146 let new_tokens = elapsed_ms * self.refill_per_ms;
147 self.tokens = (self.tokens + new_tokens).min(self.capacity);
148 self.last_refill = now;
149 }
150 }
151 /// Returns the number of currently available tokens.
152 pub fn available(&self) -> u64 {
153 self.tokens
154 }
155 /// Returns the bucket capacity.
156 pub fn capacity(&self) -> u64 {
157 self.capacity
158 }
159}
160/// A label set for a graph node.
161#[allow(dead_code)]
162pub struct LabelSet {
163 labels: Vec<String>,
164}
165#[allow(dead_code)]
166impl LabelSet {
167 /// Creates a new empty label set.
168 pub fn new() -> Self {
169 Self { labels: Vec::new() }
170 }
171 /// Adds a label (deduplicates).
172 pub fn add(&mut self, label: impl Into<String>) {
173 let s = label.into();
174 if !self.labels.contains(&s) {
175 self.labels.push(s);
176 }
177 }
178 /// Returns `true` if `label` is present.
179 pub fn has(&self, label: &str) -> bool {
180 self.labels.iter().any(|l| l == label)
181 }
182 /// Returns the count of labels.
183 pub fn count(&self) -> usize {
184 self.labels.len()
185 }
186 /// Returns all labels.
187 pub fn all(&self) -> &[String] {
188 &self.labels
189 }
190}
191/// A fixed-size sliding window that computes a running sum.
192#[allow(dead_code)]
193pub struct SlidingSum {
194 window: Vec<f64>,
195 capacity: usize,
196 pos: usize,
197 sum: f64,
198 count: usize,
199}
200#[allow(dead_code)]
201impl SlidingSum {
202 /// Creates a sliding sum with the given window size.
203 pub fn new(capacity: usize) -> Self {
204 Self {
205 window: vec![0.0; capacity],
206 capacity,
207 pos: 0,
208 sum: 0.0,
209 count: 0,
210 }
211 }
212 /// Adds a value to the window, removing the oldest if full.
213 pub fn push(&mut self, val: f64) {
214 let oldest = self.window[self.pos];
215 self.sum -= oldest;
216 self.sum += val;
217 self.window[self.pos] = val;
218 self.pos = (self.pos + 1) % self.capacity;
219 if self.count < self.capacity {
220 self.count += 1;
221 }
222 }
223 /// Returns the current window sum.
224 pub fn sum(&self) -> f64 {
225 self.sum
226 }
227 /// Returns the window mean, or `None` if empty.
228 pub fn mean(&self) -> Option<f64> {
229 if self.count == 0 {
230 None
231 } else {
232 Some(self.sum / self.count as f64)
233 }
234 }
235 /// Returns the current window size (number of valid elements).
236 pub fn count(&self) -> usize {
237 self.count
238 }
239}
240/// A simple stack-based calculator for arithmetic expressions.
241#[allow(dead_code)]
242pub struct StackCalc {
243 stack: Vec<i64>,
244}
245#[allow(dead_code)]
246impl StackCalc {
247 /// Creates a new empty calculator.
248 pub fn new() -> Self {
249 Self { stack: Vec::new() }
250 }
251 /// Pushes an integer literal.
252 pub fn push(&mut self, n: i64) {
253 self.stack.push(n);
254 }
255 /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
256 /// returns it. Returns `None`, leaving the stack unchanged, if it holds
257 /// fewer than two values.
258 fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
259 let (rest_len, a, b) = match self.stack[..] {
260 [ref rest @ .., a, b] => (rest.len(), a, b),
261 _ => return None,
262 };
263 let value = op(a, b);
264 self.stack.truncate(rest_len);
265 self.stack.push(value);
266 Some(value)
267 }
268 /// Adds the top two values and returns the sum, which replaces them.
269 /// Returns `None`, leaving the stack unchanged, if it holds fewer than
270 /// two values.
271 pub fn add(&mut self) -> Option<i64> {
272 self.apply_binary(|a, b| a + b)
273 }
274 /// Subtracts top from second and returns the difference, which replaces
275 /// them. Returns `None`, leaving the stack unchanged, if it holds fewer
276 /// than two values.
277 pub fn sub(&mut self) -> Option<i64> {
278 self.apply_binary(|a, b| a - b)
279 }
280 /// Multiplies the top two values and returns the product, which replaces
281 /// them. Returns `None`, leaving the stack unchanged, if it holds fewer
282 /// than two values.
283 pub fn mul(&mut self) -> Option<i64> {
284 self.apply_binary(|a, b| a * b)
285 }
286 /// Peeks the top value.
287 pub fn peek(&self) -> Option<i64> {
288 self.stack.last().copied()
289 }
290 /// Returns the stack depth.
291 pub fn depth(&self) -> usize {
292 self.stack.len()
293 }
294}
295/// A simple decision tree node for rule dispatching.
296#[allow(dead_code)]
297#[allow(missing_docs)]
298pub enum DecisionNode {
299 /// A leaf with an action string.
300 Leaf(String),
301 /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
302 Branch {
303 key: String,
304 val: String,
305 yes_branch: Box<DecisionNode>,
306 no_branch: Box<DecisionNode>,
307 },
308}
309#[allow(dead_code)]
310impl DecisionNode {
311 /// Evaluates the decision tree with the given context.
312 pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
313 match self {
314 DecisionNode::Leaf(action) => action.as_str(),
315 DecisionNode::Branch {
316 key,
317 val,
318 yes_branch,
319 no_branch,
320 } => {
321 let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
322 if actual == val.as_str() {
323 yes_branch.evaluate(ctx)
324 } else {
325 no_branch.evaluate(ctx)
326 }
327 }
328 }
329 }
330 /// Returns the depth of the decision tree.
331 pub fn depth(&self) -> usize {
332 match self {
333 DecisionNode::Leaf(_) => 0,
334 DecisionNode::Branch {
335 yes_branch,
336 no_branch,
337 ..
338 } => 1 + yes_branch.depth().max(no_branch.depth()),
339 }
340 }
341}
342/// A simple key-value store backed by a sorted Vec for small maps.
343#[allow(dead_code)]
344pub struct SmallMap<K: Ord + Clone, V: Clone> {
345 entries: Vec<(K, V)>,
346}
347#[allow(dead_code)]
348impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
349 /// Creates a new empty small map.
350 pub fn new() -> Self {
351 Self {
352 entries: Vec::new(),
353 }
354 }
355 /// Inserts or replaces the value for `key`.
356 pub fn insert(&mut self, key: K, val: V) {
357 match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
358 Ok(i) => self.entries[i].1 = val,
359 Err(i) => self.entries.insert(i, (key, val)),
360 }
361 }
362 /// Returns the value for `key`, or `None`.
363 pub fn get(&self, key: &K) -> Option<&V> {
364 self.entries
365 .binary_search_by_key(&key, |(k, _)| k)
366 .ok()
367 .map(|i| &self.entries[i].1)
368 }
369 /// Returns the number of entries.
370 pub fn len(&self) -> usize {
371 self.entries.len()
372 }
373 /// Returns `true` if empty.
374 pub fn is_empty(&self) -> bool {
375 self.entries.is_empty()
376 }
377 /// Returns all keys.
378 pub fn keys(&self) -> Vec<&K> {
379 self.entries.iter().map(|(k, _)| k).collect()
380 }
381 /// Returns all values.
382 pub fn values(&self) -> Vec<&V> {
383 self.entries.iter().map(|(_, v)| v).collect()
384 }
385}
386/// Named reduction rules for tracing.
387#[allow(dead_code)]
388#[derive(Debug, Clone, PartialEq, Eq)]
389pub enum ReductionRule {
390 /// Beta reduction: (λx.body) arg → body[arg/x].
391 Beta,
392 /// Delta reduction: unfold a definition.
393 Delta,
394 /// Zeta reduction: let x := v in body → body[v/x].
395 Zeta,
396 /// Iota reduction: recursor application.
397 Iota,
398 /// Projection reduction.
399 Proj,
400 /// Quotient reduction.
401 Quot,
402 /// Nat literal operation.
403 NatLit,
404 /// String literal operation.
405 StrLit,
406 /// No reduction was possible.
407 None,
408}
409/// A window iterator that yields overlapping windows of size `n`.
410#[allow(dead_code)]
411pub struct WindowIterator<'a, T> {
412 pub(super) data: &'a [T],
413 pub(super) pos: usize,
414 pub(super) window: usize,
415}
416#[allow(dead_code)]
417impl<'a, T> WindowIterator<'a, T> {
418 /// Creates a new window iterator.
419 pub fn new(data: &'a [T], window: usize) -> Self {
420 Self {
421 data,
422 pos: 0,
423 window,
424 }
425 }
426}
427/// A sparse vector: stores only non-default elements.
428#[allow(dead_code)]
429pub struct SparseVec<T: Default + Clone + PartialEq> {
430 entries: std::collections::HashMap<usize, T>,
431 default_: T,
432 logical_len: usize,
433}
434#[allow(dead_code)]
435impl<T: Default + Clone + PartialEq> SparseVec<T> {
436 /// Creates a new sparse vector with logical length `len`.
437 pub fn new(len: usize) -> Self {
438 Self {
439 entries: std::collections::HashMap::new(),
440 default_: T::default(),
441 logical_len: len,
442 }
443 }
444 /// Sets element at `idx`.
445 pub fn set(&mut self, idx: usize, val: T) {
446 if val == self.default_ {
447 self.entries.remove(&idx);
448 } else {
449 self.entries.insert(idx, val);
450 }
451 }
452 /// Gets element at `idx`.
453 pub fn get(&self, idx: usize) -> &T {
454 self.entries.get(&idx).unwrap_or(&self.default_)
455 }
456 /// Returns the logical length.
457 pub fn len(&self) -> usize {
458 self.logical_len
459 }
460 /// Returns whether the collection is empty.
461 pub fn is_empty(&self) -> bool {
462 self.len() == 0
463 }
464 /// Returns the number of non-default elements.
465 pub fn nnz(&self) -> usize {
466 self.entries.len()
467 }
468}
469/// A simple directed acyclic graph.
470#[allow(dead_code)]
471pub struct SimpleDag {
472 /// `edges[i]` is the list of direct successors of node `i`.
473 edges: Vec<Vec<usize>>,
474}
475#[allow(dead_code)]
476impl SimpleDag {
477 /// Creates a DAG with `n` nodes and no edges.
478 pub fn new(n: usize) -> Self {
479 Self {
480 edges: vec![Vec::new(); n],
481 }
482 }
483 /// Adds an edge from `from` to `to`.
484 pub fn add_edge(&mut self, from: usize, to: usize) {
485 if from < self.edges.len() {
486 self.edges[from].push(to);
487 }
488 }
489 /// Returns the successors of `node`.
490 pub fn successors(&self, node: usize) -> &[usize] {
491 self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
492 }
493 /// Returns `true` if `from` can reach `to` via DFS.
494 pub fn can_reach(&self, from: usize, to: usize) -> bool {
495 let mut visited = vec![false; self.edges.len()];
496 self.dfs(from, to, &mut visited)
497 }
498 fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
499 if cur == target {
500 return true;
501 }
502 if cur >= visited.len() || visited[cur] {
503 return false;
504 }
505 visited[cur] = true;
506 for &next in self.successors(cur) {
507 if self.dfs(next, target, visited) {
508 return true;
509 }
510 }
511 false
512 }
513 /// Returns the topological order of nodes, or `None` if a cycle is detected.
514 pub fn topological_sort(&self) -> Option<Vec<usize>> {
515 let n = self.edges.len();
516 let mut in_degree = vec![0usize; n];
517 for succs in &self.edges {
518 for &s in succs {
519 if s < n {
520 in_degree[s] += 1;
521 }
522 }
523 }
524 let mut queue: std::collections::VecDeque<usize> =
525 (0..n).filter(|&i| in_degree[i] == 0).collect();
526 let mut order = Vec::new();
527 while let Some(node) = queue.pop_front() {
528 order.push(node);
529 for &s in self.successors(node) {
530 if s < n {
531 in_degree[s] -= 1;
532 if in_degree[s] == 0 {
533 queue.push_back(s);
534 }
535 }
536 }
537 }
538 if order.len() == n {
539 Some(order)
540 } else {
541 None
542 }
543 }
544 /// Returns the number of nodes.
545 pub fn num_nodes(&self) -> usize {
546 self.edges.len()
547 }
548}
549/// A mutable reference stack for tracking the current "focus" in a tree traversal.
550#[allow(dead_code)]
551pub struct FocusStack<T> {
552 items: Vec<T>,
553}
554#[allow(dead_code)]
555impl<T> FocusStack<T> {
556 /// Creates an empty focus stack.
557 pub fn new() -> Self {
558 Self { items: Vec::new() }
559 }
560 /// Focuses on `item`.
561 pub fn focus(&mut self, item: T) {
562 self.items.push(item);
563 }
564 /// Blurs (pops) the current focus.
565 pub fn blur(&mut self) -> Option<T> {
566 self.items.pop()
567 }
568 /// Returns the current focus, or `None`.
569 pub fn current(&self) -> Option<&T> {
570 self.items.last()
571 }
572 /// Returns the focus depth.
573 pub fn depth(&self) -> usize {
574 self.items.len()
575 }
576 /// Returns `true` if there is no current focus.
577 pub fn is_empty(&self) -> bool {
578 self.items.is_empty()
579 }
580}
581/// A versioned record that stores a history of values.
582#[allow(dead_code)]
583pub struct VersionedRecord<T: Clone> {
584 /// Every earlier value, oldest first: version `n` is `earlier[n]`.
585 earlier: Vec<T>,
586 /// The latest value (version `earlier.len()`), declared last so it drops last.
587 current: T,
588}
589#[allow(dead_code)]
590impl<T: Clone> VersionedRecord<T> {
591 /// Creates a new record with an initial value.
592 pub fn new(initial: T) -> Self {
593 Self {
594 earlier: Vec::new(),
595 current: initial,
596 }
597 }
598 /// Updates the record with a new version.
599 pub fn update(&mut self, val: T) {
600 let previous = std::mem::replace(&mut self.current, val);
601 self.earlier.push(previous);
602 }
603 /// Returns the current (latest) value.
604 pub fn current(&self) -> &T {
605 &self.current
606 }
607 /// Returns the value at version `n` (0-indexed), or `None`.
608 pub fn at_version(&self, n: usize) -> Option<&T> {
609 match self.earlier.get(n) {
610 Some(value) => Some(value),
611 None if n == self.earlier.len() => Some(&self.current),
612 None => None,
613 }
614 }
615 /// Returns the version number of the current value.
616 pub fn version(&self) -> usize {
617 self.earlier.len()
618 }
619 /// Returns `true` if more than one version exists.
620 pub fn has_history(&self) -> bool {
621 !self.earlier.is_empty()
622 }
623}
624/// Represents a rewrite rule `lhs → rhs`.
625#[allow(dead_code)]
626#[allow(missing_docs)]
627pub struct RewriteRule {
628 /// The name of the rule.
629 pub name: String,
630 /// A string representation of the LHS pattern.
631 pub lhs: String,
632 /// A string representation of the RHS.
633 pub rhs: String,
634 /// Whether this is a conditional rule (has side conditions).
635 pub conditional: bool,
636}
637#[allow(dead_code)]
638impl RewriteRule {
639 /// Creates an unconditional rewrite rule.
640 pub fn unconditional(
641 name: impl Into<String>,
642 lhs: impl Into<String>,
643 rhs: impl Into<String>,
644 ) -> Self {
645 Self {
646 name: name.into(),
647 lhs: lhs.into(),
648 rhs: rhs.into(),
649 conditional: false,
650 }
651 }
652 /// Creates a conditional rewrite rule.
653 pub fn conditional(
654 name: impl Into<String>,
655 lhs: impl Into<String>,
656 rhs: impl Into<String>,
657 ) -> Self {
658 Self {
659 name: name.into(),
660 lhs: lhs.into(),
661 rhs: rhs.into(),
662 conditional: true,
663 }
664 }
665 /// Returns a textual representation.
666 pub fn display(&self) -> String {
667 format!("{}: {} → {}", self.name, self.lhs, self.rhs)
668 }
669}
670/// A dependency closure builder (transitive closure via BFS).
671#[allow(dead_code)]
672pub struct TransitiveClosure {
673 adj: Vec<Vec<usize>>,
674 n: usize,
675}
676#[allow(dead_code)]
677impl TransitiveClosure {
678 /// Creates a transitive closure builder for `n` nodes.
679 pub fn new(n: usize) -> Self {
680 Self {
681 adj: vec![Vec::new(); n],
682 n,
683 }
684 }
685 /// Adds a direct edge.
686 pub fn add_edge(&mut self, from: usize, to: usize) {
687 if from < self.n {
688 self.adj[from].push(to);
689 }
690 }
691 /// Computes all nodes reachable from `start` (including `start`).
692 pub fn reachable_from(&self, start: usize) -> Vec<usize> {
693 let mut visited = vec![false; self.n];
694 let mut queue = std::collections::VecDeque::new();
695 queue.push_back(start);
696 while let Some(node) = queue.pop_front() {
697 if node >= self.n || visited[node] {
698 continue;
699 }
700 visited[node] = true;
701 for &next in &self.adj[node] {
702 queue.push_back(next);
703 }
704 }
705 (0..self.n).filter(|&i| visited[i]).collect()
706 }
707 /// Returns `true` if `from` can transitively reach `to`.
708 pub fn can_reach(&self, from: usize, to: usize) -> bool {
709 self.reachable_from(from).contains(&to)
710 }
711}
712/// Statistics about reduction performance.
713#[allow(dead_code)]
714#[derive(Debug, Default, Clone)]
715pub struct ReducerStats {
716 /// Number of WHNF calls.
717 pub whnf_calls: u64,
718 /// Number of cache hits.
719 pub cache_hits: u64,
720 /// Number of beta reductions performed.
721 pub beta_count: u64,
722 /// Number of delta reductions performed.
723 pub delta_count: u64,
724 /// Number of iota reductions performed.
725 pub iota_count: u64,
726 /// Number of zeta reductions performed.
727 pub zeta_count: u64,
728 /// Number of nat literal reductions.
729 pub nat_lit_count: u64,
730}
731impl ReducerStats {
732 /// Compute the total reduction count.
733 #[allow(dead_code)]
734 pub fn total_reductions(&self) -> u64 {
735 self.beta_count + self.delta_count + self.iota_count + self.zeta_count + self.nat_lit_count
736 }
737 /// Cache hit rate as a fraction.
738 #[allow(dead_code)]
739 pub fn cache_hit_rate(&self) -> f64 {
740 if self.whnf_calls == 0 {
741 0.0
742 } else {
743 self.cache_hits as f64 / self.whnf_calls as f64
744 }
745 }
746 /// Pretty-print the stats.
747 #[allow(dead_code)]
748 pub fn display(&self) -> String {
749 format!(
750 "whnf:{} hits:{} β:{} δ:{} ι:{} ζ:{} lit:{}",
751 self.whnf_calls,
752 self.cache_hits,
753 self.beta_count,
754 self.delta_count,
755 self.iota_count,
756 self.zeta_count,
757 self.nat_lit_count,
758 )
759 }
760}
761/// A reusable scratch buffer for path computations.
762#[allow(dead_code)]
763pub struct PathBuf {
764 components: Vec<String>,
765}
766#[allow(dead_code)]
767impl PathBuf {
768 /// Creates a new empty path buffer.
769 pub fn new() -> Self {
770 Self {
771 components: Vec::new(),
772 }
773 }
774 /// Pushes a component.
775 pub fn push(&mut self, comp: impl Into<String>) {
776 self.components.push(comp.into());
777 }
778 /// Pops the last component.
779 pub fn pop(&mut self) {
780 self.components.pop();
781 }
782 /// Returns the current path as a `/`-separated string.
783 pub fn as_str(&self) -> String {
784 self.components.join("/")
785 }
786 /// Returns the depth of the path.
787 pub fn depth(&self) -> usize {
788 self.components.len()
789 }
790 /// Clears the path.
791 pub fn clear(&mut self) {
792 self.components.clear();
793 }
794}
795/// A hierarchical configuration tree.
796#[allow(dead_code)]
797pub struct ConfigNode {
798 key: String,
799 value: Option<String>,
800 children: Vec<ConfigNode>,
801}
802#[allow(dead_code)]
803impl ConfigNode {
804 /// Creates a leaf config node with a value.
805 pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
806 Self {
807 key: key.into(),
808 value: Some(value.into()),
809 children: Vec::new(),
810 }
811 }
812 /// Creates a section node with children.
813 pub fn section(key: impl Into<String>) -> Self {
814 Self {
815 key: key.into(),
816 value: None,
817 children: Vec::new(),
818 }
819 }
820 /// Adds a child node.
821 pub fn add_child(&mut self, child: ConfigNode) {
822 self.children.push(child);
823 }
824 /// Returns the key.
825 pub fn key(&self) -> &str {
826 &self.key
827 }
828 /// Returns the value, or `None` for section nodes.
829 pub fn value(&self) -> Option<&str> {
830 self.value.as_deref()
831 }
832 /// Returns the number of children.
833 pub fn num_children(&self) -> usize {
834 self.children.len()
835 }
836 /// Looks up a dot-separated path.
837 pub fn lookup(&self, path: &str) -> Option<&str> {
838 let mut parts = path.splitn(2, '.');
839 let head = parts.next()?;
840 let tail = parts.next();
841 if head != self.key {
842 return None;
843 }
844 match tail {
845 None => self.value.as_deref(),
846 Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
847 }
848 }
849 fn lookup_relative(&self, path: &str) -> Option<&str> {
850 let mut parts = path.splitn(2, '.');
851 let head = parts.next()?;
852 let tail = parts.next();
853 if head != self.key {
854 return None;
855 }
856 match tail {
857 None => self.value.as_deref(),
858 Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
859 }
860 }
861}
862/// A non-empty list (at least one element guaranteed).
863#[allow(dead_code)]
864pub struct NonEmptyVec<T> {
865 head: T,
866 tail: Vec<T>,
867}
868#[allow(dead_code)]
869impl<T> NonEmptyVec<T> {
870 /// Creates a non-empty vec with a single element.
871 pub fn singleton(val: T) -> Self {
872 Self {
873 head: val,
874 tail: Vec::new(),
875 }
876 }
877 /// Pushes an element.
878 pub fn push(&mut self, val: T) {
879 self.tail.push(val);
880 }
881 /// Returns a reference to the first element.
882 pub fn first(&self) -> &T {
883 &self.head
884 }
885 /// Returns a reference to the last element.
886 pub fn last(&self) -> &T {
887 self.tail.last().unwrap_or(&self.head)
888 }
889 /// Returns the number of elements.
890 pub fn len(&self) -> usize {
891 1 + self.tail.len()
892 }
893 /// Returns whether the collection is empty.
894 pub fn is_empty(&self) -> bool {
895 self.len() == 0
896 }
897 /// Returns all elements as a Vec.
898 pub fn to_vec(&self) -> Vec<&T> {
899 let mut v = vec![&self.head];
900 v.extend(self.tail.iter());
901 v
902 }
903}
904/// A pool of reusable string buffers.
905#[allow(dead_code)]
906pub struct StringPool {
907 free: Vec<String>,
908}
909#[allow(dead_code)]
910impl StringPool {
911 /// Creates a new empty string pool.
912 pub fn new() -> Self {
913 Self { free: Vec::new() }
914 }
915 /// Takes a string from the pool (may be empty).
916 pub fn take(&mut self) -> String {
917 self.free.pop().unwrap_or_default()
918 }
919 /// Returns a string to the pool.
920 pub fn give(&mut self, mut s: String) {
921 s.clear();
922 self.free.push(s);
923 }
924 /// Returns the number of free strings in the pool.
925 pub fn free_count(&self) -> usize {
926 self.free.len()
927 }
928}
929/// The outcome of one head-reduction step (see `Reducer::whnf_step`).
930enum StepOutcome {
931 /// The term is in weak head normal form.
932 Done(Expr),
933 /// One step of progress; the caller's loop continues on this term.
934 Continue(Expr),
935}
936/// Reduction context with caching.
937pub struct Reducer {
938 /// WHNF cache: expr -> whnf(expr).
939 ///
940 /// Every result is kept, whatever the size of the term as a tree: the
941 /// terms that most need the cache are the heavily shared ones, a few
942 /// hundred allocations standing for millions of tree nodes, and through
943 /// 0.1.4's first performance pass those were exactly the ones a
944 /// tree-size limit kept out — each visit reduced them again. A kept
945 /// entry holds the term and its result by reference; what that retains
946 /// beyond the term being checked is nodes some reduction built, each of
947 /// which was charged to the per-declaration budget.
948 pub(crate) cache: crate::term_cache::TermCache<Expr>,
949 /// The values of universe-polymorphic constants with their universe
950 /// parameters instantiated, by the constant as it occurs
951 /// (`Const(name, levels)`). A reduction unfolds the same constant at the
952 /// same universes over and over — tens of thousands of times in one
953 /// `omega` certificate — and instantiating rebuilds every part of the
954 /// value that mentions a universe. Keeping the result also means every
955 /// unfolding of a constant is ONE allocation, shared by all of them.
956 /// A function of the environment alone; dropped with `cache`.
957 unfolded: crate::term_cache::TermCache<Expr>,
958 /// Limit on the NESTING depth of reduction (a stack guard, always
959 /// applied) and on the LENGTH of one head-reduction chain (applied
960 /// unless lifted — see [`crate::fuel::set_reduction_cap`]). A reduction
961 /// either limit cuts short is recorded with
962 /// [`crate::fuel::note_reduction_limit`].
963 max_depth: u32,
964 /// Current transparency mode.
965 transparency: TransparencyMode,
966 /// Types of the free variables of the surrounding checker's local
967 /// context that are in scope (mirrored via
968 /// [`Reducer::record_fvar_type`], removed via
969 /// [`Reducer::forget_fvar_type`] when their binder closes). K-like iota
970 /// reduction must *type* the recursor's major premise; a major that
971 /// mentions local FVars (e.g. `Eq.symm … (h : ctorIdx t = 0)` inside
972 /// `Std.IterStep.noConfusionType`) is only typeable with this context —
973 /// Lean's kernel reads the same information from its local context.
974 local_fvar_types: crate::local_types::LocalTypes,
975}
976impl Reducer {
977 /// Create a new reducer with default settings.
978 pub fn new() -> Self {
979 Self {
980 cache: crate::term_cache::TermCache::new(),
981 unfolded: crate::term_cache::TermCache::new(),
982 max_depth: 10000,
983 transparency: TransparencyMode::Default,
984 local_fvar_types: crate::local_types::LocalTypes::new(),
985 }
986 }
987 /// Create a reducer with custom max depth.
988 pub fn with_max_depth(max_depth: u32) -> Self {
989 Self {
990 cache: crate::term_cache::TermCache::new(),
991 unfolded: crate::term_cache::TermCache::new(),
992 max_depth,
993 transparency: TransparencyMode::Default,
994 local_fvar_types: crate::local_types::LocalTypes::new(),
995 }
996 }
997 /// Record the type of a free variable opened by the surrounding checker
998 /// so K-like iota reduction can type majors that mention it.
999 ///
1000 /// `FVarId`s are dispensed monotonically and never reused, so recorded
1001 /// types are stable and cached WHNF results stay sound. Defensively, a
1002 /// re-bind that could make them unsound invalidates the cache: an id in
1003 /// scope given a different type, or an id whose binder was closed
1004 /// ([`Self::forget_fvar_type`]) bound again.
1005 pub fn record_fvar_type(&mut self, id: crate::FVarId, ty: Expr) {
1006 let _stale = self.bind_fvar_type(id, ty);
1007 }
1008 /// [`Self::record_fvar_type`], reporting whether the binding made
1009 /// earlier results stale (see `LocalTypes::bind`): `true` when this
1010 /// reducer dropped its cache, so that an owner keeping caches of its own
1011 /// over the same local context can drop them too.
1012 pub(crate) fn bind_fvar_type(&mut self, id: crate::FVarId, ty: Expr) -> bool {
1013 let stale = self.local_fvar_types.bind(id, ty);
1014 if stale {
1015 self.cache.clear();
1016 }
1017 stale
1018 }
1019 /// The binder of a free variable recorded with
1020 /// [`Self::record_fvar_type`] has been closed: forget its type.
1021 ///
1022 /// Nothing reduced from here on mentions the variable, so nothing needs
1023 /// its type; cached WHNF results that mention it are never looked up
1024 /// again, since its id is not dispensed again (and if a caller does bind
1025 /// it again, [`Self::record_fvar_type`] invalidates the cache). Without
1026 /// this the mirror grows by one type per binder opened, for as long as
1027 /// the reducer lives.
1028 pub fn forget_fvar_type(&mut self, id: crate::FVarId) {
1029 self.local_fvar_types.unbind(id);
1030 }
1031 /// This reducer's mirror of the local context (for tests).
1032 #[cfg(test)]
1033 pub(crate) fn local_types(&self) -> &crate::local_types::LocalTypes {
1034 &self.local_fvar_types
1035 }
1036 /// Set the transparency mode.
1037 pub fn set_transparency(&mut self, mode: TransparencyMode) {
1038 if self.transparency != mode {
1039 self.transparency = mode;
1040 self.cache.clear();
1041 }
1042 }
1043 /// Clear the cache (call between declarations).
1044 pub fn clear_cache(&mut self) {
1045 self.cache.clear();
1046 self.unfolded.clear();
1047 }
1048 /// The value of the constant `head` (`Const(name, levels)`) with its
1049 /// universe parameters instantiated — from the table of unfoldings when
1050 /// it has been asked for before.
1051 fn unfold_value(
1052 &mut self,
1053 head: &Expr,
1054 value: &Expr,
1055 level_params: &[Name],
1056 levels: &[crate::Level],
1057 ) -> Expr {
1058 if level_params.is_empty() || levels.is_empty() {
1059 return value.clone();
1060 }
1061 if let Some(unfolded) = self.unfolded.get(head) {
1062 return unfolded.clone();
1063 }
1064 let unfolded = instantiate_type_lparams(value, level_params, levels);
1065 self.unfolded.insert(head, unfolded.clone());
1066 unfolded
1067 }
1068 /// Reduce an expression to Weak Head Normal Form.
1069 ///
1070 /// This implements all reduction rules except delta (which needs environment).
1071 pub fn whnf(&mut self, expr: &Expr) -> Expr {
1072 self.whnf_with_depth(expr, 0)
1073 }
1074 fn whnf_with_depth(&mut self, expr: &Expr, depth: u32) -> Expr {
1075 if depth > self.max_depth {
1076 return expr.clone();
1077 }
1078 if let Some(cached) = self.cache.get(expr) {
1079 return cached.clone();
1080 }
1081 let result = match expr {
1082 Expr::Sort(_)
1083 | Expr::BVar(_)
1084 | Expr::FVar(_)
1085 | Expr::Const(_, _)
1086 | Expr::Lam(_, _, _, _)
1087 | Expr::Pi(_, _, _, _)
1088 | Expr::Lit(_) => expr.clone(),
1089 Expr::App(f, a) => {
1090 let f_whnf = self.whnf_with_depth(f, depth + 1);
1091 match f_whnf {
1092 Expr::Lam(_, _, _, body) => {
1093 let reduced = instantiate(&body, a);
1094 self.whnf_with_depth(&reduced, depth + 1)
1095 }
1096 _ => Expr::App(Node::new(f_whnf), a.clone()),
1097 }
1098 }
1099 Expr::Let(_, _, val, body) => {
1100 let reduced = instantiate(body, val);
1101 self.whnf_with_depth(&reduced, depth + 1)
1102 }
1103 Expr::Proj(struct_name, idx, struct_expr) => {
1104 let struct_whnf = self.whnf_with_depth(struct_expr, depth + 1);
1105 Expr::Proj(struct_name.clone(), *idx, Node::new(struct_whnf))
1106 }
1107 };
1108 self.cache.insert(expr, result.clone());
1109 result
1110 }
1111 /// Reduce to WHNF with delta-reduction (constant unfolding).
1112 ///
1113 /// This requires environment access to look up definitions.
1114 pub fn whnf_delta<F>(&mut self, expr: &Expr, lookup: &F) -> Expr
1115 where
1116 F: Fn(&crate::Name) -> Option<(Expr, ReducibilityHint)>,
1117 {
1118 self.whnf_delta_with_depth(expr, lookup, 0)
1119 }
1120 fn whnf_delta_with_depth<F>(&mut self, expr: &Expr, lookup: &F, depth: u32) -> Expr
1121 where
1122 F: Fn(&crate::Name) -> Option<(Expr, ReducibilityHint)>,
1123 {
1124 if depth > self.max_depth {
1125 return expr.clone();
1126 }
1127 match expr {
1128 Expr::Sort(_)
1129 | Expr::BVar(_)
1130 | Expr::FVar(_)
1131 | Expr::Lam(_, _, _, _)
1132 | Expr::Pi(_, _, _, _)
1133 | Expr::Lit(_) => expr.clone(),
1134 Expr::Const(name, _levels) => {
1135 if let Some((defn, hint)) = lookup(name) {
1136 if self.should_unfold_hint(hint) {
1137 return self.whnf_delta_with_depth(&defn, lookup, depth + 1);
1138 }
1139 }
1140 expr.clone()
1141 }
1142 Expr::App(f, a) => {
1143 let f_whnf = self.whnf_delta_with_depth(f, lookup, depth + 1);
1144 match f_whnf {
1145 Expr::Lam(_, _, _, body) => {
1146 let reduced = instantiate(&body, a);
1147 self.whnf_delta_with_depth(&reduced, lookup, depth + 1)
1148 }
1149 _ => Expr::App(Node::new(f_whnf), a.clone()),
1150 }
1151 }
1152 Expr::Let(_, _, val, body) => {
1153 let reduced = instantiate(body, val);
1154 self.whnf_delta_with_depth(&reduced, lookup, depth + 1)
1155 }
1156 Expr::Proj(struct_name, idx, struct_expr) => {
1157 let struct_whnf = self.whnf_delta_with_depth(struct_expr, lookup, depth + 1);
1158 Expr::Proj(struct_name.clone(), *idx, Node::new(struct_whnf))
1159 }
1160 }
1161 }
1162 /// Check if a hint should be unfolded under current transparency.
1163 pub(crate) fn should_unfold_hint(&self, hint: ReducibilityHint) -> bool {
1164 match self.transparency {
1165 TransparencyMode::All => true,
1166 TransparencyMode::Default => hint.should_unfold(),
1167 TransparencyMode::Reducible => matches!(hint, ReducibilityHint::Abbrev),
1168 TransparencyMode::Instances => false,
1169 TransparencyMode::None => false,
1170 }
1171 }
1172 /// Full WHNF with environment-aware reductions.
1173 ///
1174 /// Performs: beta, zeta, delta, iota (recursor), projection, nat/string ops.
1175 pub fn whnf_env(&mut self, expr: &Expr, env: &Environment) -> Expr {
1176 self.whnf_env_depth(expr, env, 0)
1177 }
1178 fn whnf_env_depth(&mut self, expr: &Expr, env: &Environment, depth: u32) -> Expr {
1179 // The thread's own limit on nested reductions, where its owner has
1180 // set one (`fuel::set_reduction_nesting_limit`: a small stack): a
1181 // reduction that would nest deeper is not started. As with the guard
1182 // below, the term returned is sound but not a normal form, and the
1183 // event is recorded.
1184 let Some(_nested) = crate::fuel::NestedReduction::enter() else {
1185 crate::fuel::note_reduction_limit();
1186 return expr.clone();
1187 };
1188 self.whnf_env_nested(expr, env, depth)
1189 }
1190 /// [`Self::whnf_env_depth`] once the nested reduction has been entered.
1191 fn whnf_env_nested(&mut self, expr: &Expr, env: &Environment, depth: u32) -> Expr {
1192 // Nesting-depth guard: `depth` counts NESTED whnf calls (an
1193 // application head, a recursor major, a projection's structure),
1194 // each of which holds a native stack frame. Past the limit the term
1195 // is returned as it is — sound (it is definitionally equal to
1196 // itself) but not a normal form, so the event is recorded: a failure
1197 // that follows is a resource limit, not a verdict.
1198 if depth > self.max_depth {
1199 crate::fuel::note_reduction_limit();
1200 return expr.clone();
1201 }
1202 // Resource-fuel degradation (C16): once the per-declaration budget
1203 // is exhausted, reduction stops making progress — a stuck term is
1204 // always sound, and it halts the term growth that exhausted the
1205 // budget. The caller reports the failure as a named resource limit.
1206 if crate::fuel::is_exhausted() {
1207 return expr.clone();
1208 }
1209 if let Some(cached) = self.cache.get(expr) {
1210 return cached.clone();
1211 }
1212 // The head-reduction chain runs as a LOOP, not recursion: each step
1213 // *moves* ownership of the intermediate term into the next step, so
1214 // exactly one intermediate is alive at a time. The old tail
1215 // recursion kept every intermediate of a long delta/iota chain
1216 // alive on the call stack simultaneously — the multi-GiB peak of
1217 // Lean core's `Int.add_mul_ediv_right` (C16) and a large share of
1218 // the deep-stack requirement (C17).
1219 //
1220 // What bounds the LENGTH of the chain. By default the reducer's own
1221 // cap does: the chain's steps are counted from the nesting depth of
1222 // this call, on the same counter and against the same `max_depth`
1223 // (so each step's nested reductions run at the depth the chain has
1224 // reached). Besides ending a runaway chain, the cap is what lets
1225 // definitional equality abandon a reduction that is going nowhere
1226 // and try another route; the verdicts on Lean core and Mathlib
1227 // depend on it, so this accounting is kept exactly. A chain the cap
1228 // cuts short is recorded — the term returned is sound but not a
1229 // normal form, and a failure that follows is not a verdict.
1230 //
1231 // A caller repeating a check that failed that way lifts the cap
1232 // (`fuel::set_reduction_cap(false)`, honoured only under a budget).
1233 // The budget then bounds the chain: every step clones or rebuilds at
1234 // least one node, which charges fuel, and `is_exhausted` polls the
1235 // wall-clock deadline. Steps are no longer charged against the
1236 // nesting depth, and every step runs at the depth of this call — so
1237 // a long but cheap chain (`Nat.rec` iterating tens of thousands of
1238 // times over a small term, as in a `decide` proof) runs to its
1239 // normal form instead of failing a valid proof.
1240 let capped = crate::fuel::reduction_cap_applies();
1241 let mut current: Option<Expr> = None;
1242 let mut steps = depth;
1243 let result = loop {
1244 let cur: &Expr = current.as_ref().unwrap_or(expr);
1245 if crate::fuel::is_exhausted() {
1246 break cur.clone();
1247 }
1248 if capped && steps > self.max_depth {
1249 crate::fuel::note_reduction_limit();
1250 break cur.clone();
1251 }
1252 let step_depth = if capped { steps } else { depth };
1253 match self.whnf_step(cur, env, step_depth) {
1254 StepOutcome::Done(done) => break done,
1255 StepOutcome::Continue(next) => {
1256 steps = steps.saturating_add(1);
1257 // Drop the previous intermediate before the next step.
1258 current = Some(next);
1259 }
1260 }
1261 };
1262 // Only the result of the whole chain is kept, not its intermediate
1263 // terms (see the cache's own documentation for what keeping it
1264 // retains).
1265 self.cache.insert(expr, result.clone());
1266 result
1267 }
1268 /// Perform ONE head-reduction step (zeta, delta, beta, literal folding,
1269 /// iota, quotient iota, projection). `StepOutcome::Continue` hands the
1270 /// reduct back to the caller's loop ([`Self::whnf_env_depth`]);
1271 /// `StepOutcome::Done` is the WHNF. Sub-terms (application heads,
1272 /// recursor majors, projection structs) are still reduced by recursive
1273 /// calls — only the head-reduction *chain* is iterative.
1274 fn whnf_step(&mut self, expr: &Expr, env: &Environment, depth: u32) -> StepOutcome {
1275 match expr {
1276 Expr::Sort(_)
1277 | Expr::BVar(_)
1278 | Expr::FVar(_)
1279 | Expr::Lam(_, _, _, _)
1280 | Expr::Pi(_, _, _, _)
1281 | Expr::Lit(_) => StepOutcome::Done(expr.clone()),
1282 Expr::Let(_, _, val, body) => StepOutcome::Continue(instantiate(body, val)),
1283 Expr::Const(name, levels) => {
1284 if let Some(ci) = env.find(name) {
1285 if let Some(val) = ci.value() {
1286 let hint = ci.reducibility_hint();
1287 if self.should_unfold_hint(hint) {
1288 let unfolded = self.unfold_value(expr, val, ci.level_params(), levels);
1289 return StepOutcome::Continue(unfolded);
1290 }
1291 }
1292 }
1293 StepOutcome::Done(expr.clone())
1294 }
1295 Expr::App(_, _) => {
1296 // Keep the spine arguments BORROWED until a reduction rule
1297 // actually needs owned copies: cloning every argument up
1298 // front (and again in the final `mk_app` rebuild) doubled
1299 // the clone volume of every whnf step on large stuck spines
1300 // (C16, Lean core's `Int.add_mul_ediv_right`).
1301 // The arguments are held as the allocations the spine holds
1302 // them in, so every rebuilt spine and every substitution
1303 // below shares them instead of copying their roots.
1304 let head = get_app_fn(expr);
1305 let args: Vec<&Node> = get_app_arg_nodes(expr);
1306 // The head is reduced ONE STEP AT A TIME while the
1307 // application context is kept, exactly like Lean's whnf loop
1308 // (whnf_core, then the literal extension, then
1309 // `unfold_definition` — which unfolds the head constant of
1310 // the WHOLE application and re-wraps the arguments). Every
1311 // iteration therefore re-checks the Nat/String literal
1312 // extension and iota on the current head, so a definition
1313 // chain that lands on `Nat.mod lit lit` (e.g. through
1314 // `HMod.hMod → instHMod → Mod.mod → Nat.instMod`) folds via
1315 // the extension instead of delta-unfolding `Nat.mod` into
1316 // its structural-recursion body. Reducing the bare head in
1317 // isolation (the old code) unfolded accelerated operations
1318 // before the extension could see the arguments — the
1319 // wrong-REJECT class of `UInt64.toUInt32_mul`,
1320 // `Int64.toInt_minValue` and the Omega constraint lemmas on
1321 // the Init corpus. The extension itself fires only at the
1322 // operation's exact arity, so an over-applied op is left
1323 // stuck and no argument is ever dropped; real exports
1324 // declare `Nat.ble`/`Nat.add`/… as ordinary definitions
1325 // (structural recursion over `Nat`), and the extension
1326 // computes exactly what those definitions compute.
1327 //
1328 // The head loop is bounded like the chain loop of
1329 // `whnf_env_depth`: by the reducer's cap, counted from
1330 // `depth` and recorded when it cuts the loop short, unless
1331 // the cap is lifted — then by the budget (each iteration
1332 // that continues clones an unfolding or steps the head,
1333 // both of which charge fuel).
1334 let capped = crate::fuel::reduction_cap_applies();
1335 let mut head_owned: Option<Expr> = None;
1336 let mut head_steps = depth;
1337 loop {
1338 if crate::fuel::is_exhausted() {
1339 break;
1340 }
1341 if capped && head_steps > self.max_depth {
1342 crate::fuel::note_reduction_limit();
1343 break;
1344 }
1345 head_steps = head_steps.saturating_add(1);
1346 let head_ref: &Expr = head_owned.as_ref().unwrap_or(head);
1347 match head_ref {
1348 Expr::Lam(_, _, _, _) => {
1349 // β: every leading lambda that has an argument,
1350 // in ONE substitution (one walk of the body
1351 // where consuming them one at a time walked it
1352 // once per argument); the arguments left over
1353 // are re-applied. If what the substitution
1354 // yields is itself a lambda, the caller's loop
1355 // comes back here for the next redex.
1356 let mut body: &Expr = head_ref;
1357 let mut taken = 0;
1358 while taken < args.len() {
1359 match body {
1360 Expr::Lam(_, _, _, inner) => {
1361 body = inner;
1362 taken += 1;
1363 }
1364 _ => break,
1365 }
1366 }
1367 let reduct = instantiate_rev_nodes(body, &args[..taken]);
1368 return StepOutcome::Continue(mk_app_nodes(reduct, &args[taken..]));
1369 }
1370 Expr::App(_, _) => {
1371 // The head STEPPED TO AN APPLICATION: a constant
1372 // whose value is a partial application
1373 // (`def f : Quot r → β := Quot.lift g h`), a
1374 // projection that yields one (an instance field
1375 // such as `⟨Quot.map g h⟩`), or a `let` body.
1376 // Its own spine and the outer arguments are ONE
1377 // application, so re-attach the outer arguments
1378 // and hand the whole term back to the caller's
1379 // loop, which re-flattens the spine. Lean's
1380 // `whnf_core` does the same when the head
1381 // changed (`whnf_core(mk_rev_app(f, args))`).
1382 //
1383 // Stepping the bare head in isolation (the old
1384 // code) saw a recursor / `Quot.lift` / `Quot.ind`
1385 // short of its major premise — the major is
1386 // among the OUTER arguments — reported it stuck,
1387 // and returned the application unreduced: the
1388 // wrong-REJECT class of Mathlib's
1389 // `AddCon.addGroup._proof_1`. It also unfolded a
1390 // partially applied accelerated operation
1391 // (`Nat.add 2` awaiting an outer `3`) before the
1392 // literal extension could see both arguments.
1393 //
1394 // `get_app_fn` never returns an application, so
1395 // this arm is only reached after at least one
1396 // head step: it always makes progress.
1397 return StepOutcome::Continue(mk_app_nodes(head_ref.clone(), &args));
1398 }
1399 Expr::Const(name, levels) => {
1400 // 1. Literal folding (Nat/String kernel
1401 // extension), before delta — the Lean order.
1402 if let Some(arity) = lit_op_arity(&name.to_string()) {
1403 if args.len() == arity {
1404 let args_whnf: Vec<Expr> = args
1405 .iter()
1406 .map(|a| self.whnf_env_depth(a, env, depth + 1))
1407 .collect();
1408 if let Some(reduced) = try_reduce_nat_app(head_ref, &args_whnf)
1409 {
1410 let reduced = respell_bool_const_for_env(reduced, env);
1411 return StepOutcome::Continue(reduced);
1412 }
1413 }
1414 }
1415 // 2. Iota (recursor) and quotient reduction.
1416 if let Some(reduced) =
1417 self.try_reduce_recursor(name, levels, &args, env, depth)
1418 {
1419 return StepOutcome::Continue(reduced);
1420 }
1421 if let Some(reduced) = self.try_reduce_quot(name, &args, env, depth) {
1422 return StepOutcome::Continue(reduced);
1423 }
1424 // 3. Delta: unfold the head IN CONTEXT and keep
1425 // stepping (the next iteration β-reduces).
1426 if let Some(ci) = env.find(name) {
1427 if let Some(val) = ci.value() {
1428 if self.should_unfold_hint(ci.reducibility_hint()) {
1429 let unfolded = self.unfold_value(
1430 head_ref,
1431 val,
1432 ci.level_params(),
1433 levels,
1434 );
1435 head_owned = Some(unfolded);
1436 continue;
1437 }
1438 }
1439 }
1440 break; // stuck constant head
1441 }
1442 // Let / Proj / FVar / Sort / Pi / Lit heads: step the
1443 // head itself; `Done` means the head is in WHNF and
1444 // the application is stuck.
1445 _ => match self.whnf_step(head_ref, env, depth + 1) {
1446 StepOutcome::Continue(h2) => {
1447 head_owned = Some(h2);
1448 }
1449 StepOutcome::Done(h2) => {
1450 head_owned = Some(h2);
1451 break;
1452 }
1453 },
1454 }
1455 }
1456 match head_owned {
1457 Some(h) => StepOutcome::Done(mk_app_nodes(h, &args)),
1458 None => StepOutcome::Done(expr.clone()),
1459 }
1460 }
1461 Expr::Proj(struct_name, idx, struct_expr) => {
1462 let mut struct_whnf = self.whnf_env_depth(struct_expr, env, depth + 1);
1463 // Lean v4.32 `reduce_proj_core`: a string-literal struct is
1464 // expanded (`String.ofList l`, then WHNF'd into constructor
1465 // form) before the field is projected — e.g.
1466 // `Proj String 0 (StrLit s)` must reduce, or Lean core's
1467 // `String.toByteArray ""` lemmas go stuck and are wrongly
1468 // rejected.
1469 if let Expr::Lit(Literal::Str(s)) = &struct_whnf {
1470 if let Some(e) = super::iota::str_lit_expansion(s, env) {
1471 struct_whnf = self.whnf_env_depth(&e, env, depth + 1);
1472 }
1473 }
1474 if let Some(reduced) = try_reduce_proj(struct_name, *idx, &struct_whnf, env) {
1475 return StepOutcome::Continue(reduced);
1476 }
1477 StepOutcome::Done(Expr::Proj(
1478 struct_name.clone(),
1479 *idx,
1480 Node::new(struct_whnf),
1481 ))
1482 }
1483 }
1484 }
1485 /// Try to reduce a recursor application (iota-reduction), following the
1486 /// Lean 4 kernel: K-like reduction first (when the recursor carries the
1487 /// K flag), then WHNF of the major premise, then `Nat`/`String` literal
1488 /// expansion, and finally rule application. Rule right-hand sides are
1489 /// closed lambdas applied to `params ++ motives ++ minors ++ fields`;
1490 /// arguments beyond the major premise are re-applied (over-application
1491 /// never drops arguments). See [`super::iota`].
1492 fn try_reduce_recursor(
1493 &mut self,
1494 rec_name: &Name,
1495 rec_levels: &[crate::Level],
1496 args: &[&Node],
1497 env: &Environment,
1498 depth: u32,
1499 ) -> Option<Expr> {
1500 let rec_val = env.get_recursor_val(rec_name)?;
1501 let major_idx = rec_val.get_major_idx() as usize;
1502 if args.len() <= major_idx {
1503 return None;
1504 }
1505 // K-like reduction: replace the major premise by the canonical
1506 // constructor when its *type* has the right shape (Lean does this
1507 // before reducing the major itself).
1508 let major_arg: &Expr = args[major_idx];
1509 let mut major = if rec_val.k {
1510 super::iota::to_ctor_when_k(rec_val, major_arg, env, &self.local_fvar_types)
1511 .unwrap_or_else(|| major_arg.clone())
1512 } else {
1513 major_arg.clone()
1514 };
1515 major = self.whnf_env_depth(&major, env, depth + 1);
1516 // Literal-to-constructor expansion (one layer at a time for Nat), and
1517 // structure-eta expansion of stuck majors of structure-like inductives
1518 // (Lean's `toCtorWhenStruct`) on the fallthrough arm — same order as
1519 // lean4lean's `inductiveReduceRec`.
1520 match &major {
1521 Expr::Lit(Literal::Nat(n)) => {
1522 if let Some(e) = super::iota::nat_lit_to_ctor(n, rec_val) {
1523 major = e;
1524 }
1525 }
1526 Expr::Lit(Literal::Str(s)) => {
1527 // Lean v4.32: `major = whnf(string_lit_to_constructor(major))`
1528 // — the expansion goes through the `String.ofList` *function*
1529 // (the constructor is `ofByteArray` since the UTF-8 String
1530 // refactor), so it must be WHNF'd into constructor form.
1531 if let Some(e) = super::iota::str_lit_expansion(s, env) {
1532 major = self.whnf_env_depth(&e, env, depth + 1);
1533 }
1534 }
1535 _ => {
1536 if let Some(e) =
1537 super::iota::to_ctor_when_struct(rec_val, &major, env, &self.local_fvar_types)
1538 {
1539 major = e;
1540 }
1541 }
1542 }
1543 super::iota::apply_recursor_rule(rec_val, rec_levels, args, major_idx, &major, env)
1544 }
1545 /// Try to reduce a `Quot.lift` / `Quot.ind` application (quotient iota).
1546 ///
1547 /// This mirrors Lean's `quot.cpp` `quot_reduce_rec`:
1548 ///
1549 /// - `Quot.lift {α} {r} {β} (f : α → β) (h) (q : Quot r)` has `mk_pos = 5`
1550 /// with the applied function `f` at position `3`. When the major premise
1551 /// `q` reduces to `Quot.mk α r a`, the result is `f a`.
1552 /// - `Quot.ind {α} {r} {β} (m : ∀ a, β (Quot.mk r a)) (q : Quot r)` has
1553 /// `mk_pos = 4` with the minor premise `m` at position `3`. When `q`
1554 /// reduces to `Quot.mk α r a`, the result is `m a`.
1555 ///
1556 /// The quotiented element is always `mk_args[2]` of the fully-applied
1557 /// `Quot.mk` (`Quot.mk {α} (r) (a)` — three explicit-position arguments).
1558 ///
1559 /// Unlike a naive implementation, this:
1560 /// 1. WHNFs the major premise before inspecting its head, so a `q` that
1561 /// only reduces to `Quot.mk` (e.g. `id (Quot.mk r a)`) still fires; and
1562 /// 2. re-applies any over-application arguments `args[mk_pos + 1 ..]` to
1563 /// the result, so no argument is ever silently dropped (which would be
1564 /// unsound on the def-eq path).
1565 fn try_reduce_quot(
1566 &mut self,
1567 name: &Name,
1568 args: &[&Node],
1569 env: &Environment,
1570 depth: u32,
1571 ) -> Option<Expr> {
1572 use crate::declaration::QuotKind;
1573 let qv = env.get_quotient_val(name)?;
1574 // (arg_pos, mk_pos): position of the applied function / minor premise,
1575 // and position of the major premise (the `Quot.mk` value).
1576 let (arg_pos, mk_pos) = match qv.kind {
1577 QuotKind::Lift => (3usize, 5usize),
1578 QuotKind::Ind => (3usize, 4usize),
1579 QuotKind::Type | QuotKind::Mk => return None,
1580 };
1581 // The major premise must be present.
1582 if args.len() <= mk_pos {
1583 return None;
1584 }
1585 // WHNF the major premise so terms that only *reduce* to `Quot.mk`
1586 // still fire the computation rule.
1587 let major_whnf = self.whnf_env_depth(args[mk_pos], env, depth + 1);
1588 let mk_head = get_app_fn(&major_whnf);
1589 let mk_name = if let Expr::Const(n, _) = mk_head {
1590 n
1591 } else {
1592 return None;
1593 };
1594 // The head must be a registered `Quot.mk`.
1595 let mk_qv = env.get_quotient_val(mk_name)?;
1596 if mk_qv.kind != QuotKind::Mk {
1597 return None;
1598 }
1599 // `Quot.mk {α} (r) (a)` — the quotiented element is `mk_args[2]`.
1600 let mk_args = get_app_arg_nodes(&major_whnf);
1601 if mk_args.len() < 3 {
1602 return None;
1603 }
1604 let a = mk_args[2];
1605 let f = args[arg_pos];
1606 let reduced = Expr::App(f.clone(), a.clone());
1607 // Re-apply any over-application arguments so they are never dropped.
1608 Some(mk_app_nodes(reduced, &args[mk_pos + 1..]))
1609 }
1610 /// Check if two expressions are alpha-equivalent (syntactically equal).
1611 pub fn is_alpha_equiv(e1: &Expr, e2: &Expr) -> bool {
1612 e1 == e2
1613 }
1614}
1615/// A set of rewrite rules.
1616#[allow(dead_code)]
1617pub struct RewriteRuleSet {
1618 rules: Vec<RewriteRule>,
1619}
1620#[allow(dead_code)]
1621impl RewriteRuleSet {
1622 /// Creates an empty rule set.
1623 pub fn new() -> Self {
1624 Self { rules: Vec::new() }
1625 }
1626 /// Adds a rule.
1627 pub fn add(&mut self, rule: RewriteRule) {
1628 self.rules.push(rule);
1629 }
1630 /// Returns the number of rules.
1631 pub fn len(&self) -> usize {
1632 self.rules.len()
1633 }
1634 /// Returns `true` if the set is empty.
1635 pub fn is_empty(&self) -> bool {
1636 self.rules.is_empty()
1637 }
1638 /// Returns all conditional rules.
1639 pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1640 self.rules.iter().filter(|r| r.conditional).collect()
1641 }
1642 /// Returns all unconditional rules.
1643 pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1644 self.rules.iter().filter(|r| !r.conditional).collect()
1645 }
1646 /// Looks up a rule by name.
1647 pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1648 self.rules.iter().find(|r| r.name == name)
1649 }
1650}
1651/// A reduction trace, collecting applied reduction steps.
1652#[allow(dead_code)]
1653#[derive(Debug, Default)]
1654pub struct ReductionTrace {
1655 steps: Vec<ReductionStep>,
1656 enabled: bool,
1657}
1658impl ReductionTrace {
1659 /// Create an enabled trace.
1660 #[allow(dead_code)]
1661 pub fn enabled() -> Self {
1662 Self {
1663 steps: Vec::new(),
1664 enabled: true,
1665 }
1666 }
1667 /// Create a disabled (no-op) trace.
1668 #[allow(dead_code)]
1669 pub fn disabled() -> Self {
1670 Self {
1671 steps: Vec::new(),
1672 enabled: false,
1673 }
1674 }
1675 /// Record a step if tracing is enabled.
1676 #[allow(dead_code)]
1677 pub fn record(&mut self, rule: ReductionRule, before: Expr, after: Expr) {
1678 if self.enabled {
1679 self.steps.push(ReductionStep {
1680 rule,
1681 before,
1682 after,
1683 });
1684 }
1685 }
1686 /// Return the number of recorded steps.
1687 #[allow(dead_code)]
1688 pub fn step_count(&self) -> usize {
1689 self.steps.len()
1690 }
1691 /// Return all recorded steps.
1692 #[allow(dead_code)]
1693 pub fn steps(&self) -> &[ReductionStep] {
1694 &self.steps
1695 }
1696 /// Clear all recorded steps.
1697 #[allow(dead_code)]
1698 pub fn clear(&mut self) {
1699 self.steps.clear();
1700 }
1701 /// Count steps by rule kind.
1702 #[allow(dead_code)]
1703 pub fn count_rule(&self, rule: &ReductionRule) -> usize {
1704 self.steps.iter().filter(|s| &s.rule == rule).count()
1705 }
1706}
1707/// A counter that can measure elapsed time between snapshots.
1708#[allow(dead_code)]
1709pub struct Stopwatch {
1710 start: crate::wall_clock::Instant,
1711 splits: Vec<f64>,
1712}
1713#[allow(dead_code)]
1714impl Stopwatch {
1715 /// Creates and starts a new stopwatch.
1716 pub fn start() -> Self {
1717 Self {
1718 start: crate::wall_clock::Instant::now(),
1719 splits: Vec::new(),
1720 }
1721 }
1722 /// Records a split time (elapsed since start).
1723 pub fn split(&mut self) {
1724 self.splits.push(self.elapsed_ms());
1725 }
1726 /// Returns total elapsed milliseconds since start.
1727 pub fn elapsed_ms(&self) -> f64 {
1728 self.start.elapsed().as_secs_f64() * 1000.0
1729 }
1730 /// Returns all recorded split times.
1731 pub fn splits(&self) -> &[f64] {
1732 &self.splits
1733 }
1734 /// Returns the number of splits.
1735 pub fn num_splits(&self) -> usize {
1736 self.splits.len()
1737 }
1738}
1739/// Transparency mode controls which definitions can be unfolded.
1740#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1741pub enum TransparencyMode {
1742 /// All definitions can be unfolded.
1743 All,
1744 /// Default: unfold non-opaque definitions.
1745 Default,
1746 /// Only unfold definitions marked as reducible/abbrev.
1747 Reducible,
1748 /// Only unfold instance definitions.
1749 Instances,
1750 /// Never unfold anything.
1751 None,
1752}
1753/// A flat list of substitution pairs `(from, to)`.
1754#[allow(dead_code)]
1755pub struct FlatSubstitution {
1756 pairs: Vec<(String, String)>,
1757}
1758#[allow(dead_code)]
1759impl FlatSubstitution {
1760 /// Creates an empty substitution.
1761 pub fn new() -> Self {
1762 Self { pairs: Vec::new() }
1763 }
1764 /// Adds a pair.
1765 pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
1766 self.pairs.push((from.into(), to.into()));
1767 }
1768 /// Applies all substitutions to `s` (leftmost-first order).
1769 pub fn apply(&self, s: &str) -> String {
1770 let mut result = s.to_string();
1771 for (from, to) in &self.pairs {
1772 result = result.replace(from.as_str(), to.as_str());
1773 }
1774 result
1775 }
1776 /// Returns the number of pairs.
1777 pub fn len(&self) -> usize {
1778 self.pairs.len()
1779 }
1780 /// Returns `true` if empty.
1781 pub fn is_empty(&self) -> bool {
1782 self.pairs.is_empty()
1783 }
1784}
1785/// A write-once cell.
1786#[allow(dead_code)]
1787pub struct WriteOnce<T> {
1788 value: std::cell::Cell<Option<T>>,
1789}
1790#[allow(dead_code)]
1791impl<T: Copy> WriteOnce<T> {
1792 /// Creates an empty write-once cell.
1793 pub fn new() -> Self {
1794 Self {
1795 value: std::cell::Cell::new(None),
1796 }
1797 }
1798 /// Writes a value. Returns `false` if already written.
1799 pub fn write(&self, val: T) -> bool {
1800 if self.value.get().is_some() {
1801 return false;
1802 }
1803 self.value.set(Some(val));
1804 true
1805 }
1806 /// Returns the value if written.
1807 pub fn read(&self) -> Option<T> {
1808 self.value.get()
1809 }
1810 /// Returns `true` if the value has been written.
1811 pub fn is_written(&self) -> bool {
1812 self.value.get().is_some()
1813 }
1814}
1815/// A pair of `StatSummary` values tracking before/after a transformation.
1816#[allow(dead_code)]
1817pub struct TransformStat {
1818 before: StatSummary,
1819 after: StatSummary,
1820}
1821#[allow(dead_code)]
1822impl TransformStat {
1823 /// Creates a new transform stat recorder.
1824 pub fn new() -> Self {
1825 Self {
1826 before: StatSummary::new(),
1827 after: StatSummary::new(),
1828 }
1829 }
1830 /// Records a before value.
1831 pub fn record_before(&mut self, v: f64) {
1832 self.before.record(v);
1833 }
1834 /// Records an after value.
1835 pub fn record_after(&mut self, v: f64) {
1836 self.after.record(v);
1837 }
1838 /// Returns the mean reduction ratio (after/before).
1839 pub fn mean_ratio(&self) -> Option<f64> {
1840 let b = self.before.mean()?;
1841 let a = self.after.mean()?;
1842 if b.abs() < f64::EPSILON {
1843 return None;
1844 }
1845 Some(a / b)
1846 }
1847}
1848/// A reduction step record used for debugging and tracing.
1849#[allow(dead_code)]
1850#[derive(Debug, Clone)]
1851pub struct ReductionStep {
1852 /// Which reduction rule was applied.
1853 pub rule: ReductionRule,
1854 /// The expression before reduction.
1855 pub before: Expr,
1856 /// The expression after reduction.
1857 pub after: Expr,
1858}
1859/// A type-erased function pointer with arity tracking.
1860#[allow(dead_code)]
1861pub struct RawFnPtr {
1862 /// The raw function pointer (stored as usize for type erasure).
1863 ptr: usize,
1864 arity: usize,
1865 name: String,
1866}
1867#[allow(dead_code)]
1868impl RawFnPtr {
1869 /// Creates a new raw function pointer descriptor.
1870 pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1871 Self {
1872 ptr,
1873 arity,
1874 name: name.into(),
1875 }
1876 }
1877 /// Returns the arity.
1878 pub fn arity(&self) -> usize {
1879 self.arity
1880 }
1881 /// Returns the name.
1882 pub fn name(&self) -> &str {
1883 &self.name
1884 }
1885 /// Returns the raw pointer value.
1886 pub fn raw(&self) -> usize {
1887 self.ptr
1888 }
1889}
1890/// Reducibility hint controls when definitions are unfolded.
1891#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1892pub enum ReducibilityHint {
1893 /// Never unfold (like theorem proofs)
1894 Opaque,
1895 /// Always unfold first (like notation)
1896 Abbrev,
1897 /// Unfold based on height (lower = unfold first)
1898 Regular(u32),
1899}
1900impl ReducibilityHint {
1901 /// Get the unfolding height (lower = unfold first)
1902 pub fn height(&self) -> u32 {
1903 match self {
1904 ReducibilityHint::Opaque => u32::MAX,
1905 ReducibilityHint::Abbrev => 0,
1906 ReducibilityHint::Regular(h) => *h,
1907 }
1908 }
1909 /// Should this definition be unfolded?
1910 pub fn should_unfold(&self) -> bool {
1911 !matches!(self, ReducibilityHint::Opaque)
1912 }
1913}