Skip to main content

oxilean_codegen/agda_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct AgdaAnalysisCache {
11    pub(super) entries: std::collections::HashMap<String, AgdaCacheEntry>,
12    pub(super) max_size: usize,
13    pub(super) hits: u64,
14    pub(super) misses: u64,
15}
16impl AgdaAnalysisCache {
17    #[allow(dead_code)]
18    pub fn new(max_size: usize) -> Self {
19        AgdaAnalysisCache {
20            entries: std::collections::HashMap::new(),
21            max_size,
22            hits: 0,
23            misses: 0,
24        }
25    }
26    #[allow(dead_code)]
27    pub fn get(&mut self, key: &str) -> Option<&AgdaCacheEntry> {
28        if self.entries.contains_key(key) {
29            self.hits += 1;
30            self.entries.get(key)
31        } else {
32            self.misses += 1;
33            None
34        }
35    }
36    #[allow(dead_code)]
37    pub fn insert(&mut self, key: String, data: Vec<u8>) {
38        if self.entries.len() >= self.max_size {
39            if let Some(oldest) = self.entries.keys().next().cloned() {
40                self.entries.remove(&oldest);
41            }
42        }
43        self.entries.insert(
44            key.clone(),
45            AgdaCacheEntry {
46                key,
47                data,
48                timestamp: 0,
49                valid: true,
50            },
51        );
52    }
53    #[allow(dead_code)]
54    pub fn invalidate(&mut self, key: &str) {
55        if let Some(entry) = self.entries.get_mut(key) {
56            entry.valid = false;
57        }
58    }
59    #[allow(dead_code)]
60    pub fn clear(&mut self) {
61        self.entries.clear();
62    }
63    #[allow(dead_code)]
64    pub fn hit_rate(&self) -> f64 {
65        let total = self.hits + self.misses;
66        if total == 0 {
67            return 0.0;
68        }
69        self.hits as f64 / total as f64
70    }
71    #[allow(dead_code)]
72    pub fn size(&self) -> usize {
73        self.entries.len()
74    }
75}
76/// Top-level `record` declaration.
77#[derive(Debug, Clone, PartialEq)]
78pub struct AgdaRecord {
79    /// Record name
80    pub name: String,
81    /// Parameters
82    pub params: Vec<(String, AgdaExpr)>,
83    /// Universe sort of the record
84    pub universe: AgdaExpr,
85    /// Optional constructor name (default: `mk<Name>`)
86    pub constructor: Option<String>,
87    /// Fields
88    pub fields: Vec<AgdaField>,
89    /// Optional copattern-style definitions
90    pub copattern_defs: Vec<AgdaClause>,
91}
92#[allow(dead_code)]
93#[derive(Debug, Clone)]
94pub struct AgdaDepGraph {
95    pub(super) nodes: Vec<u32>,
96    pub(super) edges: Vec<(u32, u32)>,
97}
98impl AgdaDepGraph {
99    #[allow(dead_code)]
100    pub fn new() -> Self {
101        AgdaDepGraph {
102            nodes: Vec::new(),
103            edges: Vec::new(),
104        }
105    }
106    #[allow(dead_code)]
107    pub fn add_node(&mut self, id: u32) {
108        if !self.nodes.contains(&id) {
109            self.nodes.push(id);
110        }
111    }
112    #[allow(dead_code)]
113    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
114        self.add_node(dep);
115        self.add_node(dependent);
116        self.edges.push((dep, dependent));
117    }
118    #[allow(dead_code)]
119    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
120        self.edges
121            .iter()
122            .filter(|(d, _)| *d == node)
123            .map(|(_, dep)| *dep)
124            .collect()
125    }
126    #[allow(dead_code)]
127    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
128        self.edges
129            .iter()
130            .filter(|(_, dep)| *dep == node)
131            .map(|(d, _)| *d)
132            .collect()
133    }
134    #[allow(dead_code)]
135    pub fn topological_sort(&self) -> Vec<u32> {
136        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
137        for &n in &self.nodes {
138            in_degree.insert(n, 0);
139        }
140        for (_, dep) in &self.edges {
141            *in_degree.entry(*dep).or_insert(0) += 1;
142        }
143        let mut queue: std::collections::VecDeque<u32> = self
144            .nodes
145            .iter()
146            .filter(|&&n| in_degree[&n] == 0)
147            .copied()
148            .collect();
149        let mut result = Vec::new();
150        while let Some(node) = queue.pop_front() {
151            result.push(node);
152            for dep in self.dependents_of(node) {
153                let cnt = in_degree.entry(dep).or_insert(0);
154                *cnt = cnt.saturating_sub(1);
155                if *cnt == 0 {
156                    queue.push_back(dep);
157                }
158            }
159        }
160        result
161    }
162    #[allow(dead_code)]
163    pub fn has_cycle(&self) -> bool {
164        self.topological_sort().len() < self.nodes.len()
165    }
166}
167/// A single constructor in a `data` declaration.
168#[derive(Debug, Clone, PartialEq)]
169pub struct AgdaConstructor {
170    /// Constructor name
171    pub name: String,
172    /// Constructor type (Agda uses GADT-style `ctor : Type`)
173    pub ty: AgdaExpr,
174}
175/// Analysis cache for AgdaX2.
176#[allow(dead_code)]
177#[derive(Debug)]
178pub struct AgdaX2Cache {
179    pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
180    pub(super) cap: usize,
181    pub(super) total_hits: u64,
182    pub(super) total_misses: u64,
183}
184impl AgdaX2Cache {
185    #[allow(dead_code)]
186    pub fn new(cap: usize) -> Self {
187        Self {
188            entries: Vec::new(),
189            cap,
190            total_hits: 0,
191            total_misses: 0,
192        }
193    }
194    #[allow(dead_code)]
195    pub fn get(&mut self, key: u64) -> Option<&[u8]> {
196        for e in self.entries.iter_mut() {
197            if e.0 == key && e.2 {
198                e.3 += 1;
199                self.total_hits += 1;
200                return Some(&e.1);
201            }
202        }
203        self.total_misses += 1;
204        None
205    }
206    #[allow(dead_code)]
207    pub fn put(&mut self, key: u64, data: Vec<u8>) {
208        if self.entries.len() >= self.cap {
209            self.entries.retain(|e| e.2);
210            if self.entries.len() >= self.cap {
211                self.entries.remove(0);
212            }
213        }
214        self.entries.push((key, data, true, 0));
215    }
216    #[allow(dead_code)]
217    pub fn invalidate(&mut self) {
218        for e in self.entries.iter_mut() {
219            e.2 = false;
220        }
221    }
222    #[allow(dead_code)]
223    pub fn hit_rate(&self) -> f64 {
224        let t = self.total_hits + self.total_misses;
225        if t == 0 {
226            0.0
227        } else {
228            self.total_hits as f64 / t as f64
229        }
230    }
231    #[allow(dead_code)]
232    pub fn live_count(&self) -> usize {
233        self.entries.iter().filter(|e| e.2).count()
234    }
235}
236/// Dominator tree for AgdaX2.
237#[allow(dead_code)]
238#[derive(Debug, Clone)]
239pub struct AgdaX2DomTree {
240    pub(super) idom: Vec<Option<usize>>,
241    pub(super) children: Vec<Vec<usize>>,
242    pub(super) depth: Vec<usize>,
243}
244impl AgdaX2DomTree {
245    #[allow(dead_code)]
246    pub fn new(n: usize) -> Self {
247        Self {
248            idom: vec![None; n],
249            children: vec![Vec::new(); n],
250            depth: vec![0; n],
251        }
252    }
253    #[allow(dead_code)]
254    pub fn set_idom(&mut self, node: usize, dom: usize) {
255        if node < self.idom.len() {
256            self.idom[node] = Some(dom);
257            if dom < self.children.len() {
258                self.children[dom].push(node);
259            }
260            self.depth[node] = if dom < self.depth.len() {
261                self.depth[dom] + 1
262            } else {
263                1
264            };
265        }
266    }
267    #[allow(dead_code)]
268    pub fn dominates(&self, a: usize, mut b: usize) -> bool {
269        if a == b {
270            return true;
271        }
272        let n = self.idom.len();
273        for _ in 0..n {
274            match self.idom.get(b).copied().flatten() {
275                None => return false,
276                Some(p) if p == a => return true,
277                Some(p) if p == b => return false,
278                Some(p) => b = p,
279            }
280        }
281        false
282    }
283    #[allow(dead_code)]
284    pub fn children_of(&self, n: usize) -> &[usize] {
285        self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
286    }
287    #[allow(dead_code)]
288    pub fn depth_of(&self, n: usize) -> usize {
289        self.depth.get(n).copied().unwrap_or(0)
290    }
291    #[allow(dead_code)]
292    pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
293        let n = self.idom.len();
294        for _ in 0..(2 * n) {
295            if a == b {
296                return a;
297            }
298            if self.depth_of(a) > self.depth_of(b) {
299                a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
300            } else {
301                b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
302            }
303        }
304        0
305    }
306}
307#[allow(dead_code)]
308#[derive(Debug, Clone)]
309pub struct AgdaCacheEntry {
310    pub key: String,
311    pub data: Vec<u8>,
312    pub timestamp: u64,
313    pub valid: bool,
314}
315#[allow(dead_code)]
316#[derive(Debug, Clone, Default)]
317pub struct AgdaPassStats {
318    pub total_runs: u32,
319    pub successful_runs: u32,
320    pub total_changes: u64,
321    pub time_ms: u64,
322    pub iterations_used: u32,
323}
324impl AgdaPassStats {
325    #[allow(dead_code)]
326    pub fn new() -> Self {
327        Self::default()
328    }
329    #[allow(dead_code)]
330    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
331        self.total_runs += 1;
332        self.successful_runs += 1;
333        self.total_changes += changes;
334        self.time_ms += time_ms;
335        self.iterations_used = iterations;
336    }
337    #[allow(dead_code)]
338    pub fn average_changes_per_run(&self) -> f64 {
339        if self.total_runs == 0 {
340            return 0.0;
341        }
342        self.total_changes as f64 / self.total_runs as f64
343    }
344    #[allow(dead_code)]
345    pub fn success_rate(&self) -> f64 {
346        if self.total_runs == 0 {
347            return 0.0;
348        }
349        self.successful_runs as f64 / self.total_runs as f64
350    }
351    #[allow(dead_code)]
352    pub fn format_summary(&self) -> String {
353        format!(
354            "Runs: {}/{}, Changes: {}, Time: {}ms",
355            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
356        )
357    }
358}
359#[allow(dead_code)]
360#[derive(Debug, Clone)]
361pub struct AgdaPassConfig {
362    pub phase: AgdaPassPhase,
363    pub enabled: bool,
364    pub max_iterations: u32,
365    pub debug_output: bool,
366    pub pass_name: String,
367}
368impl AgdaPassConfig {
369    #[allow(dead_code)]
370    pub fn new(name: impl Into<String>, phase: AgdaPassPhase) -> Self {
371        AgdaPassConfig {
372            phase,
373            enabled: true,
374            max_iterations: 10,
375            debug_output: false,
376            pass_name: name.into(),
377        }
378    }
379    #[allow(dead_code)]
380    pub fn disabled(mut self) -> Self {
381        self.enabled = false;
382        self
383    }
384    #[allow(dead_code)]
385    pub fn with_debug(mut self) -> Self {
386        self.debug_output = true;
387        self
388    }
389    #[allow(dead_code)]
390    pub fn max_iter(mut self, n: u32) -> Self {
391        self.max_iterations = n;
392        self
393    }
394}
395/// Top-level Agda 2 declaration.
396#[derive(Debug, Clone, PartialEq)]
397pub enum AgdaDecl {
398    /// Type signature: `f : T`
399    FuncType { name: String, ty: AgdaExpr },
400    /// Function definition: one or more clauses
401    FuncDef {
402        name: String,
403        clauses: Vec<AgdaClause>,
404    },
405    /// Data declaration
406    DataDecl(AgdaData),
407    /// Record declaration
408    RecordDecl(AgdaRecord),
409    /// Module declaration: `module M where ...`
410    ModuleDecl {
411        name: String,
412        params: Vec<(String, AgdaExpr)>,
413        body: Vec<AgdaDecl>,
414    },
415    /// `import Data.Nat`
416    Import(String),
417    /// `open Data.Nat` (or `open Data.Nat using (...)`)
418    Open(String),
419    /// `variable {A B : Set}`
420    Variable(Vec<(String, AgdaExpr)>),
421    /// `postulate name : T`
422    Postulate(Vec<(String, AgdaExpr)>),
423    /// `{-# BUILTIN ... #-}` pragma
424    Pragma(String),
425    /// Plain comment: `-- text`
426    Comment(String),
427    /// Raw verbatim Agda text (fallback)
428    Raw(String),
429}
430impl AgdaDecl {
431    /// Emit as an Agda source string at the given indentation level.
432    pub fn emit(&self, indent: usize) -> String {
433        let pad = "  ".repeat(indent);
434        match self {
435            AgdaDecl::FuncType { name, ty } => {
436                format!("{}{} : {}", pad, name, ty.emit(indent))
437            }
438            AgdaDecl::FuncDef { name, clauses } => clauses
439                .iter()
440                .map(|c| c.emit_clause(name, indent))
441                .collect::<Vec<_>>()
442                .join("\n"),
443            AgdaDecl::DataDecl(data) => {
444                let ps = emit_agda_params(&data.params, indent);
445                let ps_str = if ps.is_empty() {
446                    String::new()
447                } else {
448                    format!(" {}", ps)
449                };
450                let mut out = format!(
451                    "{}data {}{} : {} where\n",
452                    pad,
453                    data.name,
454                    ps_str,
455                    data.indices.emit(indent)
456                );
457                for ctor in &data.constructors {
458                    out.push_str(&format!(
459                        "{}  {} : {}\n",
460                        pad,
461                        ctor.name,
462                        ctor.ty.emit(indent + 1)
463                    ));
464                }
465                out
466            }
467            AgdaDecl::RecordDecl(rec) => {
468                let ps = emit_agda_params(&rec.params, indent);
469                let ps_str = if ps.is_empty() {
470                    String::new()
471                } else {
472                    format!(" {}", ps)
473                };
474                let mut out = format!(
475                    "{}record {}{} : {} where\n",
476                    pad,
477                    rec.name,
478                    ps_str,
479                    rec.universe.emit(indent)
480                );
481                if let Some(ctor) = &rec.constructor {
482                    out.push_str(&format!("{}  constructor {}\n", pad, ctor));
483                }
484                out.push_str(&format!("{}  field\n", pad));
485                for field in &rec.fields {
486                    out.push_str(&format!(
487                        "{}    {} : {}\n",
488                        pad,
489                        field.name,
490                        field.ty.emit(indent + 2)
491                    ));
492                }
493                for clause in &rec.copattern_defs {
494                    out.push_str(&clause.emit_clause(&rec.name, indent + 1));
495                    out.push('\n');
496                }
497                out
498            }
499            AgdaDecl::ModuleDecl { name, params, body } => {
500                let ps = emit_agda_params(params, indent);
501                let ps_str = if ps.is_empty() {
502                    String::new()
503                } else {
504                    format!(" {}", ps)
505                };
506                let mut out = format!("{}module {}{} where\n", pad, name, ps_str);
507                for decl in body {
508                    out.push_str(&decl.emit(indent + 1));
509                    out.push('\n');
510                }
511                out
512            }
513            AgdaDecl::Import(module) => format!("{}import {}", pad, module),
514            AgdaDecl::Open(module) => format!("{}open {}", pad, module),
515            AgdaDecl::Variable(vars) => {
516                let vs: Vec<String> = vars
517                    .iter()
518                    .map(|(x, ty)| format!("{{{} : {}}}", x, ty.emit(indent)))
519                    .collect();
520                format!("{}variable {}", pad, vs.join(" "))
521            }
522            AgdaDecl::Postulate(sigs) => {
523                let mut out = format!("{}postulate\n", pad);
524                for (name, ty) in sigs {
525                    out.push_str(&format!("{}  {} : {}\n", pad, name, ty.emit(indent + 1)));
526                }
527                out
528            }
529            AgdaDecl::Pragma(text) => format!("{{-# {} #-}}", text),
530            AgdaDecl::Comment(text) => format!("{}-- {}", pad, text),
531            AgdaDecl::Raw(s) => s.clone(),
532        }
533    }
534}
535/// Configuration for AgdaExt passes.
536#[allow(dead_code)]
537#[derive(Debug, Clone)]
538pub struct AgdaExtPassConfig {
539    pub name: String,
540    pub phase: AgdaExtPassPhase,
541    pub enabled: bool,
542    pub max_iterations: usize,
543    pub debug: u32,
544    pub timeout_ms: Option<u64>,
545}
546impl AgdaExtPassConfig {
547    #[allow(dead_code)]
548    pub fn new(name: impl Into<String>) -> Self {
549        Self {
550            name: name.into(),
551            phase: AgdaExtPassPhase::Middle,
552            enabled: true,
553            max_iterations: 100,
554            debug: 0,
555            timeout_ms: None,
556        }
557    }
558    #[allow(dead_code)]
559    pub fn with_phase(mut self, phase: AgdaExtPassPhase) -> Self {
560        self.phase = phase;
561        self
562    }
563    #[allow(dead_code)]
564    pub fn with_max_iter(mut self, n: usize) -> Self {
565        self.max_iterations = n;
566        self
567    }
568    #[allow(dead_code)]
569    pub fn with_debug(mut self, d: u32) -> Self {
570        self.debug = d;
571        self
572    }
573    #[allow(dead_code)]
574    pub fn disabled(mut self) -> Self {
575        self.enabled = false;
576        self
577    }
578    #[allow(dead_code)]
579    pub fn with_timeout(mut self, ms: u64) -> Self {
580        self.timeout_ms = Some(ms);
581        self
582    }
583    #[allow(dead_code)]
584    pub fn is_debug_enabled(&self) -> bool {
585        self.debug > 0
586    }
587}
588/// Statistics for AgdaX2 passes.
589#[allow(dead_code)]
590#[derive(Debug, Clone, Default)]
591pub struct AgdaX2PassStats {
592    pub iterations: usize,
593    pub changed: bool,
594    pub nodes_visited: usize,
595    pub nodes_modified: usize,
596    pub time_ms: u64,
597    pub memory_bytes: usize,
598    pub errors: usize,
599}
600impl AgdaX2PassStats {
601    #[allow(dead_code)]
602    pub fn new() -> Self {
603        Self::default()
604    }
605    #[allow(dead_code)]
606    pub fn visit(&mut self) {
607        self.nodes_visited += 1;
608    }
609    #[allow(dead_code)]
610    pub fn modify(&mut self) {
611        self.nodes_modified += 1;
612        self.changed = true;
613    }
614    #[allow(dead_code)]
615    pub fn iterate(&mut self) {
616        self.iterations += 1;
617    }
618    #[allow(dead_code)]
619    pub fn error(&mut self) {
620        self.errors += 1;
621    }
622    #[allow(dead_code)]
623    pub fn efficiency(&self) -> f64 {
624        if self.nodes_visited == 0 {
625            0.0
626        } else {
627            self.nodes_modified as f64 / self.nodes_visited as f64
628        }
629    }
630    #[allow(dead_code)]
631    pub fn merge(&mut self, o: &AgdaX2PassStats) {
632        self.iterations += o.iterations;
633        self.changed |= o.changed;
634        self.nodes_visited += o.nodes_visited;
635        self.nodes_modified += o.nodes_modified;
636        self.time_ms += o.time_ms;
637        self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
638        self.errors += o.errors;
639    }
640}
641#[allow(dead_code)]
642#[derive(Debug, Clone, PartialEq)]
643pub enum AgdaPassPhase {
644    Analysis,
645    Transformation,
646    Verification,
647    Cleanup,
648}
649impl AgdaPassPhase {
650    #[allow(dead_code)]
651    pub fn name(&self) -> &str {
652        match self {
653            AgdaPassPhase::Analysis => "analysis",
654            AgdaPassPhase::Transformation => "transformation",
655            AgdaPassPhase::Verification => "verification",
656            AgdaPassPhase::Cleanup => "cleanup",
657        }
658    }
659    #[allow(dead_code)]
660    pub fn is_modifying(&self) -> bool {
661        matches!(self, AgdaPassPhase::Transformation | AgdaPassPhase::Cleanup)
662    }
663}
664/// A pattern in an Agda function definition clause.
665#[derive(Debug, Clone, PartialEq)]
666pub enum AgdaPattern {
667    /// Named variable pattern: `n`
668    Var(String),
669    /// Constructor pattern: `(ctor p1 p2)` or `ctor` (nullary)
670    Con(String, Vec<AgdaPattern>),
671    /// Wildcard pattern: `_`
672    Wildcard,
673    /// Literal number: `0`, `42`
674    Num(i64),
675    /// Dot pattern (inaccessible): `.t`
676    Dot(Box<AgdaPattern>),
677    /// Absurd pattern: `()`
678    Absurd,
679    /// As-pattern: `n@p`
680    As(String, Box<AgdaPattern>),
681    /// Implicit argument pattern: `{p}`
682    Implicit(Box<AgdaPattern>),
683}
684/// Worklist for AgdaExt.
685#[allow(dead_code)]
686#[derive(Debug, Clone)]
687pub struct AgdaExtWorklist {
688    pub(super) items: std::collections::VecDeque<usize>,
689    pub(super) present: Vec<bool>,
690}
691impl AgdaExtWorklist {
692    #[allow(dead_code)]
693    pub fn new(capacity: usize) -> Self {
694        Self {
695            items: std::collections::VecDeque::new(),
696            present: vec![false; capacity],
697        }
698    }
699    #[allow(dead_code)]
700    pub fn push(&mut self, id: usize) {
701        if id < self.present.len() && !self.present[id] {
702            self.present[id] = true;
703            self.items.push_back(id);
704        }
705    }
706    #[allow(dead_code)]
707    pub fn push_front(&mut self, id: usize) {
708        if id < self.present.len() && !self.present[id] {
709            self.present[id] = true;
710            self.items.push_front(id);
711        }
712    }
713    #[allow(dead_code)]
714    pub fn pop(&mut self) -> Option<usize> {
715        let id = self.items.pop_front()?;
716        if id < self.present.len() {
717            self.present[id] = false;
718        }
719        Some(id)
720    }
721    #[allow(dead_code)]
722    pub fn is_empty(&self) -> bool {
723        self.items.is_empty()
724    }
725    #[allow(dead_code)]
726    pub fn len(&self) -> usize {
727        self.items.len()
728    }
729    #[allow(dead_code)]
730    pub fn contains(&self, id: usize) -> bool {
731        id < self.present.len() && self.present[id]
732    }
733    #[allow(dead_code)]
734    pub fn drain_all(&mut self) -> Vec<usize> {
735        let v: Vec<usize> = self.items.drain(..).collect();
736        for &id in &v {
737            if id < self.present.len() {
738                self.present[id] = false;
739            }
740        }
741        v
742    }
743}
744#[allow(dead_code)]
745#[derive(Debug, Clone)]
746pub struct AgdaLivenessInfo {
747    pub live_in: Vec<std::collections::HashSet<u32>>,
748    pub live_out: Vec<std::collections::HashSet<u32>>,
749    pub defs: Vec<std::collections::HashSet<u32>>,
750    pub uses: Vec<std::collections::HashSet<u32>>,
751}
752impl AgdaLivenessInfo {
753    #[allow(dead_code)]
754    pub fn new(block_count: usize) -> Self {
755        AgdaLivenessInfo {
756            live_in: vec![std::collections::HashSet::new(); block_count],
757            live_out: vec![std::collections::HashSet::new(); block_count],
758            defs: vec![std::collections::HashSet::new(); block_count],
759            uses: vec![std::collections::HashSet::new(); block_count],
760        }
761    }
762    #[allow(dead_code)]
763    pub fn add_def(&mut self, block: usize, var: u32) {
764        if block < self.defs.len() {
765            self.defs[block].insert(var);
766        }
767    }
768    #[allow(dead_code)]
769    pub fn add_use(&mut self, block: usize, var: u32) {
770        if block < self.uses.len() {
771            self.uses[block].insert(var);
772        }
773    }
774    #[allow(dead_code)]
775    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
776        self.live_in
777            .get(block)
778            .map(|s| s.contains(&var))
779            .unwrap_or(false)
780    }
781    #[allow(dead_code)]
782    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
783        self.live_out
784            .get(block)
785            .map(|s| s.contains(&var))
786            .unwrap_or(false)
787    }
788}
789/// Pass execution phase for AgdaExt.
790#[allow(dead_code)]
791#[derive(Debug, Clone, PartialEq, Eq, Hash)]
792pub enum AgdaExtPassPhase {
793    Early,
794    Middle,
795    Late,
796    Finalize,
797}
798impl AgdaExtPassPhase {
799    #[allow(dead_code)]
800    pub fn is_early(&self) -> bool {
801        matches!(self, Self::Early)
802    }
803    #[allow(dead_code)]
804    pub fn is_middle(&self) -> bool {
805        matches!(self, Self::Middle)
806    }
807    #[allow(dead_code)]
808    pub fn is_late(&self) -> bool {
809        matches!(self, Self::Late)
810    }
811    #[allow(dead_code)]
812    pub fn is_finalize(&self) -> bool {
813        matches!(self, Self::Finalize)
814    }
815    #[allow(dead_code)]
816    pub fn order(&self) -> u32 {
817        match self {
818            Self::Early => 0,
819            Self::Middle => 1,
820            Self::Late => 2,
821            Self::Finalize => 3,
822        }
823    }
824    #[allow(dead_code)]
825    pub fn from_order(n: u32) -> Option<Self> {
826        match n {
827            0 => Some(Self::Early),
828            1 => Some(Self::Middle),
829            2 => Some(Self::Late),
830            3 => Some(Self::Finalize),
831            _ => None,
832        }
833    }
834}
835/// Pass registry for AgdaExt.
836#[allow(dead_code)]
837#[derive(Debug, Default)]
838pub struct AgdaExtPassRegistry {
839    pub(super) configs: Vec<AgdaExtPassConfig>,
840    pub(super) stats: Vec<AgdaExtPassStats>,
841}
842impl AgdaExtPassRegistry {
843    #[allow(dead_code)]
844    pub fn new() -> Self {
845        Self::default()
846    }
847    #[allow(dead_code)]
848    pub fn register(&mut self, c: AgdaExtPassConfig) {
849        self.stats.push(AgdaExtPassStats::new());
850        self.configs.push(c);
851    }
852    #[allow(dead_code)]
853    pub fn len(&self) -> usize {
854        self.configs.len()
855    }
856    #[allow(dead_code)]
857    pub fn is_empty(&self) -> bool {
858        self.configs.is_empty()
859    }
860    #[allow(dead_code)]
861    pub fn get(&self, i: usize) -> Option<&AgdaExtPassConfig> {
862        self.configs.get(i)
863    }
864    #[allow(dead_code)]
865    pub fn get_stats(&self, i: usize) -> Option<&AgdaExtPassStats> {
866        self.stats.get(i)
867    }
868    #[allow(dead_code)]
869    pub fn enabled_passes(&self) -> Vec<&AgdaExtPassConfig> {
870        self.configs.iter().filter(|c| c.enabled).collect()
871    }
872    #[allow(dead_code)]
873    pub fn passes_in_phase(&self, ph: &AgdaExtPassPhase) -> Vec<&AgdaExtPassConfig> {
874        self.configs
875            .iter()
876            .filter(|c| c.enabled && &c.phase == ph)
877            .collect()
878    }
879    #[allow(dead_code)]
880    pub fn total_nodes_visited(&self) -> usize {
881        self.stats.iter().map(|s| s.nodes_visited).sum()
882    }
883    #[allow(dead_code)]
884    pub fn any_changed(&self) -> bool {
885        self.stats.iter().any(|s| s.changed)
886    }
887}
888/// Constant folding helper for AgdaExt.
889#[allow(dead_code)]
890#[derive(Debug, Clone, Default)]
891pub struct AgdaExtConstFolder {
892    pub(super) folds: usize,
893    pub(super) failures: usize,
894    pub(super) enabled: bool,
895}
896impl AgdaExtConstFolder {
897    #[allow(dead_code)]
898    pub fn new() -> Self {
899        Self {
900            folds: 0,
901            failures: 0,
902            enabled: true,
903        }
904    }
905    #[allow(dead_code)]
906    pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
907        self.folds += 1;
908        a.checked_add(b)
909    }
910    #[allow(dead_code)]
911    pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
912        self.folds += 1;
913        a.checked_sub(b)
914    }
915    #[allow(dead_code)]
916    pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
917        self.folds += 1;
918        a.checked_mul(b)
919    }
920    #[allow(dead_code)]
921    pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
922        if b == 0 {
923            self.failures += 1;
924            None
925        } else {
926            self.folds += 1;
927            a.checked_div(b)
928        }
929    }
930    #[allow(dead_code)]
931    pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
932        if b == 0 {
933            self.failures += 1;
934            None
935        } else {
936            self.folds += 1;
937            a.checked_rem(b)
938        }
939    }
940    #[allow(dead_code)]
941    pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
942        self.folds += 1;
943        a.checked_neg()
944    }
945    #[allow(dead_code)]
946    pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
947        if s >= 64 {
948            self.failures += 1;
949            None
950        } else {
951            self.folds += 1;
952            a.checked_shl(s)
953        }
954    }
955    #[allow(dead_code)]
956    pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
957        if s >= 64 {
958            self.failures += 1;
959            None
960        } else {
961            self.folds += 1;
962            a.checked_shr(s)
963        }
964    }
965    #[allow(dead_code)]
966    pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
967        self.folds += 1;
968        a & b
969    }
970    #[allow(dead_code)]
971    pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
972        self.folds += 1;
973        a | b
974    }
975    #[allow(dead_code)]
976    pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
977        self.folds += 1;
978        a ^ b
979    }
980    #[allow(dead_code)]
981    pub fn not_i64(&mut self, a: i64) -> i64 {
982        self.folds += 1;
983        !a
984    }
985    #[allow(dead_code)]
986    pub fn fold_count(&self) -> usize {
987        self.folds
988    }
989    #[allow(dead_code)]
990    pub fn failure_count(&self) -> usize {
991        self.failures
992    }
993    #[allow(dead_code)]
994    pub fn enable(&mut self) {
995        self.enabled = true;
996    }
997    #[allow(dead_code)]
998    pub fn disable(&mut self) {
999        self.enabled = false;
1000    }
1001    #[allow(dead_code)]
1002    pub fn is_enabled(&self) -> bool {
1003        self.enabled
1004    }
1005}
1006#[allow(dead_code)]
1007pub struct AgdaPassRegistry {
1008    pub(super) configs: Vec<AgdaPassConfig>,
1009    pub(super) stats: std::collections::HashMap<String, AgdaPassStats>,
1010}
1011impl AgdaPassRegistry {
1012    #[allow(dead_code)]
1013    pub fn new() -> Self {
1014        AgdaPassRegistry {
1015            configs: Vec::new(),
1016            stats: std::collections::HashMap::new(),
1017        }
1018    }
1019    #[allow(dead_code)]
1020    pub fn register(&mut self, config: AgdaPassConfig) {
1021        self.stats
1022            .insert(config.pass_name.clone(), AgdaPassStats::new());
1023        self.configs.push(config);
1024    }
1025    #[allow(dead_code)]
1026    pub fn enabled_passes(&self) -> Vec<&AgdaPassConfig> {
1027        self.configs.iter().filter(|c| c.enabled).collect()
1028    }
1029    #[allow(dead_code)]
1030    pub fn get_stats(&self, name: &str) -> Option<&AgdaPassStats> {
1031        self.stats.get(name)
1032    }
1033    #[allow(dead_code)]
1034    pub fn total_passes(&self) -> usize {
1035        self.configs.len()
1036    }
1037    #[allow(dead_code)]
1038    pub fn enabled_count(&self) -> usize {
1039        self.enabled_passes().len()
1040    }
1041    #[allow(dead_code)]
1042    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
1043        if let Some(stats) = self.stats.get_mut(name) {
1044            stats.record_run(changes, time_ms, iter);
1045        }
1046    }
1047}
1048/// A single function definition clause.
1049/// `f p1 p2 = rhs (where decls...)`
1050#[derive(Debug, Clone, PartialEq)]
1051pub struct AgdaClause {
1052    /// Argument patterns
1053    pub patterns: Vec<AgdaPattern>,
1054    /// Right-hand side (`None` for absurd clauses)
1055    pub rhs: Option<AgdaExpr>,
1056    /// Optional `where` declarations
1057    pub where_decls: Vec<AgdaDecl>,
1058}
1059impl AgdaClause {
1060    /// Emit pattern list (space-separated).
1061    pub fn emit_patterns(&self) -> String {
1062        self.patterns
1063            .iter()
1064            .map(|p| p.to_string())
1065            .collect::<Vec<_>>()
1066            .join(" ")
1067    }
1068    /// Emit a full clause line: `func_name patterns = rhs`
1069    pub fn emit_clause(&self, func_name: &str, indent: usize) -> String {
1070        let pad = "  ".repeat(indent);
1071        let pats = self.emit_patterns();
1072        let lhs = if pats.is_empty() {
1073            func_name.to_string()
1074        } else {
1075            format!("{} {}", func_name, pats)
1076        };
1077        match &self.rhs {
1078            None => format!("{}{}", pad, lhs),
1079            Some(rhs) => {
1080                let mut out = format!("{}{} = {}", pad, lhs, rhs.emit(indent));
1081                if !self.where_decls.is_empty() {
1082                    out.push_str(&format!("\n{}  where", pad));
1083                    for w in &self.where_decls {
1084                        for line in w.emit(indent + 2).lines() {
1085                            out.push_str(&format!("\n{}  {}", pad, line));
1086                        }
1087                    }
1088                }
1089                out
1090            }
1091        }
1092    }
1093}
1094/// Dependency graph for AgdaExt.
1095#[allow(dead_code)]
1096#[derive(Debug, Clone)]
1097pub struct AgdaExtDepGraph {
1098    pub(super) n: usize,
1099    pub(super) adj: Vec<Vec<usize>>,
1100    pub(super) rev: Vec<Vec<usize>>,
1101    pub(super) edge_count: usize,
1102}
1103impl AgdaExtDepGraph {
1104    #[allow(dead_code)]
1105    pub fn new(n: usize) -> Self {
1106        Self {
1107            n,
1108            adj: vec![Vec::new(); n],
1109            rev: vec![Vec::new(); n],
1110            edge_count: 0,
1111        }
1112    }
1113    #[allow(dead_code)]
1114    pub fn add_edge(&mut self, from: usize, to: usize) {
1115        if from < self.n && to < self.n {
1116            if !self.adj[from].contains(&to) {
1117                self.adj[from].push(to);
1118                self.rev[to].push(from);
1119                self.edge_count += 1;
1120            }
1121        }
1122    }
1123    #[allow(dead_code)]
1124    pub fn succs(&self, n: usize) -> &[usize] {
1125        self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1126    }
1127    #[allow(dead_code)]
1128    pub fn preds(&self, n: usize) -> &[usize] {
1129        self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1130    }
1131    #[allow(dead_code)]
1132    pub fn topo_sort(&self) -> Option<Vec<usize>> {
1133        let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
1134        let mut q: std::collections::VecDeque<usize> =
1135            (0..self.n).filter(|&i| deg[i] == 0).collect();
1136        let mut out = Vec::with_capacity(self.n);
1137        while let Some(u) = q.pop_front() {
1138            out.push(u);
1139            for &v in &self.adj[u] {
1140                deg[v] -= 1;
1141                if deg[v] == 0 {
1142                    q.push_back(v);
1143                }
1144            }
1145        }
1146        if out.len() == self.n {
1147            Some(out)
1148        } else {
1149            None
1150        }
1151    }
1152    #[allow(dead_code)]
1153    pub fn has_cycle(&self) -> bool {
1154        self.topo_sort().is_none()
1155    }
1156    #[allow(dead_code)]
1157    pub fn reachable(&self, start: usize) -> Vec<usize> {
1158        let mut vis = vec![false; self.n];
1159        let mut stk = vec![start];
1160        let mut out = Vec::new();
1161        while let Some(u) = stk.pop() {
1162            if u < self.n && !vis[u] {
1163                vis[u] = true;
1164                out.push(u);
1165                for &v in &self.adj[u] {
1166                    if !vis[v] {
1167                        stk.push(v);
1168                    }
1169                }
1170            }
1171        }
1172        out
1173    }
1174    #[allow(dead_code)]
1175    pub fn scc(&self) -> Vec<Vec<usize>> {
1176        let mut visited = vec![false; self.n];
1177        let mut order = Vec::new();
1178        for i in 0..self.n {
1179            if !visited[i] {
1180                let mut stk = vec![(i, 0usize)];
1181                while let Some((u, idx)) = stk.last_mut() {
1182                    if !visited[*u] {
1183                        visited[*u] = true;
1184                    }
1185                    if *idx < self.adj[*u].len() {
1186                        let v = self.adj[*u][*idx];
1187                        *idx += 1;
1188                        if !visited[v] {
1189                            stk.push((v, 0));
1190                        }
1191                    } else {
1192                        order.push(*u);
1193                        stk.pop();
1194                    }
1195                }
1196            }
1197        }
1198        let mut comp = vec![usize::MAX; self.n];
1199        let mut components: Vec<Vec<usize>> = Vec::new();
1200        for &start in order.iter().rev() {
1201            if comp[start] == usize::MAX {
1202                let cid = components.len();
1203                let mut component = Vec::new();
1204                let mut stk = vec![start];
1205                while let Some(u) = stk.pop() {
1206                    if comp[u] == usize::MAX {
1207                        comp[u] = cid;
1208                        component.push(u);
1209                        for &v in &self.rev[u] {
1210                            if comp[v] == usize::MAX {
1211                                stk.push(v);
1212                            }
1213                        }
1214                    }
1215                }
1216                components.push(component);
1217            }
1218        }
1219        components
1220    }
1221    #[allow(dead_code)]
1222    pub fn node_count(&self) -> usize {
1223        self.n
1224    }
1225    #[allow(dead_code)]
1226    pub fn edge_count(&self) -> usize {
1227        self.edge_count
1228    }
1229}
1230#[allow(dead_code)]
1231#[derive(Debug, Clone)]
1232pub struct AgdaDominatorTree {
1233    pub idom: Vec<Option<u32>>,
1234    pub dom_children: Vec<Vec<u32>>,
1235    pub dom_depth: Vec<u32>,
1236}
1237impl AgdaDominatorTree {
1238    #[allow(dead_code)]
1239    pub fn new(size: usize) -> Self {
1240        AgdaDominatorTree {
1241            idom: vec![None; size],
1242            dom_children: vec![Vec::new(); size],
1243            dom_depth: vec![0; size],
1244        }
1245    }
1246    #[allow(dead_code)]
1247    pub fn set_idom(&mut self, node: usize, idom: u32) {
1248        self.idom[node] = Some(idom);
1249    }
1250    #[allow(dead_code)]
1251    pub fn dominates(&self, a: usize, b: usize) -> bool {
1252        if a == b {
1253            return true;
1254        }
1255        let mut cur = b;
1256        loop {
1257            match self.idom[cur] {
1258                Some(parent) if parent as usize == a => return true,
1259                Some(parent) if parent as usize == cur => return false,
1260                Some(parent) => cur = parent as usize,
1261                None => return false,
1262            }
1263        }
1264    }
1265    #[allow(dead_code)]
1266    pub fn depth(&self, node: usize) -> u32 {
1267        self.dom_depth.get(node).copied().unwrap_or(0)
1268    }
1269}
1270/// Dominator tree for AgdaExt.
1271#[allow(dead_code)]
1272#[derive(Debug, Clone)]
1273pub struct AgdaExtDomTree {
1274    pub(super) idom: Vec<Option<usize>>,
1275    pub(super) children: Vec<Vec<usize>>,
1276    pub(super) depth: Vec<usize>,
1277}
1278impl AgdaExtDomTree {
1279    #[allow(dead_code)]
1280    pub fn new(n: usize) -> Self {
1281        Self {
1282            idom: vec![None; n],
1283            children: vec![Vec::new(); n],
1284            depth: vec![0; n],
1285        }
1286    }
1287    #[allow(dead_code)]
1288    pub fn set_idom(&mut self, node: usize, dom: usize) {
1289        if node < self.idom.len() {
1290            self.idom[node] = Some(dom);
1291            if dom < self.children.len() {
1292                self.children[dom].push(node);
1293            }
1294            self.depth[node] = if dom < self.depth.len() {
1295                self.depth[dom] + 1
1296            } else {
1297                1
1298            };
1299        }
1300    }
1301    #[allow(dead_code)]
1302    pub fn dominates(&self, a: usize, mut b: usize) -> bool {
1303        if a == b {
1304            return true;
1305        }
1306        let n = self.idom.len();
1307        for _ in 0..n {
1308            match self.idom.get(b).copied().flatten() {
1309                None => return false,
1310                Some(p) if p == a => return true,
1311                Some(p) if p == b => return false,
1312                Some(p) => b = p,
1313            }
1314        }
1315        false
1316    }
1317    #[allow(dead_code)]
1318    pub fn children_of(&self, n: usize) -> &[usize] {
1319        self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1320    }
1321    #[allow(dead_code)]
1322    pub fn depth_of(&self, n: usize) -> usize {
1323        self.depth.get(n).copied().unwrap_or(0)
1324    }
1325    #[allow(dead_code)]
1326    pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
1327        let n = self.idom.len();
1328        for _ in 0..(2 * n) {
1329            if a == b {
1330                return a;
1331            }
1332            if self.depth_of(a) > self.depth_of(b) {
1333                a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
1334            } else {
1335                b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
1336            }
1337        }
1338        0
1339    }
1340}
1341/// A single field in a `record` declaration.
1342#[derive(Debug, Clone, PartialEq)]
1343pub struct AgdaField {
1344    /// Field name
1345    pub name: String,
1346    /// Field type
1347    pub ty: AgdaExpr,
1348}
1349/// Agda 2 expression AST.
1350#[derive(Debug, Clone, PartialEq)]
1351pub enum AgdaExpr {
1352    /// Variable or qualified name: `n`, `List.map`, `Data.Nat.zero`
1353    Var(String),
1354    /// Function application: `f a` (left-associative)
1355    App(Box<AgdaExpr>, Box<AgdaExpr>),
1356    /// Lambda abstraction: `λ x → body` (uses `\` ASCII prefix in code)
1357    Lambda(String, Box<AgdaExpr>),
1358    /// Dependent Pi type: `(x : A) → B` or `{x : A} → B`
1359    /// `None` = non-dependent `A → B`
1360    Pi(Option<String>, Box<AgdaExpr>, Box<AgdaExpr>),
1361    /// Let binding: `let x = rhs in body`
1362    Let(String, Box<AgdaExpr>, Box<AgdaExpr>),
1363    /// With clause extension (for auxiliary matches)
1364    With(Box<AgdaExpr>, Vec<AgdaExpr>),
1365    /// Case expression (implemented via a helper lambda + with)
1366    Case(Box<AgdaExpr>, Vec<AgdaClause>),
1367    /// Universe: `Set`, `Set₁`, `Set n`
1368    Set(Option<u32>),
1369    /// `Prop` universe
1370    Prop,
1371    /// Typed hole: `{! !}` (interactive proof obligation)
1372    Hole,
1373    /// Anonymous/inferred: `_`
1374    Underscore,
1375    /// Integer literal: `0`, `42`
1376    Num(i64),
1377    /// String literal: `"hello"`
1378    Str(String),
1379    /// A qualified module expression: `Module.Name`
1380    Module(String),
1381    /// Implicit argument: `{e}`
1382    Implicit(Box<AgdaExpr>),
1383    /// Tuple / pair: `(a , b)`
1384    Tuple(Vec<AgdaExpr>),
1385    /// Record construction: `record { f = v ; g = w }`
1386    Record(Vec<(String, AgdaExpr)>),
1387    /// Type ascription: `(e : T)`
1388    Ascription(Box<AgdaExpr>, Box<AgdaExpr>),
1389    /// If-then-else: `if b then t else f`
1390    IfThenElse(Box<AgdaExpr>, Box<AgdaExpr>, Box<AgdaExpr>),
1391}
1392impl AgdaExpr {
1393    /// Emit the expression with the given indentation level.
1394    pub fn emit(&self, indent: usize) -> String {
1395        let pad = "  ".repeat(indent);
1396        match self {
1397            AgdaExpr::Var(x) => x.clone(),
1398            AgdaExpr::Num(n) => n.to_string(),
1399            AgdaExpr::Str(s) => format!("\"{}\"", escape_agda_string(s)),
1400            AgdaExpr::Hole => "{! !}".to_string(),
1401            AgdaExpr::Underscore => "_".to_string(),
1402            AgdaExpr::Prop => "Prop".to_string(),
1403            AgdaExpr::Module(m) => m.clone(),
1404            AgdaExpr::Set(None) => "Set".to_string(),
1405            AgdaExpr::Set(Some(0)) => "Set".to_string(),
1406            AgdaExpr::Set(Some(1)) => "Set₁".to_string(),
1407            AgdaExpr::Set(Some(2)) => "Set₂".to_string(),
1408            AgdaExpr::Set(Some(n)) => format!("Set{}", n),
1409            AgdaExpr::App(f, a) => {
1410                let fs = f.emit_func(indent);
1411                let as_ = a.emit_atom(indent);
1412                format!("{} {}", fs, as_)
1413            }
1414            AgdaExpr::Lambda(x, body) => format!("λ {} → {}", x, body.emit(indent)),
1415            AgdaExpr::Pi(None, dom, cod) => {
1416                format!("{} → {}", dom.emit_pi_dom(indent), cod.emit(indent))
1417            }
1418            AgdaExpr::Pi(Some(x), dom, cod) => {
1419                format!("({} : {}) → {}", x, dom.emit(indent), cod.emit(indent))
1420            }
1421            AgdaExpr::Let(x, rhs, body) => {
1422                format!(
1423                    "let {} = {}\n{}in {}",
1424                    x,
1425                    rhs.emit(indent + 1),
1426                    pad,
1427                    body.emit(indent)
1428                )
1429            }
1430            AgdaExpr::With(e, ws) => {
1431                let ws_s: Vec<String> = ws.iter().map(|w| w.emit(indent)).collect();
1432                format!("{} | {}", e.emit(indent), ws_s.join(" | "))
1433            }
1434            AgdaExpr::Case(scrutinee, clauses) => {
1435                let mut out = "(λ _case → {\n".to_string();
1436                for clause in clauses {
1437                    out.push_str(&format!(
1438                        "{}  ; {} → {}\n",
1439                        pad,
1440                        clause.emit_patterns(),
1441                        clause
1442                            .rhs
1443                            .as_ref()
1444                            .map(|r| r.emit(indent + 2))
1445                            .unwrap_or_else(|| "⊥-elim _".to_string())
1446                    ));
1447                }
1448                out.push_str(&format!("{}}} {}", pad, scrutinee.emit(indent)));
1449                out.push(')');
1450                out
1451            }
1452            AgdaExpr::Implicit(e) => format!("{{{}}}", e.emit(indent)),
1453            AgdaExpr::Tuple(elems) => {
1454                let es: Vec<String> = elems.iter().map(|e| e.emit(indent)).collect();
1455                format!("({})", es.join(" , "))
1456            }
1457            AgdaExpr::Record(fields) => {
1458                let fs: Vec<String> = fields
1459                    .iter()
1460                    .map(|(k, v)| format!("{} = {}", k, v.emit(indent)))
1461                    .collect();
1462                format!("record {{ {} }}", fs.join(" ; "))
1463            }
1464            AgdaExpr::Ascription(e, ty) => {
1465                format!("({} : {})", e.emit(indent), ty.emit(indent))
1466            }
1467            AgdaExpr::IfThenElse(cond, then_, else_) => {
1468                format!(
1469                    "if {} then {} else {}",
1470                    cond.emit(indent),
1471                    then_.emit(indent),
1472                    else_.emit(indent)
1473                )
1474            }
1475        }
1476    }
1477    /// Emit in the domain (left) position of a non-dependent Pi (arrow).
1478    /// Only wraps Pi/Lambda/Let/With/Case forms in parens; App is fine without.
1479    pub(super) fn emit_pi_dom(&self, indent: usize) -> String {
1480        match self {
1481            AgdaExpr::Pi(_, _, _) | AgdaExpr::Lambda(_, _) | AgdaExpr::Let(_, _, _) => {
1482                format!("({})", self.emit(indent))
1483            }
1484            _ => self.emit(indent),
1485        }
1486    }
1487    /// Emit in function position (left side of application).
1488    /// Application is left-associative, so `App` nodes do not need parens here.
1489    pub(super) fn emit_func(&self, indent: usize) -> String {
1490        match self {
1491            AgdaExpr::Var(_)
1492            | AgdaExpr::Num(_)
1493            | AgdaExpr::Str(_)
1494            | AgdaExpr::Hole
1495            | AgdaExpr::Underscore
1496            | AgdaExpr::Prop
1497            | AgdaExpr::Set(_)
1498            | AgdaExpr::Module(_)
1499            | AgdaExpr::Tuple(_)
1500            | AgdaExpr::Record(_)
1501            | AgdaExpr::Implicit(_)
1502            | AgdaExpr::App(_, _) => self.emit(indent),
1503            _ => format!("({})", self.emit(indent)),
1504        }
1505    }
1506    /// Emit as an atomic expression (wrap compound forms in parentheses).
1507    pub(super) fn emit_atom(&self, indent: usize) -> String {
1508        match self {
1509            AgdaExpr::Var(_)
1510            | AgdaExpr::Num(_)
1511            | AgdaExpr::Str(_)
1512            | AgdaExpr::Hole
1513            | AgdaExpr::Underscore
1514            | AgdaExpr::Prop
1515            | AgdaExpr::Set(_)
1516            | AgdaExpr::Module(_)
1517            | AgdaExpr::Tuple(_)
1518            | AgdaExpr::Record(_)
1519            | AgdaExpr::Implicit(_) => self.emit(indent),
1520            _ => format!("({})", self.emit(indent)),
1521        }
1522    }
1523}
1524/// Statistics for AgdaExt passes.
1525#[allow(dead_code)]
1526#[derive(Debug, Clone, Default)]
1527pub struct AgdaExtPassStats {
1528    pub iterations: usize,
1529    pub changed: bool,
1530    pub nodes_visited: usize,
1531    pub nodes_modified: usize,
1532    pub time_ms: u64,
1533    pub memory_bytes: usize,
1534    pub errors: usize,
1535}
1536impl AgdaExtPassStats {
1537    #[allow(dead_code)]
1538    pub fn new() -> Self {
1539        Self::default()
1540    }
1541    #[allow(dead_code)]
1542    pub fn visit(&mut self) {
1543        self.nodes_visited += 1;
1544    }
1545    #[allow(dead_code)]
1546    pub fn modify(&mut self) {
1547        self.nodes_modified += 1;
1548        self.changed = true;
1549    }
1550    #[allow(dead_code)]
1551    pub fn iterate(&mut self) {
1552        self.iterations += 1;
1553    }
1554    #[allow(dead_code)]
1555    pub fn error(&mut self) {
1556        self.errors += 1;
1557    }
1558    #[allow(dead_code)]
1559    pub fn efficiency(&self) -> f64 {
1560        if self.nodes_visited == 0 {
1561            0.0
1562        } else {
1563            self.nodes_modified as f64 / self.nodes_visited as f64
1564        }
1565    }
1566    #[allow(dead_code)]
1567    pub fn merge(&mut self, o: &AgdaExtPassStats) {
1568        self.iterations += o.iterations;
1569        self.changed |= o.changed;
1570        self.nodes_visited += o.nodes_visited;
1571        self.nodes_modified += o.nodes_modified;
1572        self.time_ms += o.time_ms;
1573        self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
1574        self.errors += o.errors;
1575    }
1576}
1577/// Configuration for AgdaX2 passes.
1578#[allow(dead_code)]
1579#[derive(Debug, Clone)]
1580pub struct AgdaX2PassConfig {
1581    pub name: String,
1582    pub phase: AgdaX2PassPhase,
1583    pub enabled: bool,
1584    pub max_iterations: usize,
1585    pub debug: u32,
1586    pub timeout_ms: Option<u64>,
1587}
1588impl AgdaX2PassConfig {
1589    #[allow(dead_code)]
1590    pub fn new(name: impl Into<String>) -> Self {
1591        Self {
1592            name: name.into(),
1593            phase: AgdaX2PassPhase::Middle,
1594            enabled: true,
1595            max_iterations: 100,
1596            debug: 0,
1597            timeout_ms: None,
1598        }
1599    }
1600    #[allow(dead_code)]
1601    pub fn with_phase(mut self, phase: AgdaX2PassPhase) -> Self {
1602        self.phase = phase;
1603        self
1604    }
1605    #[allow(dead_code)]
1606    pub fn with_max_iter(mut self, n: usize) -> Self {
1607        self.max_iterations = n;
1608        self
1609    }
1610    #[allow(dead_code)]
1611    pub fn with_debug(mut self, d: u32) -> Self {
1612        self.debug = d;
1613        self
1614    }
1615    #[allow(dead_code)]
1616    pub fn disabled(mut self) -> Self {
1617        self.enabled = false;
1618        self
1619    }
1620    #[allow(dead_code)]
1621    pub fn with_timeout(mut self, ms: u64) -> Self {
1622        self.timeout_ms = Some(ms);
1623        self
1624    }
1625    #[allow(dead_code)]
1626    pub fn is_debug_enabled(&self) -> bool {
1627        self.debug > 0
1628    }
1629}
1630/// Constant folding helper for AgdaX2.
1631#[allow(dead_code)]
1632#[derive(Debug, Clone, Default)]
1633pub struct AgdaX2ConstFolder {
1634    pub(super) folds: usize,
1635    pub(super) failures: usize,
1636    pub(super) enabled: bool,
1637}
1638impl AgdaX2ConstFolder {
1639    #[allow(dead_code)]
1640    pub fn new() -> Self {
1641        Self {
1642            folds: 0,
1643            failures: 0,
1644            enabled: true,
1645        }
1646    }
1647    #[allow(dead_code)]
1648    pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1649        self.folds += 1;
1650        a.checked_add(b)
1651    }
1652    #[allow(dead_code)]
1653    pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1654        self.folds += 1;
1655        a.checked_sub(b)
1656    }
1657    #[allow(dead_code)]
1658    pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1659        self.folds += 1;
1660        a.checked_mul(b)
1661    }
1662    #[allow(dead_code)]
1663    pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1664        if b == 0 {
1665            self.failures += 1;
1666            None
1667        } else {
1668            self.folds += 1;
1669            a.checked_div(b)
1670        }
1671    }
1672    #[allow(dead_code)]
1673    pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1674        if b == 0 {
1675            self.failures += 1;
1676            None
1677        } else {
1678            self.folds += 1;
1679            a.checked_rem(b)
1680        }
1681    }
1682    #[allow(dead_code)]
1683    pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
1684        self.folds += 1;
1685        a.checked_neg()
1686    }
1687    #[allow(dead_code)]
1688    pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1689        if s >= 64 {
1690            self.failures += 1;
1691            None
1692        } else {
1693            self.folds += 1;
1694            a.checked_shl(s)
1695        }
1696    }
1697    #[allow(dead_code)]
1698    pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1699        if s >= 64 {
1700            self.failures += 1;
1701            None
1702        } else {
1703            self.folds += 1;
1704            a.checked_shr(s)
1705        }
1706    }
1707    #[allow(dead_code)]
1708    pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
1709        self.folds += 1;
1710        a & b
1711    }
1712    #[allow(dead_code)]
1713    pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
1714        self.folds += 1;
1715        a | b
1716    }
1717    #[allow(dead_code)]
1718    pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
1719        self.folds += 1;
1720        a ^ b
1721    }
1722    #[allow(dead_code)]
1723    pub fn not_i64(&mut self, a: i64) -> i64 {
1724        self.folds += 1;
1725        !a
1726    }
1727    #[allow(dead_code)]
1728    pub fn fold_count(&self) -> usize {
1729        self.folds
1730    }
1731    #[allow(dead_code)]
1732    pub fn failure_count(&self) -> usize {
1733        self.failures
1734    }
1735    #[allow(dead_code)]
1736    pub fn enable(&mut self) {
1737        self.enabled = true;
1738    }
1739    #[allow(dead_code)]
1740    pub fn disable(&mut self) {
1741        self.enabled = false;
1742    }
1743    #[allow(dead_code)]
1744    pub fn is_enabled(&self) -> bool {
1745        self.enabled
1746    }
1747}
1748/// Dependency graph for AgdaX2.
1749#[allow(dead_code)]
1750#[derive(Debug, Clone)]
1751pub struct AgdaX2DepGraph {
1752    pub(super) n: usize,
1753    pub(super) adj: Vec<Vec<usize>>,
1754    pub(super) rev: Vec<Vec<usize>>,
1755    pub(super) edge_count: usize,
1756}
1757impl AgdaX2DepGraph {
1758    #[allow(dead_code)]
1759    pub fn new(n: usize) -> Self {
1760        Self {
1761            n,
1762            adj: vec![Vec::new(); n],
1763            rev: vec![Vec::new(); n],
1764            edge_count: 0,
1765        }
1766    }
1767    #[allow(dead_code)]
1768    pub fn add_edge(&mut self, from: usize, to: usize) {
1769        if from < self.n && to < self.n {
1770            if !self.adj[from].contains(&to) {
1771                self.adj[from].push(to);
1772                self.rev[to].push(from);
1773                self.edge_count += 1;
1774            }
1775        }
1776    }
1777    #[allow(dead_code)]
1778    pub fn succs(&self, n: usize) -> &[usize] {
1779        self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1780    }
1781    #[allow(dead_code)]
1782    pub fn preds(&self, n: usize) -> &[usize] {
1783        self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1784    }
1785    #[allow(dead_code)]
1786    pub fn topo_sort(&self) -> Option<Vec<usize>> {
1787        let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
1788        let mut q: std::collections::VecDeque<usize> =
1789            (0..self.n).filter(|&i| deg[i] == 0).collect();
1790        let mut out = Vec::with_capacity(self.n);
1791        while let Some(u) = q.pop_front() {
1792            out.push(u);
1793            for &v in &self.adj[u] {
1794                deg[v] -= 1;
1795                if deg[v] == 0 {
1796                    q.push_back(v);
1797                }
1798            }
1799        }
1800        if out.len() == self.n {
1801            Some(out)
1802        } else {
1803            None
1804        }
1805    }
1806    #[allow(dead_code)]
1807    pub fn has_cycle(&self) -> bool {
1808        self.topo_sort().is_none()
1809    }
1810    #[allow(dead_code)]
1811    pub fn reachable(&self, start: usize) -> Vec<usize> {
1812        let mut vis = vec![false; self.n];
1813        let mut stk = vec![start];
1814        let mut out = Vec::new();
1815        while let Some(u) = stk.pop() {
1816            if u < self.n && !vis[u] {
1817                vis[u] = true;
1818                out.push(u);
1819                for &v in &self.adj[u] {
1820                    if !vis[v] {
1821                        stk.push(v);
1822                    }
1823                }
1824            }
1825        }
1826        out
1827    }
1828    #[allow(dead_code)]
1829    pub fn scc(&self) -> Vec<Vec<usize>> {
1830        let mut visited = vec![false; self.n];
1831        let mut order = Vec::new();
1832        for i in 0..self.n {
1833            if !visited[i] {
1834                let mut stk = vec![(i, 0usize)];
1835                while let Some((u, idx)) = stk.last_mut() {
1836                    if !visited[*u] {
1837                        visited[*u] = true;
1838                    }
1839                    if *idx < self.adj[*u].len() {
1840                        let v = self.adj[*u][*idx];
1841                        *idx += 1;
1842                        if !visited[v] {
1843                            stk.push((v, 0));
1844                        }
1845                    } else {
1846                        order.push(*u);
1847                        stk.pop();
1848                    }
1849                }
1850            }
1851        }
1852        let mut comp = vec![usize::MAX; self.n];
1853        let mut components: Vec<Vec<usize>> = Vec::new();
1854        for &start in order.iter().rev() {
1855            if comp[start] == usize::MAX {
1856                let cid = components.len();
1857                let mut component = Vec::new();
1858                let mut stk = vec![start];
1859                while let Some(u) = stk.pop() {
1860                    if comp[u] == usize::MAX {
1861                        comp[u] = cid;
1862                        component.push(u);
1863                        for &v in &self.rev[u] {
1864                            if comp[v] == usize::MAX {
1865                                stk.push(v);
1866                            }
1867                        }
1868                    }
1869                }
1870                components.push(component);
1871            }
1872        }
1873        components
1874    }
1875    #[allow(dead_code)]
1876    pub fn node_count(&self) -> usize {
1877        self.n
1878    }
1879    #[allow(dead_code)]
1880    pub fn edge_count(&self) -> usize {
1881        self.edge_count
1882    }
1883}
1884/// Pass execution phase for AgdaX2.
1885#[allow(dead_code)]
1886#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1887pub enum AgdaX2PassPhase {
1888    Early,
1889    Middle,
1890    Late,
1891    Finalize,
1892}
1893impl AgdaX2PassPhase {
1894    #[allow(dead_code)]
1895    pub fn is_early(&self) -> bool {
1896        matches!(self, Self::Early)
1897    }
1898    #[allow(dead_code)]
1899    pub fn is_middle(&self) -> bool {
1900        matches!(self, Self::Middle)
1901    }
1902    #[allow(dead_code)]
1903    pub fn is_late(&self) -> bool {
1904        matches!(self, Self::Late)
1905    }
1906    #[allow(dead_code)]
1907    pub fn is_finalize(&self) -> bool {
1908        matches!(self, Self::Finalize)
1909    }
1910    #[allow(dead_code)]
1911    pub fn order(&self) -> u32 {
1912        match self {
1913            Self::Early => 0,
1914            Self::Middle => 1,
1915            Self::Late => 2,
1916            Self::Finalize => 3,
1917        }
1918    }
1919    #[allow(dead_code)]
1920    pub fn from_order(n: u32) -> Option<Self> {
1921        match n {
1922            0 => Some(Self::Early),
1923            1 => Some(Self::Middle),
1924            2 => Some(Self::Late),
1925            3 => Some(Self::Finalize),
1926            _ => None,
1927        }
1928    }
1929}
1930/// Analysis cache for AgdaExt.
1931#[allow(dead_code)]
1932#[derive(Debug)]
1933pub struct AgdaExtCache {
1934    pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
1935    pub(super) cap: usize,
1936    pub(super) total_hits: u64,
1937    pub(super) total_misses: u64,
1938}
1939impl AgdaExtCache {
1940    #[allow(dead_code)]
1941    pub fn new(cap: usize) -> Self {
1942        Self {
1943            entries: Vec::new(),
1944            cap,
1945            total_hits: 0,
1946            total_misses: 0,
1947        }
1948    }
1949    #[allow(dead_code)]
1950    pub fn get(&mut self, key: u64) -> Option<&[u8]> {
1951        for e in self.entries.iter_mut() {
1952            if e.0 == key && e.2 {
1953                e.3 += 1;
1954                self.total_hits += 1;
1955                return Some(&e.1);
1956            }
1957        }
1958        self.total_misses += 1;
1959        None
1960    }
1961    #[allow(dead_code)]
1962    pub fn put(&mut self, key: u64, data: Vec<u8>) {
1963        if self.entries.len() >= self.cap {
1964            self.entries.retain(|e| e.2);
1965            if self.entries.len() >= self.cap {
1966                self.entries.remove(0);
1967            }
1968        }
1969        self.entries.push((key, data, true, 0));
1970    }
1971    #[allow(dead_code)]
1972    pub fn invalidate(&mut self) {
1973        for e in self.entries.iter_mut() {
1974            e.2 = false;
1975        }
1976    }
1977    #[allow(dead_code)]
1978    pub fn hit_rate(&self) -> f64 {
1979        let t = self.total_hits + self.total_misses;
1980        if t == 0 {
1981            0.0
1982        } else {
1983            self.total_hits as f64 / t as f64
1984        }
1985    }
1986    #[allow(dead_code)]
1987    pub fn live_count(&self) -> usize {
1988        self.entries.iter().filter(|e| e.2).count()
1989    }
1990}
1991/// Liveness analysis for AgdaExt.
1992#[allow(dead_code)]
1993#[derive(Debug, Clone, Default)]
1994pub struct AgdaExtLiveness {
1995    pub live_in: Vec<Vec<usize>>,
1996    pub live_out: Vec<Vec<usize>>,
1997    pub defs: Vec<Vec<usize>>,
1998    pub uses: Vec<Vec<usize>>,
1999}
2000impl AgdaExtLiveness {
2001    #[allow(dead_code)]
2002    pub fn new(n: usize) -> Self {
2003        Self {
2004            live_in: vec![Vec::new(); n],
2005            live_out: vec![Vec::new(); n],
2006            defs: vec![Vec::new(); n],
2007            uses: vec![Vec::new(); n],
2008        }
2009    }
2010    #[allow(dead_code)]
2011    pub fn live_in(&self, b: usize, v: usize) -> bool {
2012        self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2013    }
2014    #[allow(dead_code)]
2015    pub fn live_out(&self, b: usize, v: usize) -> bool {
2016        self.live_out
2017            .get(b)
2018            .map(|s| s.contains(&v))
2019            .unwrap_or(false)
2020    }
2021    #[allow(dead_code)]
2022    pub fn add_def(&mut self, b: usize, v: usize) {
2023        if let Some(s) = self.defs.get_mut(b) {
2024            if !s.contains(&v) {
2025                s.push(v);
2026            }
2027        }
2028    }
2029    #[allow(dead_code)]
2030    pub fn add_use(&mut self, b: usize, v: usize) {
2031        if let Some(s) = self.uses.get_mut(b) {
2032            if !s.contains(&v) {
2033                s.push(v);
2034            }
2035        }
2036    }
2037    #[allow(dead_code)]
2038    pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
2039        self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2040    }
2041    #[allow(dead_code)]
2042    pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
2043        self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2044    }
2045}
2046/// Worklist for AgdaX2.
2047#[allow(dead_code)]
2048#[derive(Debug, Clone)]
2049pub struct AgdaX2Worklist {
2050    pub(super) items: std::collections::VecDeque<usize>,
2051    pub(super) present: Vec<bool>,
2052}
2053impl AgdaX2Worklist {
2054    #[allow(dead_code)]
2055    pub fn new(capacity: usize) -> Self {
2056        Self {
2057            items: std::collections::VecDeque::new(),
2058            present: vec![false; capacity],
2059        }
2060    }
2061    #[allow(dead_code)]
2062    pub fn push(&mut self, id: usize) {
2063        if id < self.present.len() && !self.present[id] {
2064            self.present[id] = true;
2065            self.items.push_back(id);
2066        }
2067    }
2068    #[allow(dead_code)]
2069    pub fn push_front(&mut self, id: usize) {
2070        if id < self.present.len() && !self.present[id] {
2071            self.present[id] = true;
2072            self.items.push_front(id);
2073        }
2074    }
2075    #[allow(dead_code)]
2076    pub fn pop(&mut self) -> Option<usize> {
2077        let id = self.items.pop_front()?;
2078        if id < self.present.len() {
2079            self.present[id] = false;
2080        }
2081        Some(id)
2082    }
2083    #[allow(dead_code)]
2084    pub fn is_empty(&self) -> bool {
2085        self.items.is_empty()
2086    }
2087    #[allow(dead_code)]
2088    pub fn len(&self) -> usize {
2089        self.items.len()
2090    }
2091    #[allow(dead_code)]
2092    pub fn contains(&self, id: usize) -> bool {
2093        id < self.present.len() && self.present[id]
2094    }
2095    #[allow(dead_code)]
2096    pub fn drain_all(&mut self) -> Vec<usize> {
2097        let v: Vec<usize> = self.items.drain(..).collect();
2098        for &id in &v {
2099            if id < self.present.len() {
2100                self.present[id] = false;
2101            }
2102        }
2103        v
2104    }
2105}
2106/// Liveness analysis for AgdaX2.
2107#[allow(dead_code)]
2108#[derive(Debug, Clone, Default)]
2109pub struct AgdaX2Liveness {
2110    pub live_in: Vec<Vec<usize>>,
2111    pub live_out: Vec<Vec<usize>>,
2112    pub defs: Vec<Vec<usize>>,
2113    pub uses: Vec<Vec<usize>>,
2114}
2115impl AgdaX2Liveness {
2116    #[allow(dead_code)]
2117    pub fn new(n: usize) -> Self {
2118        Self {
2119            live_in: vec![Vec::new(); n],
2120            live_out: vec![Vec::new(); n],
2121            defs: vec![Vec::new(); n],
2122            uses: vec![Vec::new(); n],
2123        }
2124    }
2125    #[allow(dead_code)]
2126    pub fn live_in(&self, b: usize, v: usize) -> bool {
2127        self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2128    }
2129    #[allow(dead_code)]
2130    pub fn live_out(&self, b: usize, v: usize) -> bool {
2131        self.live_out
2132            .get(b)
2133            .map(|s| s.contains(&v))
2134            .unwrap_or(false)
2135    }
2136    #[allow(dead_code)]
2137    pub fn add_def(&mut self, b: usize, v: usize) {
2138        if let Some(s) = self.defs.get_mut(b) {
2139            if !s.contains(&v) {
2140                s.push(v);
2141            }
2142        }
2143    }
2144    #[allow(dead_code)]
2145    pub fn add_use(&mut self, b: usize, v: usize) {
2146        if let Some(s) = self.uses.get_mut(b) {
2147            if !s.contains(&v) {
2148                s.push(v);
2149            }
2150        }
2151    }
2152    #[allow(dead_code)]
2153    pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
2154        self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2155    }
2156    #[allow(dead_code)]
2157    pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
2158        self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2159    }
2160}
2161/// A complete Agda 2 source file.
2162#[derive(Debug, Clone)]
2163pub struct AgdaModule {
2164    /// Top-level module name (must match filename without `.agda` extension)
2165    pub module_name: String,
2166    /// Module-level parameters (for parameterised modules)
2167    pub params: Vec<(String, AgdaExpr)>,
2168    /// `import` directives
2169    pub imports: Vec<String>,
2170    /// `open` directives (after imports)
2171    pub opens: Vec<String>,
2172    /// Top-level declarations
2173    pub declarations: Vec<AgdaDecl>,
2174}
2175impl AgdaModule {
2176    /// Construct an empty module.
2177    pub fn new(module_name: impl Into<String>) -> Self {
2178        Self {
2179            module_name: module_name.into(),
2180            params: Vec::new(),
2181            imports: Vec::new(),
2182            opens: Vec::new(),
2183            declarations: Vec::new(),
2184        }
2185    }
2186    /// Add an `import`.
2187    pub fn import(&mut self, module: impl Into<String>) {
2188        self.imports.push(module.into());
2189    }
2190    /// Add an `open`.
2191    pub fn open(&mut self, module: impl Into<String>) {
2192        self.opens.push(module.into());
2193    }
2194    /// Add a declaration.
2195    pub fn add(&mut self, decl: AgdaDecl) {
2196        self.declarations.push(decl);
2197    }
2198    /// Emit the full Agda 2 source as a `String`.
2199    pub fn emit(&self) -> String {
2200        let mut out = "-- Generated by OxiLean\n".to_string();
2201        let ps = emit_agda_params(&self.params, 0);
2202        if ps.is_empty() {
2203            out.push_str(&format!("module {} where\n\n", self.module_name));
2204        } else {
2205            out.push_str(&format!("module {} {} where\n\n", self.module_name, ps));
2206        }
2207        for imp in &self.imports {
2208            out.push_str(&format!("import {}\n", imp));
2209        }
2210        if !self.imports.is_empty() {
2211            out.push('\n');
2212        }
2213        for op in &self.opens {
2214            out.push_str(&format!("open {}\n", op));
2215        }
2216        if !self.opens.is_empty() {
2217            out.push('\n');
2218        }
2219        for decl in &self.declarations {
2220            out.push_str(&decl.emit(0));
2221            out.push('\n');
2222        }
2223        out
2224    }
2225}
2226/// Pass registry for AgdaX2.
2227#[allow(dead_code)]
2228#[derive(Debug, Default)]
2229pub struct AgdaX2PassRegistry {
2230    pub(super) configs: Vec<AgdaX2PassConfig>,
2231    pub(super) stats: Vec<AgdaX2PassStats>,
2232}
2233impl AgdaX2PassRegistry {
2234    #[allow(dead_code)]
2235    pub fn new() -> Self {
2236        Self::default()
2237    }
2238    #[allow(dead_code)]
2239    pub fn register(&mut self, c: AgdaX2PassConfig) {
2240        self.stats.push(AgdaX2PassStats::new());
2241        self.configs.push(c);
2242    }
2243    #[allow(dead_code)]
2244    pub fn len(&self) -> usize {
2245        self.configs.len()
2246    }
2247    #[allow(dead_code)]
2248    pub fn is_empty(&self) -> bool {
2249        self.configs.is_empty()
2250    }
2251    #[allow(dead_code)]
2252    pub fn get(&self, i: usize) -> Option<&AgdaX2PassConfig> {
2253        self.configs.get(i)
2254    }
2255    #[allow(dead_code)]
2256    pub fn get_stats(&self, i: usize) -> Option<&AgdaX2PassStats> {
2257        self.stats.get(i)
2258    }
2259    #[allow(dead_code)]
2260    pub fn enabled_passes(&self) -> Vec<&AgdaX2PassConfig> {
2261        self.configs.iter().filter(|c| c.enabled).collect()
2262    }
2263    #[allow(dead_code)]
2264    pub fn passes_in_phase(&self, ph: &AgdaX2PassPhase) -> Vec<&AgdaX2PassConfig> {
2265        self.configs
2266            .iter()
2267            .filter(|c| c.enabled && &c.phase == ph)
2268            .collect()
2269    }
2270    #[allow(dead_code)]
2271    pub fn total_nodes_visited(&self) -> usize {
2272        self.stats.iter().map(|s| s.nodes_visited).sum()
2273    }
2274    #[allow(dead_code)]
2275    pub fn any_changed(&self) -> bool {
2276        self.stats.iter().any(|s| s.changed)
2277    }
2278}
2279/// Top-level `data` declaration.
2280#[derive(Debug, Clone, PartialEq)]
2281pub struct AgdaData {
2282    /// Type name
2283    pub name: String,
2284    /// Parameters (bound variables with types): `(A : Set)`
2285    pub params: Vec<(String, AgdaExpr)>,
2286    /// Indices / kind annotation: the `: Set` after parameters
2287    pub indices: AgdaExpr,
2288    /// Constructors
2289    pub constructors: Vec<AgdaConstructor>,
2290}
2291#[allow(dead_code)]
2292#[derive(Debug, Clone)]
2293pub struct AgdaWorklist {
2294    pub(super) items: std::collections::VecDeque<u32>,
2295    pub(super) in_worklist: std::collections::HashSet<u32>,
2296}
2297impl AgdaWorklist {
2298    #[allow(dead_code)]
2299    pub fn new() -> Self {
2300        AgdaWorklist {
2301            items: std::collections::VecDeque::new(),
2302            in_worklist: std::collections::HashSet::new(),
2303        }
2304    }
2305    #[allow(dead_code)]
2306    pub fn push(&mut self, item: u32) -> bool {
2307        if self.in_worklist.insert(item) {
2308            self.items.push_back(item);
2309            true
2310        } else {
2311            false
2312        }
2313    }
2314    #[allow(dead_code)]
2315    pub fn pop(&mut self) -> Option<u32> {
2316        let item = self.items.pop_front()?;
2317        self.in_worklist.remove(&item);
2318        Some(item)
2319    }
2320    #[allow(dead_code)]
2321    pub fn is_empty(&self) -> bool {
2322        self.items.is_empty()
2323    }
2324    #[allow(dead_code)]
2325    pub fn len(&self) -> usize {
2326        self.items.len()
2327    }
2328    #[allow(dead_code)]
2329    pub fn contains(&self, item: u32) -> bool {
2330        self.in_worklist.contains(&item)
2331    }
2332}
2333#[allow(dead_code)]
2334pub struct AgdaConstantFoldingHelper;
2335impl AgdaConstantFoldingHelper {
2336    #[allow(dead_code)]
2337    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
2338        a.checked_add(b)
2339    }
2340    #[allow(dead_code)]
2341    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
2342        a.checked_sub(b)
2343    }
2344    #[allow(dead_code)]
2345    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
2346        a.checked_mul(b)
2347    }
2348    #[allow(dead_code)]
2349    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
2350        if b == 0 {
2351            None
2352        } else {
2353            a.checked_div(b)
2354        }
2355    }
2356    #[allow(dead_code)]
2357    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
2358        a + b
2359    }
2360    #[allow(dead_code)]
2361    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
2362        a * b
2363    }
2364    #[allow(dead_code)]
2365    pub fn fold_neg_i64(a: i64) -> Option<i64> {
2366        a.checked_neg()
2367    }
2368    #[allow(dead_code)]
2369    pub fn fold_not_bool(a: bool) -> bool {
2370        !a
2371    }
2372    #[allow(dead_code)]
2373    pub fn fold_and_bool(a: bool, b: bool) -> bool {
2374        a && b
2375    }
2376    #[allow(dead_code)]
2377    pub fn fold_or_bool(a: bool, b: bool) -> bool {
2378        a || b
2379    }
2380    #[allow(dead_code)]
2381    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
2382        a.checked_shl(b)
2383    }
2384    #[allow(dead_code)]
2385    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
2386        a.checked_shr(b)
2387    }
2388    #[allow(dead_code)]
2389    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
2390        if b == 0 {
2391            None
2392        } else {
2393            Some(a % b)
2394        }
2395    }
2396    #[allow(dead_code)]
2397    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
2398        a & b
2399    }
2400    #[allow(dead_code)]
2401    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
2402        a | b
2403    }
2404    #[allow(dead_code)]
2405    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
2406        a ^ b
2407    }
2408    #[allow(dead_code)]
2409    pub fn fold_bitnot_i64(a: i64) -> i64 {
2410        !a
2411    }
2412}