Skip to main content

oxilean_codegen/ruby_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::HashSet;
7
8use super::functions::RUBY_RUNTIME;
9
10use super::functions::*;
11use std::collections::{HashMap, VecDeque};
12use std::fmt;
13use std::fmt::Write as FmtWrite;
14
15/// Ruby name mangler
16#[allow(dead_code)]
17#[derive(Debug, Default)]
18pub struct RubyNameMangler {
19    pub used: std::collections::HashSet<String>,
20    pub map: std::collections::HashMap<String, String>,
21}
22#[allow(dead_code)]
23impl RubyNameMangler {
24    pub fn new() -> Self {
25        Self::default()
26    }
27    pub fn mangle_constant(&mut self, name: &str) -> String {
28        let mut mangled: String = name
29            .chars()
30            .map(|c| {
31                if c.is_alphanumeric() || c == '_' {
32                    c
33                } else {
34                    '_'
35                }
36            })
37            .collect();
38        if !mangled.starts_with(|c: char| c.is_uppercase()) {
39            mangled = format!("Ox{}", mangled);
40        }
41        let base = mangled.clone();
42        let mut cnt = 0;
43        while self.used.contains(&mangled) {
44            cnt += 1;
45            mangled = format!("{}_{}", base, cnt);
46        }
47        self.used.insert(mangled.clone());
48        self.map.insert(name.to_string(), mangled.clone());
49        mangled
50    }
51    pub fn mangle_method(&mut self, name: &str) -> String {
52        let mangled: String = name
53            .chars()
54            .map(|c| {
55                if c.is_alphanumeric() || c == '_' {
56                    c
57                } else {
58                    '_'
59                }
60            })
61            .collect();
62        let ruby_reserved = [
63            "__method__",
64            "__dir__",
65            "__callee__",
66            "begin",
67            "end",
68            "do",
69            "if",
70            "unless",
71            "while",
72            "until",
73            "for",
74            "return",
75            "yield",
76            "class",
77            "module",
78            "def",
79            "alias",
80            "and",
81            "or",
82            "not",
83            "in",
84            "then",
85            "case",
86            "when",
87            "rescue",
88            "ensure",
89        ];
90        let base = if ruby_reserved.contains(&mangled.as_str()) {
91            format!("ox_{}", mangled)
92        } else {
93            mangled
94        };
95        let mut candidate = base.clone();
96        let mut cnt = 0;
97        while self.used.contains(&candidate) {
98            cnt += 1;
99            candidate = format!("{}_{}", base, cnt);
100        }
101        self.used.insert(candidate.clone());
102        self.map.insert(name.to_string(), candidate.clone());
103        candidate
104    }
105}
106/// Ruby method alias
107#[allow(dead_code)]
108#[derive(Debug, Clone)]
109pub struct RubyAlias {
110    pub new_name: String,
111    pub old_name: String,
112}
113#[allow(dead_code)]
114#[derive(Debug, Clone)]
115pub struct RubyLivenessInfo {
116    pub live_in: Vec<std::collections::HashSet<u32>>,
117    pub live_out: Vec<std::collections::HashSet<u32>>,
118    pub defs: Vec<std::collections::HashSet<u32>>,
119    pub uses: Vec<std::collections::HashSet<u32>>,
120}
121impl RubyLivenessInfo {
122    #[allow(dead_code)]
123    pub fn new(block_count: usize) -> Self {
124        RubyLivenessInfo {
125            live_in: vec![std::collections::HashSet::new(); block_count],
126            live_out: vec![std::collections::HashSet::new(); block_count],
127            defs: vec![std::collections::HashSet::new(); block_count],
128            uses: vec![std::collections::HashSet::new(); block_count],
129        }
130    }
131    #[allow(dead_code)]
132    pub fn add_def(&mut self, block: usize, var: u32) {
133        if block < self.defs.len() {
134            self.defs[block].insert(var);
135        }
136    }
137    #[allow(dead_code)]
138    pub fn add_use(&mut self, block: usize, var: u32) {
139        if block < self.uses.len() {
140            self.uses[block].insert(var);
141        }
142    }
143    #[allow(dead_code)]
144    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
145        self.live_in
146            .get(block)
147            .map(|s| s.contains(&var))
148            .unwrap_or(false)
149    }
150    #[allow(dead_code)]
151    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
152        self.live_out
153            .get(block)
154            .map(|s| s.contains(&var))
155            .unwrap_or(false)
156    }
157}
158#[allow(dead_code)]
159pub struct RubyConstantFoldingHelper;
160impl RubyConstantFoldingHelper {
161    #[allow(dead_code)]
162    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
163        a.checked_add(b)
164    }
165    #[allow(dead_code)]
166    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
167        a.checked_sub(b)
168    }
169    #[allow(dead_code)]
170    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
171        a.checked_mul(b)
172    }
173    #[allow(dead_code)]
174    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
175        if b == 0 {
176            None
177        } else {
178            a.checked_div(b)
179        }
180    }
181    #[allow(dead_code)]
182    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
183        a + b
184    }
185    #[allow(dead_code)]
186    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
187        a * b
188    }
189    #[allow(dead_code)]
190    pub fn fold_neg_i64(a: i64) -> Option<i64> {
191        a.checked_neg()
192    }
193    #[allow(dead_code)]
194    pub fn fold_not_bool(a: bool) -> bool {
195        !a
196    }
197    #[allow(dead_code)]
198    pub fn fold_and_bool(a: bool, b: bool) -> bool {
199        a && b
200    }
201    #[allow(dead_code)]
202    pub fn fold_or_bool(a: bool, b: bool) -> bool {
203        a || b
204    }
205    #[allow(dead_code)]
206    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
207        a.checked_shl(b)
208    }
209    #[allow(dead_code)]
210    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
211        a.checked_shr(b)
212    }
213    #[allow(dead_code)]
214    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
215        if b == 0 {
216            None
217        } else {
218            Some(a % b)
219        }
220    }
221    #[allow(dead_code)]
222    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
223        a & b
224    }
225    #[allow(dead_code)]
226    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
227        a | b
228    }
229    #[allow(dead_code)]
230    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
231        a ^ b
232    }
233    #[allow(dead_code)]
234    pub fn fold_bitnot_i64(a: i64) -> i64 {
235        !a
236    }
237}
238#[allow(dead_code)]
239#[derive(Debug, Clone)]
240pub struct RubyDepGraph {
241    pub(super) nodes: Vec<u32>,
242    pub(super) edges: Vec<(u32, u32)>,
243}
244impl RubyDepGraph {
245    #[allow(dead_code)]
246    pub fn new() -> Self {
247        RubyDepGraph {
248            nodes: Vec::new(),
249            edges: Vec::new(),
250        }
251    }
252    #[allow(dead_code)]
253    pub fn add_node(&mut self, id: u32) {
254        if !self.nodes.contains(&id) {
255            self.nodes.push(id);
256        }
257    }
258    #[allow(dead_code)]
259    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
260        self.add_node(dep);
261        self.add_node(dependent);
262        self.edges.push((dep, dependent));
263    }
264    #[allow(dead_code)]
265    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
266        self.edges
267            .iter()
268            .filter(|(d, _)| *d == node)
269            .map(|(_, dep)| *dep)
270            .collect()
271    }
272    #[allow(dead_code)]
273    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
274        self.edges
275            .iter()
276            .filter(|(_, dep)| *dep == node)
277            .map(|(d, _)| *d)
278            .collect()
279    }
280    #[allow(dead_code)]
281    pub fn topological_sort(&self) -> Vec<u32> {
282        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
283        for &n in &self.nodes {
284            in_degree.insert(n, 0);
285        }
286        for (_, dep) in &self.edges {
287            *in_degree.entry(*dep).or_insert(0) += 1;
288        }
289        let mut queue: std::collections::VecDeque<u32> = self
290            .nodes
291            .iter()
292            .filter(|&&n| in_degree[&n] == 0)
293            .copied()
294            .collect();
295        let mut result = Vec::new();
296        while let Some(node) = queue.pop_front() {
297            result.push(node);
298            for dep in self.dependents_of(node) {
299                let cnt = in_degree.entry(dep).or_insert(0);
300                *cnt = cnt.saturating_sub(1);
301                if *cnt == 0 {
302                    queue.push_back(dep);
303                }
304            }
305        }
306        result
307    }
308    #[allow(dead_code)]
309    pub fn has_cycle(&self) -> bool {
310        self.topological_sort().len() < self.nodes.len()
311    }
312}
313/// Ruby source buffer
314#[allow(dead_code)]
315#[derive(Debug, Default)]
316pub struct RubyExtSourceBuffer {
317    pub sections: Vec<(String, String)>,
318    pub current: String,
319    pub indent: usize,
320}
321#[allow(dead_code)]
322impl RubyExtSourceBuffer {
323    pub fn new() -> Self {
324        Self::default()
325    }
326    pub fn write(&mut self, s: &str) {
327        let pad = "  ".repeat(self.indent);
328        self.current.push_str(&pad);
329        self.current.push_str(s);
330    }
331    pub fn writeln(&mut self, s: &str) {
332        let pad = "  ".repeat(self.indent);
333        self.current.push_str(&pad);
334        self.current.push_str(s);
335        self.current.push('\n');
336    }
337    pub fn indent(&mut self) {
338        self.indent += 1;
339    }
340    pub fn dedent(&mut self) {
341        if self.indent > 0 {
342            self.indent -= 1;
343        }
344    }
345    pub fn finish(mut self) -> String {
346        let done = std::mem::take(&mut self.current);
347        if !done.is_empty() {
348            self.sections.push(("".to_string(), done));
349        }
350        self.sections
351            .iter()
352            .map(|(_, s)| s.as_str())
353            .collect::<Vec<_>>()
354            .join("")
355    }
356}
357/// Ruby proc vs lambda differences
358#[allow(dead_code)]
359pub struct RubyProcLambdaDiff {
360    pub arity_strict: bool,
361    pub return_behavior: String,
362}
363/// Ruby class definition
364#[allow(dead_code)]
365#[derive(Debug, Clone)]
366pub struct RubyClassDef {
367    pub name: String,
368    pub superclass: Option<String>,
369    pub includes: Vec<String>,
370    pub extends: Vec<String>,
371    pub prepends: Vec<String>,
372    pub methods: Vec<RubyMethodDef>,
373    pub attrs: Vec<(String, bool, bool)>,
374    pub constants: Vec<(String, String)>,
375}
376/// Ruby final pass summary
377#[allow(dead_code)]
378#[derive(Debug, Default, Clone)]
379pub struct RubyPassSummary {
380    pub pass_name: String,
381    pub functions_compiled: usize,
382    pub classes_emitted: usize,
383    pub modules_emitted: usize,
384    pub duration_us: u64,
385}
386/// Ruby class registry
387#[allow(dead_code)]
388#[derive(Debug, Default)]
389pub struct RubyClassRegistry {
390    pub classes: Vec<RubyClassDef>,
391    pub modules: Vec<RubyModuleDef>,
392}
393#[allow(dead_code)]
394impl RubyClassRegistry {
395    pub fn new() -> Self {
396        Self::default()
397    }
398    pub fn add_class(&mut self, c: RubyClassDef) {
399        self.classes.push(c);
400    }
401    pub fn add_module(&mut self, m: RubyModuleDef) {
402        self.modules.push(m);
403    }
404    pub fn total_items(&self) -> usize {
405        self.classes.len() + self.modules.len()
406    }
407    pub fn emit_all(&self) -> String {
408        let mut out = String::new();
409        for m in &self.modules {
410            out.push_str(&format!("{}\n\n", m));
411        }
412        for c in &self.classes {
413            out.push_str(&format!("{}\n\n", c));
414        }
415        out
416    }
417}
418#[allow(dead_code)]
419#[derive(Debug, Clone)]
420pub struct RubyAnalysisCache {
421    pub(super) entries: std::collections::HashMap<String, RubyCacheEntry>,
422    pub(super) max_size: usize,
423    pub(super) hits: u64,
424    pub(super) misses: u64,
425}
426impl RubyAnalysisCache {
427    #[allow(dead_code)]
428    pub fn new(max_size: usize) -> Self {
429        RubyAnalysisCache {
430            entries: std::collections::HashMap::new(),
431            max_size,
432            hits: 0,
433            misses: 0,
434        }
435    }
436    #[allow(dead_code)]
437    pub fn get(&mut self, key: &str) -> Option<&RubyCacheEntry> {
438        if self.entries.contains_key(key) {
439            self.hits += 1;
440            self.entries.get(key)
441        } else {
442            self.misses += 1;
443            None
444        }
445    }
446    #[allow(dead_code)]
447    pub fn insert(&mut self, key: String, data: Vec<u8>) {
448        if self.entries.len() >= self.max_size {
449            if let Some(oldest) = self.entries.keys().next().cloned() {
450                self.entries.remove(&oldest);
451            }
452        }
453        self.entries.insert(
454            key.clone(),
455            RubyCacheEntry {
456                key,
457                data,
458                timestamp: 0,
459                valid: true,
460            },
461        );
462    }
463    #[allow(dead_code)]
464    pub fn invalidate(&mut self, key: &str) {
465        if let Some(entry) = self.entries.get_mut(key) {
466            entry.valid = false;
467        }
468    }
469    #[allow(dead_code)]
470    pub fn clear(&mut self) {
471        self.entries.clear();
472    }
473    #[allow(dead_code)]
474    pub fn hit_rate(&self) -> f64 {
475        let total = self.hits + self.misses;
476        if total == 0 {
477            return 0.0;
478        }
479        self.hits as f64 / total as f64
480    }
481    #[allow(dead_code)]
482    pub fn size(&self) -> usize {
483        self.entries.len()
484    }
485}
486/// A Ruby method definition (`def name(params) ... end`).
487#[derive(Debug, Clone, PartialEq)]
488pub struct RubyMethod {
489    /// Method name (snake_case).
490    pub name: std::string::String,
491    /// Parameter names.
492    pub params: Vec<std::string::String>,
493    /// Body statements.
494    pub body: Vec<RubyStmt>,
495    /// Visibility modifier (default: Public).
496    pub visibility: RubyVisibility,
497}
498impl RubyMethod {
499    /// Create a new public method.
500    pub fn new(name: &str, params: Vec<&str>, body: Vec<RubyStmt>) -> Self {
501        RubyMethod {
502            name: name.to_string(),
503            params: params.into_iter().map(|s| s.to_string()).collect(),
504            body,
505            visibility: RubyVisibility::Public,
506        }
507    }
508    /// Create a private method.
509    pub fn private(name: &str, params: Vec<&str>, body: Vec<RubyStmt>) -> Self {
510        RubyMethod {
511            name: name.to_string(),
512            params: params.into_iter().map(|s| s.to_string()).collect(),
513            body,
514            visibility: RubyVisibility::Private,
515        }
516    }
517}
518/// Ruby method visibility modifier.
519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520pub enum RubyVisibility {
521    /// Public (default) — callable from anywhere
522    Public,
523    /// Protected — callable from the class and subclasses
524    Protected,
525    /// Private — callable only within the defining class
526    Private,
527}
528/// Ruby id generator
529#[allow(dead_code)]
530#[derive(Debug, Default)]
531pub struct RubyExtIdGen {
532    pub(super) counter: u64,
533    pub(super) prefix: String,
534}
535#[allow(dead_code)]
536impl RubyExtIdGen {
537    pub fn new(prefix: &str) -> Self {
538        Self {
539            counter: 0,
540            prefix: prefix.to_string(),
541        }
542    }
543    pub fn next(&mut self) -> String {
544        let id = self.counter;
545        self.counter += 1;
546        format!("{}_{}", self.prefix, id)
547    }
548}
549/// Ruby diagnostic
550#[allow(dead_code)]
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub enum RubyDiagLevel {
553    Info,
554    Warning,
555    Error,
556}
557/// Ruby fiber definition
558#[allow(dead_code)]
559#[derive(Debug, Clone)]
560pub struct RubyFiber {
561    pub name: String,
562    pub params: Vec<String>,
563    pub body: String,
564    pub is_async: bool,
565}
566#[allow(dead_code)]
567#[derive(Debug, Clone)]
568pub struct RubyCacheEntry {
569    pub key: String,
570    pub data: Vec<u8>,
571    pub timestamp: u64,
572    pub valid: bool,
573}
574#[allow(dead_code)]
575#[derive(Debug, Clone)]
576pub struct RubyPassConfig {
577    pub phase: RubyPassPhase,
578    pub enabled: bool,
579    pub max_iterations: u32,
580    pub debug_output: bool,
581    pub pass_name: String,
582}
583impl RubyPassConfig {
584    #[allow(dead_code)]
585    pub fn new(name: impl Into<String>, phase: RubyPassPhase) -> Self {
586        RubyPassConfig {
587            phase,
588            enabled: true,
589            max_iterations: 10,
590            debug_output: false,
591            pass_name: name.into(),
592        }
593    }
594    #[allow(dead_code)]
595    pub fn disabled(mut self) -> Self {
596        self.enabled = false;
597        self
598    }
599    #[allow(dead_code)]
600    pub fn with_debug(mut self) -> Self {
601        self.debug_output = true;
602        self
603    }
604    #[allow(dead_code)]
605    pub fn max_iter(mut self, n: u32) -> Self {
606        self.max_iterations = n;
607        self
608    }
609}
610#[allow(dead_code)]
611#[derive(Debug, Clone)]
612pub struct RubyWorklist {
613    pub(super) items: std::collections::VecDeque<u32>,
614    pub(super) in_worklist: std::collections::HashSet<u32>,
615}
616impl RubyWorklist {
617    #[allow(dead_code)]
618    pub fn new() -> Self {
619        RubyWorklist {
620            items: std::collections::VecDeque::new(),
621            in_worklist: std::collections::HashSet::new(),
622        }
623    }
624    #[allow(dead_code)]
625    pub fn push(&mut self, item: u32) -> bool {
626        if self.in_worklist.insert(item) {
627            self.items.push_back(item);
628            true
629        } else {
630            false
631        }
632    }
633    #[allow(dead_code)]
634    pub fn pop(&mut self) -> Option<u32> {
635        let item = self.items.pop_front()?;
636        self.in_worklist.remove(&item);
637        Some(item)
638    }
639    #[allow(dead_code)]
640    pub fn is_empty(&self) -> bool {
641        self.items.is_empty()
642    }
643    #[allow(dead_code)]
644    pub fn len(&self) -> usize {
645        self.items.len()
646    }
647    #[allow(dead_code)]
648    pub fn contains(&self, item: u32) -> bool {
649        self.in_worklist.contains(&item)
650    }
651}
652#[allow(dead_code)]
653#[derive(Debug, Clone)]
654pub struct RubyDominatorTree {
655    pub idom: Vec<Option<u32>>,
656    pub dom_children: Vec<Vec<u32>>,
657    pub dom_depth: Vec<u32>,
658}
659impl RubyDominatorTree {
660    #[allow(dead_code)]
661    pub fn new(size: usize) -> Self {
662        RubyDominatorTree {
663            idom: vec![None; size],
664            dom_children: vec![Vec::new(); size],
665            dom_depth: vec![0; size],
666        }
667    }
668    #[allow(dead_code)]
669    pub fn set_idom(&mut self, node: usize, idom: u32) {
670        self.idom[node] = Some(idom);
671    }
672    #[allow(dead_code)]
673    pub fn dominates(&self, a: usize, b: usize) -> bool {
674        if a == b {
675            return true;
676        }
677        let mut cur = b;
678        loop {
679            match self.idom[cur] {
680                Some(parent) if parent as usize == a => return true,
681                Some(parent) if parent as usize == cur => return false,
682                Some(parent) => cur = parent as usize,
683                None => return false,
684            }
685        }
686    }
687    #[allow(dead_code)]
688    pub fn depth(&self, node: usize) -> u32 {
689        self.dom_depth.get(node).copied().unwrap_or(0)
690    }
691}
692/// Ruby method contract
693#[allow(dead_code)]
694#[derive(Debug, Clone)]
695pub struct RubyMethodContract {
696    pub preconditions: Vec<String>,
697    pub postconditions: Vec<String>,
698}
699#[allow(dead_code)]
700impl RubyMethodContract {
701    pub fn new() -> Self {
702        Self {
703            preconditions: Vec::new(),
704            postconditions: Vec::new(),
705        }
706    }
707    pub fn add_pre(&mut self, cond: &str) {
708        self.preconditions.push(cond.to_string());
709    }
710    pub fn add_post(&mut self, cond: &str) {
711        self.postconditions.push(cond.to_string());
712    }
713    pub fn emit_assertions(&self) -> String {
714        let mut out = String::new();
715        for pre in &self.preconditions {
716            out.push_str(&format!("raise ArgumentError unless {}\n", pre));
717        }
718        for post in &self.postconditions {
719            out.push_str(&format!("raise RuntimeError unless {}\n", post));
720        }
721        out
722    }
723}
724/// Ruby type representation for type-directed code generation.
725#[derive(Debug, Clone, PartialEq, Eq, Hash)]
726pub enum RubyType {
727    /// `Integer` — arbitrary-precision integer (maps to Lean Nat)
728    Integer,
729    /// `Float` — 64-bit IEEE 754 float
730    Float,
731    /// `String`
732    String,
733    /// `TrueClass` / `FalseClass` — boolean
734    Bool,
735    /// `NilClass`
736    Nil,
737    /// `Array` — homogeneous list
738    Array(Box<RubyType>),
739    /// `Hash` — key-value map
740    Hash(Box<RubyType>, Box<RubyType>),
741    /// `Symbol`
742    Symbol,
743    /// Arbitrary named class / module
744    Object(std::string::String),
745    /// `Proc` — first-class callable
746    Proc,
747}
748/// Ruby pattern matching (Ruby 3.x)
749#[allow(dead_code)]
750#[derive(Debug, Clone)]
751pub enum RubyPattern {
752    Pin(String),
753    Variable(String),
754    Literal(String),
755    Array(Vec<RubyPattern>),
756    Hash(Vec<(String, Option<RubyPattern>)>),
757    Find(Vec<RubyPattern>),
758    Deconstruct(String, Vec<(String, RubyPattern)>),
759    Guard(Box<RubyPattern>, String),
760}
761#[allow(dead_code)]
762pub struct RubyPassRegistry {
763    pub(super) configs: Vec<RubyPassConfig>,
764    pub(super) stats: std::collections::HashMap<String, RubyPassStats>,
765}
766impl RubyPassRegistry {
767    #[allow(dead_code)]
768    pub fn new() -> Self {
769        RubyPassRegistry {
770            configs: Vec::new(),
771            stats: std::collections::HashMap::new(),
772        }
773    }
774    #[allow(dead_code)]
775    pub fn register(&mut self, config: RubyPassConfig) {
776        self.stats
777            .insert(config.pass_name.clone(), RubyPassStats::new());
778        self.configs.push(config);
779    }
780    #[allow(dead_code)]
781    pub fn enabled_passes(&self) -> Vec<&RubyPassConfig> {
782        self.configs.iter().filter(|c| c.enabled).collect()
783    }
784    #[allow(dead_code)]
785    pub fn get_stats(&self, name: &str) -> Option<&RubyPassStats> {
786        self.stats.get(name)
787    }
788    #[allow(dead_code)]
789    pub fn total_passes(&self) -> usize {
790        self.configs.len()
791    }
792    #[allow(dead_code)]
793    pub fn enabled_count(&self) -> usize {
794        self.enabled_passes().len()
795    }
796    #[allow(dead_code)]
797    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
798        if let Some(stats) = self.stats.get_mut(name) {
799            stats.record_run(changes, time_ms, iter);
800        }
801    }
802}
803/// Ruby type (extended)
804#[allow(dead_code)]
805#[derive(Debug, Clone, PartialEq)]
806pub enum RubyTypeExt {
807    Integer,
808    Float,
809    String,
810    Symbol,
811    Bool,
812    Nil,
813    Array(Box<RubyTypeExt>),
814    Hash(Box<RubyTypeExt>, Box<RubyTypeExt>),
815    Proc,
816    Lambda,
817    Range,
818    Struct(String),
819    Class(String),
820    Module(String),
821    Any,
822}
823/// Ruby literal values.
824#[derive(Debug, Clone, PartialEq)]
825pub enum RubyLit {
826    /// Integer literal: `42`, `0`, `-7`
827    Int(i64),
828    /// Float literal: `3.14`
829    Float(f64),
830    /// String literal: `"hello"`
831    Str(std::string::String),
832    /// Boolean literal: `true` or `false`
833    Bool(bool),
834    /// `nil` literal
835    Nil,
836    /// Symbol literal: `:name`
837    Symbol(std::string::String),
838}
839/// Ruby begin/rescue/ensure
840#[allow(dead_code)]
841#[derive(Debug, Clone)]
842pub struct RubyRescueBlock {
843    pub body: String,
844    pub rescues: Vec<(Vec<String>, Option<String>, String)>,
845    pub ensure: Option<String>,
846}
847/// Ruby case/in expression (pattern matching)
848#[allow(dead_code)]
849#[derive(Debug, Clone)]
850pub struct RubyCaseIn {
851    pub scrutinee: String,
852    pub arms: Vec<(RubyPattern, String)>,
853    pub else_body: Option<String>,
854}
855/// Ruby statement AST.
856#[derive(Debug, Clone, PartialEq)]
857pub enum RubyStmt {
858    /// Standalone expression statement.
859    Expr(RubyExpr),
860    /// Local variable assignment: `name = expr`
861    Assign(std::string::String, RubyExpr),
862    /// Method definition: `def name(params) ... end`
863    Def(RubyMethod),
864    /// Class definition: `class Name ... end`
865    Class(RubyClass),
866    /// Module definition: `module Name ... end`
867    Mod(RubyModule),
868    /// `if cond ... elsif ... else ... end`
869    If(
870        RubyExpr,
871        Vec<RubyStmt>,
872        Vec<(RubyExpr, Vec<RubyStmt>)>,
873        Option<Vec<RubyStmt>>,
874    ),
875    /// `while cond ... end`
876    While(RubyExpr, Vec<RubyStmt>),
877    /// `return expr`
878    Return(RubyExpr),
879    /// `begin ... rescue ... ensure ... end`
880    Begin(
881        Vec<RubyStmt>,
882        Option<(std::string::String, Vec<RubyStmt>)>,
883        Option<Vec<RubyStmt>>,
884    ),
885}
886/// Ruby expression AST.
887#[derive(Debug, Clone, PartialEq)]
888pub enum RubyExpr {
889    /// Literal value: `42`, `"hello"`, `:sym`, `nil`
890    Lit(RubyLit),
891    /// Variable / local name: `x`, `result`, `_t0`
892    Var(std::string::String),
893    /// Binary operator: `lhs + rhs`, `a == b`, `x && y`
894    BinOp(std::string::String, Box<RubyExpr>, Box<RubyExpr>),
895    /// Unary operator: `!x`, `-n`, `~bits`
896    UnaryOp(std::string::String, Box<RubyExpr>),
897    /// Free function call: `foo(a, b)`
898    Call(std::string::String, Vec<RubyExpr>),
899    /// Method call: `obj.method(a, b)`
900    MethodCall(Box<RubyExpr>, std::string::String, Vec<RubyExpr>),
901    /// Block with brace syntax: `{ |x| expr }`
902    Block(Vec<std::string::String>, Vec<RubyStmt>),
903    /// Lambda (stabby): `->(x, y) { body }`
904    Lambda(Vec<std::string::String>, Vec<RubyStmt>),
905    /// Ternary / conditional expression: `cond ? then_e : else_e`
906    If(Box<RubyExpr>, Box<RubyExpr>, Box<RubyExpr>),
907    /// `case` expression with `when` branches (value-based)
908    Case(
909        Box<RubyExpr>,
910        Vec<(RubyExpr, RubyExpr)>,
911        Option<Box<RubyExpr>>,
912    ),
913    /// Array literal: `[a, b, c]`
914    Array(Vec<RubyExpr>),
915    /// Hash literal: `{ key: val, ... }` (symbol keys) or `{ "k" => v }`
916    Hash(Vec<(RubyExpr, RubyExpr)>),
917    /// Local variable assignment expression: `x = expr` (returns the rhs)
918    Assign(std::string::String, Box<RubyExpr>),
919    /// `return expr`
920    Return(Box<RubyExpr>),
921}
922/// Ruby backend config (extended)
923#[allow(dead_code)]
924#[derive(Debug, Clone)]
925pub struct RubyExtConfig {
926    pub ruby_version: String,
927    pub use_sorbet: bool,
928    pub use_rbs: bool,
929    pub frozen_string_literals: bool,
930    pub encoding: String,
931    pub indent_size: usize,
932    pub use_keyword_args: bool,
933}
934#[allow(dead_code)]
935#[derive(Debug, Clone, PartialEq)]
936pub enum RubyPassPhase {
937    Analysis,
938    Transformation,
939    Verification,
940    Cleanup,
941}
942impl RubyPassPhase {
943    #[allow(dead_code)]
944    pub fn name(&self) -> &str {
945        match self {
946            RubyPassPhase::Analysis => "analysis",
947            RubyPassPhase::Transformation => "transformation",
948            RubyPassPhase::Verification => "verification",
949            RubyPassPhase::Cleanup => "cleanup",
950        }
951    }
952    #[allow(dead_code)]
953    pub fn is_modifying(&self) -> bool {
954        matches!(self, RubyPassPhase::Transformation | RubyPassPhase::Cleanup)
955    }
956}
957/// Ruby module definition
958#[allow(dead_code)]
959#[derive(Debug, Clone)]
960pub struct RubyModuleDef {
961    pub name: String,
962    pub includes: Vec<String>,
963    pub methods: Vec<RubyMethodDef>,
964    pub constants: Vec<(String, String)>,
965}
966/// Ruby block parameter
967#[allow(dead_code)]
968#[derive(Debug, Clone)]
969pub struct RubyBlock {
970    pub params: Vec<String>,
971    pub body: String,
972    pub is_proc: bool,
973}
974/// Ruby require statement
975#[allow(dead_code)]
976#[derive(Debug, Clone)]
977pub enum RubyRequire {
978    Require(String),
979    RequireRelative(String),
980    Autoload(String, String),
981}
982/// Ruby method definition
983#[allow(dead_code)]
984#[derive(Debug, Clone)]
985pub struct RubyMethodDef {
986    pub name: String,
987    pub params: Vec<(String, Option<RubyTypeExt>)>,
988    pub return_type: Option<RubyTypeExt>,
989    pub body: String,
990    pub visibility: RubyVisibility,
991    pub is_class_method: bool,
992    pub is_abstract: bool,
993}
994/// Ruby Enumerator::Lazy
995#[allow(dead_code)]
996#[derive(Debug, Clone)]
997pub struct RubyLazyEnumerator {
998    pub source: String,
999    pub transforms: Vec<String>,
1000}
1001/// A Ruby class definition.
1002#[derive(Debug, Clone, PartialEq)]
1003pub struct RubyClass {
1004    /// Class name (CamelCase).
1005    pub name: std::string::String,
1006    /// Optional superclass name.
1007    pub superclass: Option<std::string::String>,
1008    /// Instance methods.
1009    pub methods: Vec<RubyMethod>,
1010    /// Class-level methods (will be emitted as `def self.name`).
1011    pub class_methods: Vec<RubyMethod>,
1012    /// `attr_reader` accessor names.
1013    pub attr_readers: Vec<std::string::String>,
1014    /// `attr_writer` accessor names.
1015    pub attr_writers: Vec<std::string::String>,
1016}
1017impl RubyClass {
1018    /// Create a new empty class.
1019    pub fn new(name: &str) -> Self {
1020        RubyClass {
1021            name: name.to_string(),
1022            superclass: None,
1023            methods: Vec::new(),
1024            class_methods: Vec::new(),
1025            attr_readers: Vec::new(),
1026            attr_writers: Vec::new(),
1027        }
1028    }
1029    /// Set the superclass.
1030    pub fn with_superclass(mut self, superclass: &str) -> Self {
1031        self.superclass = Some(superclass.to_string());
1032        self
1033    }
1034    /// Add an `attr_reader`.
1035    pub fn add_attr_reader(&mut self, name: &str) {
1036        self.attr_readers.push(name.to_string());
1037    }
1038    /// Add an instance method.
1039    pub fn add_method(&mut self, method: RubyMethod) {
1040        self.methods.push(method);
1041    }
1042    /// Add a class method.
1043    pub fn add_class_method(&mut self, method: RubyMethod) {
1044        self.class_methods.push(method);
1045    }
1046}
1047/// Ruby code stats
1048#[allow(dead_code)]
1049#[derive(Debug, Default, Clone)]
1050pub struct RubyCodeStats {
1051    pub classes: usize,
1052    pub modules: usize,
1053    pub methods: usize,
1054    pub lambdas: usize,
1055    pub blocks: usize,
1056    pub total_lines: usize,
1057}
1058/// Ruby backend code stats v2
1059#[allow(dead_code)]
1060#[derive(Debug, Default, Clone)]
1061pub struct RubyBackendCodeStats {
1062    pub files: usize,
1063    pub classes: usize,
1064    pub modules: usize,
1065    pub methods: usize,
1066    pub lines: usize,
1067}
1068/// Ruby emit stats
1069#[allow(dead_code)]
1070#[derive(Debug, Default, Clone)]
1071pub struct RubyExtEmitStats {
1072    pub bytes_written: usize,
1073    pub items_emitted: usize,
1074    pub errors: usize,
1075    pub warnings: usize,
1076    pub classes_emitted: usize,
1077    pub modules_emitted: usize,
1078    pub methods_emitted: usize,
1079}
1080/// Ruby struct definition
1081#[allow(dead_code)]
1082#[derive(Debug, Clone)]
1083pub struct RubyStructDef {
1084    pub name: String,
1085    pub members: Vec<(String, Option<RubyTypeExt>)>,
1086}
1087/// A Ruby module definition (also used as the top-level compilation unit).
1088#[derive(Debug, Clone, PartialEq)]
1089pub struct RubyModule {
1090    /// Module name (CamelCase).
1091    pub name: std::string::String,
1092    /// Module-level constants and assignments.
1093    pub constants: Vec<(std::string::String, RubyExpr)>,
1094    /// Module methods (emitted as `module_function` or `def self.name`).
1095    pub functions: Vec<RubyMethod>,
1096    /// Nested classes inside this module.
1097    pub classes: Vec<RubyClass>,
1098    /// Nested sub-modules.
1099    pub submodules: Vec<RubyModule>,
1100    /// Whether to emit `module_function` marker before functions.
1101    pub module_function: bool,
1102}
1103impl RubyModule {
1104    /// Create a new empty module.
1105    pub fn new(name: &str) -> Self {
1106        RubyModule {
1107            name: name.to_string(),
1108            constants: Vec::new(),
1109            functions: Vec::new(),
1110            classes: Vec::new(),
1111            submodules: Vec::new(),
1112            module_function: true,
1113        }
1114    }
1115    /// Generate valid Ruby source for this module.
1116    pub fn emit(&self) -> std::string::String {
1117        let mut out = std::string::String::new();
1118        writeln!(out, "# frozen_string_literal: true").expect("writing to String never fails");
1119        writeln!(out).expect("writing to String never fails");
1120        self.emit_module_body(&mut out, "");
1121        out
1122    }
1123    pub(super) fn emit_module_body(&self, out: &mut std::string::String, indent: &str) {
1124        let inner = format!("{}  ", indent);
1125        writeln!(out, "{}module {}", indent, self.name).expect("writing to String never fails");
1126        for (name, expr) in &self.constants {
1127            writeln!(out, "{}{} = {}", inner, name, expr).expect("writing to String never fails");
1128        }
1129        if !self.constants.is_empty() {
1130            writeln!(out).expect("writing to String never fails");
1131        }
1132        for submod in &self.submodules {
1133            submod.emit_module_body(out, &inner);
1134            writeln!(out).expect("writing to String never fails");
1135        }
1136        for class in &self.classes {
1137            let mut fmt_buf = std::string::String::new();
1138            struct Wrapper<'a>(&'a RubyClass, &'a str);
1139            impl fmt::Display for Wrapper<'_> {
1140                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1141                    fmt_ruby_class(self.0, self.1, f)
1142                }
1143            }
1144            write!(fmt_buf, "{}", Wrapper(class, &inner)).expect("writing to String never fails");
1145            out.push_str(&fmt_buf);
1146            writeln!(out).expect("writing to String never fails");
1147        }
1148        if !self.functions.is_empty() {
1149            if self.module_function {
1150                writeln!(out, "{}module_function", inner).expect("writing to String never fails");
1151                writeln!(out).expect("writing to String never fails");
1152            }
1153            for method in &self.functions {
1154                let mut fmt_buf = std::string::String::new();
1155                struct Wrapper<'a>(&'a RubyMethod, &'a str);
1156                impl fmt::Display for Wrapper<'_> {
1157                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158                        fmt_ruby_method(self.0, self.1, f)
1159                    }
1160                }
1161                write!(fmt_buf, "{}", Wrapper(method, &inner))
1162                    .expect("writing to String never fails");
1163                out.push_str(&fmt_buf);
1164            }
1165        }
1166        writeln!(out, "{}end", indent).expect("writing to String never fails");
1167    }
1168}
1169/// Ruby feature flags
1170#[allow(dead_code)]
1171#[derive(Debug, Clone, Default)]
1172pub struct RubyFeatureFlags {
1173    pub use_data_class: bool,
1174    pub use_pattern_matching: bool,
1175    pub use_numbered_params: bool,
1176    pub use_hash_shorthand: bool,
1177}
1178/// Ruby code generation backend.
1179///
1180/// Compiles LCNF function declarations to Ruby methods and bundles
1181/// them into a `RubyModule`.
1182pub struct RubyBackend {
1183    /// Counter for fresh temporary variable names.
1184    pub(super) tmp_counter: u64,
1185    /// Mangle cache.
1186    pub(super) mangle_cache: std::collections::HashMap<std::string::String, std::string::String>,
1187}
1188impl RubyBackend {
1189    /// Create a new `RubyBackend`.
1190    pub fn new() -> Self {
1191        RubyBackend {
1192            tmp_counter: 0,
1193            mangle_cache: std::collections::HashMap::new(),
1194        }
1195    }
1196    /// Generate a fresh temporary variable name.
1197    pub(super) fn fresh_tmp(&mut self) -> std::string::String {
1198        let n = self.tmp_counter;
1199        self.tmp_counter += 1;
1200        format!("_t{}", n)
1201    }
1202    /// Mangle an LCNF name to a valid Ruby identifier (snake_case).
1203    pub fn mangle_name(&mut self, name: &str) -> std::string::String {
1204        if let Some(cached) = self.mangle_cache.get(name) {
1205            return cached.clone();
1206        }
1207        let result = ruby_mangle(name);
1208        self.mangle_cache.insert(name.to_string(), result.clone());
1209        result
1210    }
1211    /// Compile an LCNF function declaration to a `RubyMethod`.
1212    pub fn compile_decl(&mut self, decl: &LcnfFunDecl) -> Result<RubyMethod, std::string::String> {
1213        let method_name = self.mangle_name(&decl.name);
1214        let params: Vec<std::string::String> = decl
1215            .params
1216            .iter()
1217            .map(|p| {
1218                if p.name.is_empty() || p.name == "_" {
1219                    format!("_x{}", p.id.0)
1220                } else {
1221                    ruby_mangle(&p.name)
1222                }
1223            })
1224            .collect();
1225        let mut stmts: Vec<RubyStmt> = Vec::new();
1226        let result_expr = self.compile_expr(&decl.body, &mut stmts)?;
1227        let already_returns = matches!(stmts.last(), Some(RubyStmt::Return(_)));
1228        if !already_returns {
1229            stmts.push(RubyStmt::Return(result_expr));
1230        }
1231        Ok(RubyMethod {
1232            name: method_name,
1233            params: params.iter().map(|s| s.to_string()).collect(),
1234            body: stmts,
1235            visibility: RubyVisibility::Public,
1236        })
1237    }
1238    /// Compile a complete set of LCNF declarations to a Ruby source string.
1239    pub fn emit_module(decls: &[LcnfFunDecl]) -> Result<std::string::String, std::string::String> {
1240        let mut backend = RubyBackend::new();
1241        let mut module = RubyModule::new("OxiLean");
1242        module.module_function = true;
1243        let mut ctor_names: HashSet<std::string::String> = HashSet::new();
1244        for decl in decls {
1245            collect_ctor_names_from_expr(&decl.body, &mut ctor_names);
1246        }
1247        for ctor in &ctor_names {
1248            let class_name = ruby_const_name(ctor);
1249            let mut class = RubyClass::new(&class_name);
1250            class.superclass = Some("Data".to_string());
1251            module.constants.push((
1252                class_name.clone(),
1253                RubyExpr::MethodCall(
1254                    Box::new(RubyExpr::Var("Data".to_string())),
1255                    "define".to_string(),
1256                    vec![
1257                        RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1258                        RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1259                    ],
1260                ),
1261            ));
1262        }
1263        for decl in decls {
1264            let method = backend.compile_decl(decl)?;
1265            module.functions.push(method);
1266        }
1267        let mut source = RUBY_RUNTIME.to_string();
1268        source.push('\n');
1269        source.push_str(&module.emit());
1270        Ok(source)
1271    }
1272    /// Compile an LCNF expression to a Ruby expression, pushing any needed
1273    /// intermediate statements into `stmts`.
1274    pub(super) fn compile_expr(
1275        &mut self,
1276        expr: &LcnfExpr,
1277        stmts: &mut Vec<RubyStmt>,
1278    ) -> Result<RubyExpr, std::string::String> {
1279        match expr {
1280            LcnfExpr::Return(arg) => Ok(self.compile_arg(arg)),
1281            LcnfExpr::Unreachable => {
1282                let raise_call = RubyExpr::Call(
1283                    "raise".to_string(),
1284                    vec![
1285                        RubyExpr::Var("RuntimeError".to_string()),
1286                        RubyExpr::Lit(RubyLit::Str("OxiLean: unreachable".to_string())),
1287                    ],
1288                );
1289                stmts.push(RubyStmt::Expr(raise_call));
1290                Ok(RubyExpr::Lit(RubyLit::Nil))
1291            }
1292            LcnfExpr::TailCall(func, args) => {
1293                let callee = self.compile_arg(func);
1294                let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1295                match callee {
1296                    RubyExpr::Var(name) => Ok(RubyExpr::Call(name, rb_args)),
1297                    other => Ok(RubyExpr::MethodCall(
1298                        Box::new(other),
1299                        "call".to_string(),
1300                        rb_args,
1301                    )),
1302                }
1303            }
1304            LcnfExpr::Let {
1305                id,
1306                name,
1307                ty: _,
1308                value,
1309                body,
1310            } => {
1311                let var_name = if name.is_empty() || name == "_" {
1312                    format!("_x{}", id.0)
1313                } else {
1314                    ruby_mangle(name)
1315                };
1316                let val_expr = self.compile_let_value(value)?;
1317                stmts.push(RubyStmt::Assign(var_name, val_expr));
1318                self.compile_expr(body, stmts)
1319            }
1320            LcnfExpr::Case {
1321                scrutinee,
1322                alts,
1323                default,
1324                ..
1325            } => {
1326                let scrutinee_expr = RubyExpr::Var(format!("_x{}", scrutinee.0));
1327                let tag_expr = RubyExpr::MethodCall(
1328                    Box::new(scrutinee_expr.clone()),
1329                    "tag".to_string(),
1330                    vec![],
1331                );
1332                let result_var = self.fresh_tmp();
1333                stmts.push(RubyStmt::Assign(
1334                    result_var.clone(),
1335                    RubyExpr::Lit(RubyLit::Nil),
1336                ));
1337                let mut when_branches: Vec<(RubyExpr, Vec<RubyStmt>)> = Vec::new();
1338                for alt in alts {
1339                    let mut branch_stmts: Vec<RubyStmt> = Vec::new();
1340                    for (field_idx, param) in alt.params.iter().enumerate() {
1341                        let field_var = if param.name.is_empty() || param.name == "_" {
1342                            format!("_x{}", param.id.0)
1343                        } else {
1344                            ruby_mangle(&param.name)
1345                        };
1346                        let field_access = RubyExpr::MethodCall(
1347                            Box::new(RubyExpr::MethodCall(
1348                                Box::new(scrutinee_expr.clone()),
1349                                "fields".to_string(),
1350                                vec![],
1351                            )),
1352                            "[]".to_string(),
1353                            vec![RubyExpr::Lit(RubyLit::Int(field_idx as i64))],
1354                        );
1355                        branch_stmts.push(RubyStmt::Assign(field_var, field_access));
1356                    }
1357                    let branch_result = self.compile_expr(&alt.body, &mut branch_stmts)?;
1358                    branch_stmts.push(RubyStmt::Assign(result_var.clone(), branch_result));
1359                    when_branches.push((
1360                        RubyExpr::Lit(RubyLit::Int(alt.ctor_tag as i64)),
1361                        branch_stmts,
1362                    ));
1363                }
1364                let mut default_stmts: Vec<RubyStmt> = Vec::new();
1365                if let Some(def) = default {
1366                    let def_result = self.compile_expr(def, &mut default_stmts)?;
1367                    default_stmts.push(RubyStmt::Assign(result_var.clone(), def_result));
1368                } else {
1369                    default_stmts.push(RubyStmt::Expr(RubyExpr::Call(
1370                        "raise".to_string(),
1371                        vec![
1372                            RubyExpr::Var("RuntimeError".to_string()),
1373                            RubyExpr::Lit(RubyLit::Str("OxiLean: unreachable".to_string())),
1374                        ],
1375                    )));
1376                }
1377                let mut all_stmts_flat: Vec<RubyStmt> = Vec::new();
1378                if when_branches.is_empty() {
1379                    for s in default_stmts {
1380                        all_stmts_flat.push(s);
1381                    }
1382                } else {
1383                    let (first_pat, first_body) = when_branches.remove(0);
1384                    let cond = RubyExpr::BinOp(
1385                        "==".to_string(),
1386                        Box::new(tag_expr.clone()),
1387                        Box::new(first_pat),
1388                    );
1389                    let elsif: Vec<(RubyExpr, Vec<RubyStmt>)> = when_branches
1390                        .into_iter()
1391                        .map(|(pat, body)| {
1392                            let c = RubyExpr::BinOp(
1393                                "==".to_string(),
1394                                Box::new(tag_expr.clone()),
1395                                Box::new(pat),
1396                            );
1397                            (c, body)
1398                        })
1399                        .collect();
1400                    all_stmts_flat.push(RubyStmt::If(cond, first_body, elsif, Some(default_stmts)));
1401                }
1402                for s in all_stmts_flat {
1403                    stmts.push(s);
1404                }
1405                Ok(RubyExpr::Var(result_var))
1406            }
1407        }
1408    }
1409    /// Compile an LCNF let-value to a Ruby expression.
1410    pub(super) fn compile_let_value(
1411        &mut self,
1412        value: &LcnfLetValue,
1413    ) -> Result<RubyExpr, std::string::String> {
1414        match value {
1415            LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
1416            LcnfLetValue::Erased => Ok(RubyExpr::Lit(RubyLit::Nil)),
1417            LcnfLetValue::FVar(id) => Ok(RubyExpr::Var(format!("_x{}", id.0))),
1418            LcnfLetValue::App(func, args) => {
1419                let callee = self.compile_arg(func);
1420                let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1421                match callee {
1422                    RubyExpr::Var(name) => Ok(RubyExpr::Call(name, rb_args)),
1423                    other => Ok(RubyExpr::MethodCall(
1424                        Box::new(other),
1425                        "call".to_string(),
1426                        rb_args,
1427                    )),
1428                }
1429            }
1430            LcnfLetValue::Proj(_name, idx, var) => {
1431                let base = RubyExpr::Var(format!("_x{}", var.0));
1432                Ok(RubyExpr::MethodCall(
1433                    Box::new(RubyExpr::MethodCall(
1434                        Box::new(base),
1435                        "fields".to_string(),
1436                        vec![],
1437                    )),
1438                    "[]".to_string(),
1439                    vec![RubyExpr::Lit(RubyLit::Int(*idx as i64))],
1440                ))
1441            }
1442            LcnfLetValue::Ctor(name, tag, args) => {
1443                let class_name = ruby_const_name(name);
1444                let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1445                Ok(RubyExpr::MethodCall(
1446                    Box::new(RubyExpr::Var(class_name)),
1447                    "new".to_string(),
1448                    vec![RubyExpr::Hash(vec![
1449                        (
1450                            RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1451                            RubyExpr::Lit(RubyLit::Int(*tag as i64)),
1452                        ),
1453                        (
1454                            RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1455                            RubyExpr::Array(rb_args),
1456                        ),
1457                    ])],
1458                ))
1459            }
1460            LcnfLetValue::Reset(_var) => Ok(RubyExpr::Lit(RubyLit::Nil)),
1461            LcnfLetValue::Reuse(_slot, name, tag, args) => {
1462                let class_name = ruby_const_name(name);
1463                let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1464                Ok(RubyExpr::MethodCall(
1465                    Box::new(RubyExpr::Var(class_name)),
1466                    "new".to_string(),
1467                    vec![RubyExpr::Hash(vec![
1468                        (
1469                            RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1470                            RubyExpr::Lit(RubyLit::Int(*tag as i64)),
1471                        ),
1472                        (
1473                            RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1474                            RubyExpr::Array(rb_args),
1475                        ),
1476                    ])],
1477                ))
1478            }
1479        }
1480    }
1481    /// Compile an LCNF argument to a Ruby expression.
1482    pub(super) fn compile_arg(&self, arg: &LcnfArg) -> RubyExpr {
1483        match arg {
1484            LcnfArg::Var(id) => RubyExpr::Var(format!("_x{}", id.0)),
1485            LcnfArg::Lit(lit) => self.compile_lit(lit),
1486            LcnfArg::Erased => RubyExpr::Lit(RubyLit::Nil),
1487            LcnfArg::Type(_) => RubyExpr::Lit(RubyLit::Nil),
1488        }
1489    }
1490    /// Compile an LCNF literal to a Ruby expression.
1491    pub(super) fn compile_lit(&self, lit: &LcnfLit) -> RubyExpr {
1492        match lit {
1493            LcnfLit::Nat(n) => RubyExpr::Lit(RubyLit::Int(*n as i64)),
1494            LcnfLit::Str(s) => RubyExpr::Lit(RubyLit::Str(s.clone())),
1495        }
1496    }
1497}
1498#[allow(dead_code)]
1499#[derive(Debug, Clone, Default)]
1500pub struct RubyPassStats {
1501    pub total_runs: u32,
1502    pub successful_runs: u32,
1503    pub total_changes: u64,
1504    pub time_ms: u64,
1505    pub iterations_used: u32,
1506}
1507impl RubyPassStats {
1508    #[allow(dead_code)]
1509    pub fn new() -> Self {
1510        Self::default()
1511    }
1512    #[allow(dead_code)]
1513    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1514        self.total_runs += 1;
1515        self.successful_runs += 1;
1516        self.total_changes += changes;
1517        self.time_ms += time_ms;
1518        self.iterations_used = iterations;
1519    }
1520    #[allow(dead_code)]
1521    pub fn average_changes_per_run(&self) -> f64 {
1522        if self.total_runs == 0 {
1523            return 0.0;
1524        }
1525        self.total_changes as f64 / self.total_runs as f64
1526    }
1527    #[allow(dead_code)]
1528    pub fn success_rate(&self) -> f64 {
1529        if self.total_runs == 0 {
1530            return 0.0;
1531        }
1532        self.successful_runs as f64 / self.total_runs as f64
1533    }
1534    #[allow(dead_code)]
1535    pub fn format_summary(&self) -> String {
1536        format!(
1537            "Runs: {}/{}, Changes: {}, Time: {}ms",
1538            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1539        )
1540    }
1541}
1542#[allow(dead_code)]
1543#[derive(Debug, Default)]
1544pub struct RubyDiagSink {
1545    pub diags: Vec<(RubyDiagLevel, String)>,
1546}
1547#[allow(dead_code)]
1548impl RubyDiagSink {
1549    pub fn new() -> Self {
1550        Self::default()
1551    }
1552    pub fn push(&mut self, level: RubyDiagLevel, msg: &str) {
1553        self.diags.push((level, msg.to_string()));
1554    }
1555    pub fn has_errors(&self) -> bool {
1556        self.diags.iter().any(|(l, _)| *l == RubyDiagLevel::Error)
1557    }
1558}