Skip to main content

oxilean_codegen/ruby_backend/
functions.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::types::{
9    RubyAnalysisCache, RubyBackend, RubyClass, RubyConstantFoldingHelper, RubyDepGraph,
10    RubyDominatorTree, RubyExpr, RubyLit, RubyLivenessInfo, RubyMethod, RubyModule, RubyPassConfig,
11    RubyPassPhase, RubyPassRegistry, RubyPassStats, RubyStmt, RubyType, RubyVisibility,
12    RubyWorklist,
13};
14use std::fmt;
15
16/// Map an LCNF type to a Ruby type annotation.
17pub(super) fn lcnf_type_to_ruby(ty: &LcnfType) -> RubyType {
18    match ty {
19        LcnfType::Nat => RubyType::Integer,
20        LcnfType::LcnfString => RubyType::String,
21        LcnfType::Unit | LcnfType::Erased | LcnfType::Irrelevant => RubyType::Nil,
22        LcnfType::Object => RubyType::Object("Object".to_string()),
23        LcnfType::Var(name) => RubyType::Object(name.clone()),
24        LcnfType::Fun(_, _) => RubyType::Proc,
25        LcnfType::Ctor(name, _) => RubyType::Object(ruby_const_name(name)),
26    }
27}
28pub(super) fn fmt_ruby_stmt(
29    stmt: &RubyStmt,
30    indent: &str,
31    f: &mut fmt::Formatter<'_>,
32) -> fmt::Result {
33    let inner = format!("{}  ", indent);
34    match stmt {
35        RubyStmt::Expr(expr) => writeln!(f, "{}{}", indent, expr),
36        RubyStmt::Assign(name, expr) => writeln!(f, "{}{} = {}", indent, name, expr),
37        RubyStmt::Return(expr) => writeln!(f, "{}return {}", indent, expr),
38        RubyStmt::Def(method) => fmt_ruby_method(method, indent, f),
39        RubyStmt::Class(class) => fmt_ruby_class(class, indent, f),
40        RubyStmt::Mod(module) => fmt_ruby_module_stmt(module, indent, f),
41        RubyStmt::If(cond, then_stmts, elsif_branches, else_stmts) => {
42            writeln!(f, "{}if {}", indent, cond)?;
43            for s in then_stmts {
44                fmt_ruby_stmt(s, &inner, f)?;
45            }
46            for (elsif_cond, elsif_body) in elsif_branches {
47                writeln!(f, "{}elsif {}", indent, elsif_cond)?;
48                for s in elsif_body {
49                    fmt_ruby_stmt(s, &inner, f)?;
50                }
51            }
52            if let Some(else_body) = else_stmts {
53                writeln!(f, "{}else", indent)?;
54                for s in else_body {
55                    fmt_ruby_stmt(s, &inner, f)?;
56                }
57            }
58            writeln!(f, "{}end", indent)
59        }
60        RubyStmt::While(cond, body) => {
61            writeln!(f, "{}while {}", indent, cond)?;
62            for s in body {
63                fmt_ruby_stmt(s, &inner, f)?;
64            }
65            writeln!(f, "{}end", indent)
66        }
67        RubyStmt::Begin(body, rescue, ensure) => {
68            writeln!(f, "{}begin", indent)?;
69            for s in body {
70                fmt_ruby_stmt(s, &inner, f)?;
71            }
72            if let Some((exc_var, rescue_body)) = rescue {
73                writeln!(f, "{}rescue => {}", indent, exc_var)?;
74                for s in rescue_body {
75                    fmt_ruby_stmt(s, &inner, f)?;
76                }
77            }
78            if let Some(ensure_body) = ensure {
79                writeln!(f, "{}ensure", indent)?;
80                for s in ensure_body {
81                    fmt_ruby_stmt(s, &inner, f)?;
82                }
83            }
84            writeln!(f, "{}end", indent)
85        }
86    }
87}
88pub(super) fn fmt_ruby_method(
89    method: &RubyMethod,
90    indent: &str,
91    f: &mut fmt::Formatter<'_>,
92) -> fmt::Result {
93    let inner = format!("{}  ", indent);
94    if method.visibility != RubyVisibility::Public {
95        writeln!(f, "{}{}", indent, method.visibility)?;
96    }
97    write!(f, "{}def {}", indent, method.name)?;
98    if !method.params.is_empty() {
99        write!(f, "({})", method.params.join(", "))?;
100    }
101    writeln!(f)?;
102    for stmt in &method.body {
103        fmt_ruby_stmt(stmt, &inner, f)?;
104    }
105    writeln!(f, "{}end", indent)
106}
107pub(super) fn fmt_ruby_class(
108    class: &RubyClass,
109    indent: &str,
110    f: &mut fmt::Formatter<'_>,
111) -> fmt::Result {
112    let inner = format!("{}  ", indent);
113    match &class.superclass {
114        Some(sup) => writeln!(f, "{}class {} < {}", indent, class.name, sup)?,
115        None => writeln!(f, "{}class {}", indent, class.name)?,
116    }
117    if !class.attr_readers.is_empty() {
118        let readers: Vec<&str> = class.attr_readers.iter().map(|s| s.as_str()).collect();
119        let syms: Vec<std::string::String> = readers.iter().map(|s| format!(":{}", s)).collect();
120        writeln!(f, "{}attr_reader {}", inner, syms.join(", "))?;
121    }
122    if !class.attr_writers.is_empty() {
123        let syms: Vec<std::string::String> = class
124            .attr_writers
125            .iter()
126            .map(|s| format!(":{}", s))
127            .collect();
128        writeln!(f, "{}attr_writer {}", inner, syms.join(", "))?;
129    }
130    for method in &class.class_methods {
131        let self_method = RubyMethod {
132            name: format!("self.{}", method.name),
133            ..method.clone()
134        };
135        fmt_ruby_method(&self_method, &inner, f)?;
136    }
137    for method in &class.methods {
138        fmt_ruby_method(method, &inner, f)?;
139    }
140    writeln!(f, "{}end", indent)
141}
142pub(super) fn fmt_ruby_module_stmt(
143    module: &RubyModule,
144    indent: &str,
145    f: &mut fmt::Formatter<'_>,
146) -> fmt::Result {
147    let inner = format!("{}  ", indent);
148    writeln!(f, "{}module {}", indent, module.name)?;
149    for (name, expr) in &module.constants {
150        writeln!(f, "{}{} = {}", inner, name, expr)?;
151    }
152    if !module.functions.is_empty() {
153        if module.module_function {
154            writeln!(f, "{}module_function", inner)?;
155            writeln!(f)?;
156        }
157        for method in &module.functions {
158            fmt_ruby_method(method, &inner, f)?;
159        }
160    }
161    for class in &module.classes {
162        fmt_ruby_class(class, &inner, f)?;
163    }
164    writeln!(f, "{}end", indent)
165}
166/// Ruby reserved keywords that cannot be used as identifiers.
167const RUBY_KEYWORDS: &[&str] = &[
168    "alias", "and", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif",
169    "end", "ensure", "false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo",
170    "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", "until",
171    "when", "while", "yield",
172];
173/// Convert a potentially dotted/primed LCNF name to a valid Ruby snake_case identifier.
174pub(super) fn ruby_mangle(name: &str) -> std::string::String {
175    if name.is_empty() {
176        return "_anon".to_string();
177    }
178    let mut result = std::string::String::new();
179    for c in name.chars() {
180        match c {
181            '.' | ':' => result.push('_'),
182            '\'' => result.push('_'),
183            c if c.is_alphanumeric() || c == '_' => result.push(c),
184            _ => result.push('_'),
185        }
186    }
187    if result.starts_with(|c: char| c.is_ascii_digit()) {
188        result.insert(0, '_');
189    }
190    if RUBY_KEYWORDS.contains(&result.as_str()) {
191        result.insert(0, '_');
192    }
193    result
194}
195/// Convert a constructor/class name to Ruby CamelCase constant.
196pub(super) fn ruby_const_name(name: &str) -> std::string::String {
197    if name.is_empty() {
198        return "Anon".to_string();
199    }
200    let parts: Vec<&str> = name.split(['.', '_']).collect();
201    parts
202        .iter()
203        .map(|p| {
204            let mut s = std::string::String::new();
205            let mut first = true;
206            for c in p.chars() {
207                if first {
208                    for upper in c.to_uppercase() {
209                        s.push(upper);
210                    }
211                    first = false;
212                } else {
213                    s.push(c);
214                }
215            }
216            s
217        })
218        .collect::<Vec<_>>()
219        .join("")
220}
221pub(super) fn collect_ctor_names_from_expr(
222    expr: &LcnfExpr,
223    out: &mut HashSet<std::string::String>,
224) {
225    match expr {
226        LcnfExpr::Let { value, body, .. } => {
227            collect_ctor_names_from_value(value, out);
228            collect_ctor_names_from_expr(body, out);
229        }
230        LcnfExpr::Case { alts, default, .. } => {
231            for alt in alts {
232                out.insert(alt.ctor_name.clone());
233                collect_ctor_names_from_expr(&alt.body, out);
234            }
235            if let Some(d) = default {
236                collect_ctor_names_from_expr(d, out);
237            }
238        }
239        LcnfExpr::Return(_) | LcnfExpr::Unreachable | LcnfExpr::TailCall(_, _) => {}
240    }
241}
242pub(super) fn collect_ctor_names_from_value(
243    value: &LcnfLetValue,
244    out: &mut HashSet<std::string::String>,
245) {
246    match value {
247        LcnfLetValue::Ctor(name, _, _) | LcnfLetValue::Reuse(_, name, _, _) => {
248            out.insert(name.clone());
249        }
250        _ => {}
251    }
252}
253/// Minimal Ruby runtime module emitted before the generated code.
254pub const RUBY_RUNTIME: &str = r#"# frozen_string_literal: true
255# OxiLean Ruby Runtime — auto-generated
256
257module OxiLeanRuntime
258  module_function
259
260  # Natural-number addition (Ruby Integer is arbitrary-precision)
261  def nat_add(a, b) = a + b
262
263  # Natural-number saturating subtraction
264  def nat_sub(a, b) = [0, a - b].max
265
266  # Natural-number multiplication
267  def nat_mul(a, b) = a * b
268
269  # Natural-number division (truncating, div-by-zero → 0)
270  def nat_div(a, b) = b.zero? ? 0 : a / b
271
272  # Natural-number modulo (div-by-zero → a)
273  def nat_mod(a, b) = b.zero? ? a : a % b
274
275  # Decidable boolean → 0/1 as Integer
276  def decide(b) = b ? 1 : 0
277
278  # Natural number to string
279  def nat_to_string(n) = n.to_s
280
281  # String append
282  def str_append(a, b) = a + b
283
284  # String length
285  def str_length(s) = s.length
286
287  # List cons: prepend element
288  def cons(head, tail) = [head, *tail]
289
290  # List nil: empty list
291  def nil_list = []
292
293  # Pair constructor
294  def mk_pair(a, b) = [a, b]
295
296  # Unreachable branch
297  def unreachable! = raise(RuntimeError, "OxiLean: unreachable code reached")
298end
299"#;
300#[cfg(test)]
301mod tests {
302    use super::*;
303    #[test]
304    pub(super) fn test_ruby_type_display_integer() {
305        assert_eq!(RubyType::Integer.to_string(), "Integer");
306    }
307    #[test]
308    pub(super) fn test_ruby_type_display_float() {
309        assert_eq!(RubyType::Float.to_string(), "Float");
310    }
311    #[test]
312    pub(super) fn test_ruby_type_display_array() {
313        let ty = RubyType::Array(Box::new(RubyType::Integer));
314        assert_eq!(ty.to_string(), "Array[Integer]");
315    }
316    #[test]
317    pub(super) fn test_ruby_type_display_hash() {
318        let ty = RubyType::Hash(Box::new(RubyType::Symbol), Box::new(RubyType::String));
319        assert_eq!(ty.to_string(), "Hash[Symbol, String]");
320    }
321    #[test]
322    pub(super) fn test_ruby_type_display_proc() {
323        assert_eq!(RubyType::Proc.to_string(), "Proc");
324    }
325    #[test]
326    pub(super) fn test_ruby_lit_int() {
327        assert_eq!(RubyLit::Int(42).to_string(), "42");
328        assert_eq!(RubyLit::Int(-7).to_string(), "-7");
329    }
330    #[test]
331    pub(super) fn test_ruby_lit_float() {
332        assert_eq!(RubyLit::Float(1.0).to_string(), "1.0");
333        assert_eq!(RubyLit::Float(3.14).to_string(), "3.14");
334    }
335    #[test]
336    pub(super) fn test_ruby_lit_str_escape() {
337        let lit = RubyLit::Str("hello \"world\"\nnewline".to_string());
338        assert_eq!(lit.to_string(), "\"hello \\\"world\\\"\\nnewline\"");
339    }
340    #[test]
341    pub(super) fn test_ruby_lit_str_hash_escape() {
342        let lit = RubyLit::Str("a#b".to_string());
343        assert_eq!(lit.to_string(), "\"a\\#b\"");
344    }
345    #[test]
346    pub(super) fn test_ruby_lit_bool() {
347        assert_eq!(RubyLit::Bool(true).to_string(), "true");
348        assert_eq!(RubyLit::Bool(false).to_string(), "false");
349    }
350    #[test]
351    pub(super) fn test_ruby_lit_nil() {
352        assert_eq!(RubyLit::Nil.to_string(), "nil");
353    }
354    #[test]
355    pub(super) fn test_ruby_lit_symbol() {
356        assert_eq!(RubyLit::Symbol("foo".to_string()).to_string(), ":foo");
357    }
358    #[test]
359    pub(super) fn test_ruby_expr_binop() {
360        let expr = RubyExpr::BinOp(
361            "+".to_string(),
362            Box::new(RubyExpr::Lit(RubyLit::Int(1))),
363            Box::new(RubyExpr::Lit(RubyLit::Int(2))),
364        );
365        assert_eq!(expr.to_string(), "(1 + 2)");
366    }
367    #[test]
368    pub(super) fn test_ruby_expr_call() {
369        let expr = RubyExpr::Call(
370            "puts".to_string(),
371            vec![RubyExpr::Lit(RubyLit::Str("hi".to_string()))],
372        );
373        assert_eq!(expr.to_string(), "puts(\"hi\")");
374    }
375    #[test]
376    pub(super) fn test_ruby_expr_method_call() {
377        let expr = RubyExpr::MethodCall(
378            Box::new(RubyExpr::Var("arr".to_string())),
379            "map".to_string(),
380            vec![RubyExpr::Lit(RubyLit::Symbol("to_s".to_string()))],
381        );
382        assert_eq!(expr.to_string(), "arr.map(:to_s)");
383    }
384    #[test]
385    pub(super) fn test_ruby_expr_array() {
386        let expr = RubyExpr::Array(vec![
387            RubyExpr::Lit(RubyLit::Int(1)),
388            RubyExpr::Lit(RubyLit::Int(2)),
389            RubyExpr::Lit(RubyLit::Int(3)),
390        ]);
391        assert_eq!(expr.to_string(), "[1, 2, 3]");
392    }
393    #[test]
394    pub(super) fn test_ruby_expr_hash_symbol_key() {
395        let expr = RubyExpr::Hash(vec![(
396            RubyExpr::Lit(RubyLit::Symbol("name".to_string())),
397            RubyExpr::Lit(RubyLit::Str("Alice".to_string())),
398        )]);
399        assert_eq!(expr.to_string(), "{name: \"Alice\"}");
400    }
401    #[test]
402    pub(super) fn test_ruby_expr_hash_string_key() {
403        let expr = RubyExpr::Hash(vec![(
404            RubyExpr::Lit(RubyLit::Str("key".to_string())),
405            RubyExpr::Lit(RubyLit::Int(42)),
406        )]);
407        assert_eq!(expr.to_string(), "{\"key\" => 42}");
408    }
409    #[test]
410    pub(super) fn test_ruby_expr_lambda() {
411        let expr = RubyExpr::Lambda(
412            vec!["x".to_string(), "y".to_string()],
413            vec![RubyStmt::Return(RubyExpr::BinOp(
414                "+".to_string(),
415                Box::new(RubyExpr::Var("x".to_string())),
416                Box::new(RubyExpr::Var("y".to_string())),
417            ))],
418        );
419        let s = expr.to_string();
420        assert!(s.contains("->(x, y)"), "Expected lambda params, got: {}", s);
421        assert!(s.contains("x + y"), "Expected body, got: {}", s);
422    }
423    #[test]
424    pub(super) fn test_ruby_expr_ternary() {
425        let expr = RubyExpr::If(
426            Box::new(RubyExpr::Var("flag".to_string())),
427            Box::new(RubyExpr::Lit(RubyLit::Int(1))),
428            Box::new(RubyExpr::Lit(RubyLit::Int(0))),
429        );
430        assert_eq!(expr.to_string(), "(flag ? 1 : 0)");
431    }
432    #[test]
433    pub(super) fn test_ruby_method_display() {
434        let method = RubyMethod::new(
435            "add",
436            vec!["a", "b"],
437            vec![RubyStmt::Return(RubyExpr::BinOp(
438                "+".to_string(),
439                Box::new(RubyExpr::Var("a".to_string())),
440                Box::new(RubyExpr::Var("b".to_string())),
441            ))],
442        );
443        let s = method.to_string();
444        assert!(s.contains("def add(a, b)"), "Expected def, got: {}", s);
445        assert!(s.contains("return (a + b)"), "Expected return, got: {}", s);
446        assert!(s.contains("end"), "Expected end, got: {}", s);
447    }
448    #[test]
449    pub(super) fn test_ruby_method_private_display() {
450        let method = RubyMethod::private("secret", vec![], vec![]);
451        let s = method.to_string();
452        assert!(
453            s.contains("private"),
454            "Expected private visibility, got: {}",
455            s
456        );
457        assert!(s.contains("def secret"), "Expected def secret, got: {}", s);
458    }
459    #[test]
460    pub(super) fn test_ruby_class_display() {
461        let mut class = RubyClass::new("Animal");
462        class.add_attr_reader("name");
463        class.add_method(RubyMethod::new(
464            "speak",
465            vec![],
466            vec![RubyStmt::Return(RubyExpr::Lit(RubyLit::Str(
467                "...".to_string(),
468            )))],
469        ));
470        let s = class.to_string();
471        assert!(
472            s.contains("class Animal"),
473            "Expected class Animal, got: {}",
474            s
475        );
476        assert!(
477            s.contains("attr_reader :name"),
478            "Expected attr_reader, got: {}",
479            s
480        );
481        assert!(s.contains("def speak"), "Expected def speak, got: {}", s);
482        assert!(s.contains("end"), "Expected end, got: {}", s);
483    }
484    #[test]
485    pub(super) fn test_ruby_class_with_superclass() {
486        let class = RubyClass::new("Dog").with_superclass("Animal");
487        let s = class.to_string();
488        assert!(
489            s.contains("class Dog < Animal"),
490            "Expected inheritance, got: {}",
491            s
492        );
493    }
494    #[test]
495    pub(super) fn test_ruby_module_emit_frozen_literal() {
496        let module = RubyModule::new("MyLib");
497        let src = module.emit();
498        assert!(
499            src.starts_with("# frozen_string_literal: true"),
500            "Expected frozen string literal pragma, got: {}",
501            &src[..50.min(src.len())]
502        );
503    }
504    #[test]
505    pub(super) fn test_ruby_module_emit_structure() {
506        let mut module = RubyModule::new("OxiLean");
507        module.functions.push(RubyMethod::new(
508            "hello",
509            vec![],
510            vec![RubyStmt::Return(RubyExpr::Lit(RubyLit::Str(
511                "world".to_string(),
512            )))],
513        ));
514        let src = module.emit();
515        assert!(
516            src.contains("module OxiLean"),
517            "Expected module OxiLean, got: {}",
518            src
519        );
520        assert!(
521            src.contains("module_function"),
522            "Expected module_function, got: {}",
523            src
524        );
525        assert!(
526            src.contains("def hello"),
527            "Expected def hello, got: {}",
528            src
529        );
530        assert!(src.contains("end"), "Expected end, got: {}", src);
531    }
532    #[test]
533    pub(super) fn test_ruby_mangle_dotted() {
534        assert_eq!(ruby_mangle("Nat.add"), "Nat_add");
535        assert_eq!(ruby_mangle("List.cons"), "List_cons");
536    }
537    #[test]
538    pub(super) fn test_ruby_mangle_prime() {
539        assert_eq!(ruby_mangle("foo'"), "foo_");
540    }
541    #[test]
542    pub(super) fn test_ruby_mangle_keyword() {
543        assert_eq!(ruby_mangle("return"), "_return");
544        assert_eq!(ruby_mangle("class"), "_class");
545        assert_eq!(ruby_mangle("end"), "_end");
546    }
547    #[test]
548    pub(super) fn test_ruby_mangle_empty() {
549        assert_eq!(ruby_mangle(""), "_anon");
550    }
551    #[test]
552    pub(super) fn test_ruby_const_name() {
553        assert_eq!(ruby_const_name("some"), "Some");
554        assert_eq!(ruby_const_name("list.nil"), "ListNil");
555        assert_eq!(ruby_const_name("nat_add"), "NatAdd");
556    }
557    #[test]
558    pub(super) fn test_compile_simple_decl() {
559        let decl = LcnfFunDecl {
560            name: "answer".to_string(),
561            original_name: None,
562            params: vec![],
563            ret_type: LcnfType::Nat,
564            body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(42))),
565            is_recursive: false,
566            is_lifted: false,
567            inline_cost: 0,
568        };
569        let mut backend = RubyBackend::new();
570        let method = backend.compile_decl(&decl).expect("compile failed");
571        assert_eq!(method.name, "answer");
572        assert!(method.params.is_empty());
573        let s = method.to_string();
574        assert!(s.contains("return 42"), "Expected return 42, got: {}", s);
575    }
576    #[test]
577    pub(super) fn test_compile_let_binding() {
578        let x_id = LcnfVarId(0);
579        let y_id = LcnfVarId(1);
580        let decl = LcnfFunDecl {
581            name: "double".to_string(),
582            original_name: None,
583            params: vec![LcnfParam {
584                id: x_id,
585                name: "x".to_string(),
586                ty: LcnfType::Nat,
587                erased: false,
588                borrowed: false,
589            }],
590            ret_type: LcnfType::Nat,
591            body: LcnfExpr::Let {
592                id: y_id,
593                name: "y".to_string(),
594                ty: LcnfType::Nat,
595                value: LcnfLetValue::App(LcnfArg::Var(x_id), vec![LcnfArg::Var(x_id)]),
596                body: Box::new(LcnfExpr::Return(LcnfArg::Var(y_id))),
597            },
598            is_recursive: false,
599            is_lifted: false,
600            inline_cost: 0,
601        };
602        let mut backend = RubyBackend::new();
603        let method = backend.compile_decl(&decl).expect("compile failed");
604        let s = method.to_string();
605        assert!(
606            s.contains("def double(x)"),
607            "Expected def double(x), got: {}",
608            s
609        );
610        assert!(s.contains("y ="), "Expected y = assignment, got: {}", s);
611    }
612    #[test]
613    pub(super) fn test_emit_module() {
614        let decl = LcnfFunDecl {
615            name: "main".to_string(),
616            original_name: None,
617            params: vec![],
618            ret_type: LcnfType::Unit,
619            body: LcnfExpr::Return(LcnfArg::Erased),
620            is_recursive: false,
621            is_lifted: false,
622            inline_cost: 0,
623        };
624        let src = RubyBackend::emit_module(&[decl]).expect("emit failed");
625        assert!(src.contains("OxiLeanRuntime"), "Missing runtime module");
626        assert!(src.contains("nat_add"), "Missing nat_add runtime helper");
627        assert!(src.contains("def main"), "Missing main method");
628        assert!(src.contains("module OxiLean"), "Missing OxiLean module");
629    }
630    #[test]
631    pub(super) fn test_mangle_name_caching() {
632        let mut backend = RubyBackend::new();
633        let a = backend.mangle_name("Nat.add");
634        let b = backend.mangle_name("Nat.add");
635        assert_eq!(a, b);
636        assert_eq!(a, "Nat_add");
637    }
638    #[test]
639    pub(super) fn test_lcnf_type_nat_to_ruby() {
640        assert_eq!(lcnf_type_to_ruby(&LcnfType::Nat), RubyType::Integer);
641    }
642    #[test]
643    pub(super) fn test_lcnf_type_string_to_ruby() {
644        assert_eq!(lcnf_type_to_ruby(&LcnfType::LcnfString), RubyType::String);
645    }
646    #[test]
647    pub(super) fn test_lcnf_type_unit_to_ruby() {
648        assert_eq!(lcnf_type_to_ruby(&LcnfType::Unit), RubyType::Nil);
649    }
650}
651/// Ruby pass version
652#[allow(dead_code)]
653pub const RUBY_PASS_VERSION: &str = "1.0.0";
654/// Ruby version string
655#[allow(dead_code)]
656pub const RUBY_BACKEND_VERSION: &str = "1.0.0";
657/// Ruby min version supported
658#[allow(dead_code)]
659pub const RUBY_MIN_VERSION: &str = "3.0";
660/// Ruby frozen literal constants
661#[allow(dead_code)]
662pub fn ruby_frozen_str(s: &str) -> String {
663    format!("{}.freeze", s)
664}
665/// Ruby safe navigation operator
666#[allow(dead_code)]
667pub fn ruby_safe_nav(obj: &str, method: &str) -> String {
668    format!("{}?.{}", obj, method)
669}
670/// Ruby tap helper
671#[allow(dead_code)]
672pub fn ruby_tap(expr: &str, block: &str) -> String {
673    format!("{}.tap {{ |it| {} }}", expr, block)
674}
675/// Ruby then/yield_self
676#[allow(dead_code)]
677pub fn ruby_then(expr: &str, block: &str) -> String {
678    format!("{}.then {{ |it| {} }}", expr, block)
679}
680/// Ruby memoize pattern
681#[allow(dead_code)]
682pub fn ruby_memoize(ivar: &str, expr: &str) -> String {
683    format!("{} ||= {}", ivar, expr)
684}
685/// Ruby double splat
686#[allow(dead_code)]
687pub fn ruby_double_splat(hash: &str) -> String {
688    format!("**{}", hash)
689}
690/// Ruby format string (Kernel#format)
691#[allow(dead_code)]
692pub fn ruby_format(template: &str, args: &[&str]) -> String {
693    let args_str = args.join(", ");
694    format!("format({:?}, {})", template, args_str)
695}
696/// Ruby heredoc
697#[allow(dead_code)]
698pub fn ruby_heredoc(label: &str, content: &str) -> String {
699    format!("<<~{}\n{}{}\n", label, content, label)
700}
701/// Ruby method missing
702#[allow(dead_code)]
703pub fn ruby_method_missing(name_var: &str, args_var: &str, block_var: &str, body: &str) -> String {
704    format!(
705        "def method_missing({}, *{}, &{})\n  {}\nend",
706        name_var, args_var, block_var, body
707    )
708}
709/// Ruby respond_to_missing?
710#[allow(dead_code)]
711pub fn ruby_respond_to_missing(name_var: &str, include_private: &str, body: &str) -> String {
712    format!(
713        "def respond_to_missing?({}, {} = false)\n  {}\nend",
714        name_var, include_private, body
715    )
716}
717/// Ruby concurrent-ruby primitives
718#[allow(dead_code)]
719pub fn ruby_concurrent_promise(body: &str) -> String {
720    format!("Concurrent::Promise.execute {{ {} }}", body)
721}
722#[allow(dead_code)]
723pub fn ruby_concurrent_future(body: &str) -> String {
724    format!("Concurrent::Future.execute {{ {} }}", body)
725}
726/// Ruby Comparable mixin helper
727#[allow(dead_code)]
728pub fn ruby_comparable_impl(spaceship_body: &str) -> String {
729    format!(
730        "include Comparable\n\ndef <=>(other)\n  {}\nend",
731        spaceship_body
732    )
733}
734/// Ruby Enumerable mixin helper
735#[allow(dead_code)]
736pub fn ruby_enumerable_impl(each_body: &str) -> String {
737    format!(
738        "include Enumerable\n\ndef each(&block)\n  {}\nend",
739        each_body
740    )
741}
742/// Ruby ObjectSpace finalizer
743#[allow(dead_code)]
744pub fn ruby_define_finalizer(var: &str, finalizer: &str) -> String {
745    format!("ObjectSpace.define_finalizer({}, {})", var, finalizer)
746}
747/// Ruby backend version
748#[allow(dead_code)]
749pub const RUBY_BACKEND_PASS_VERSION: &str = "1.0.0";
750#[cfg(test)]
751mod Ruby_infra_tests {
752    use super::*;
753    #[test]
754    pub(super) fn test_pass_config() {
755        let config = RubyPassConfig::new("test_pass", RubyPassPhase::Transformation);
756        assert!(config.enabled);
757        assert!(config.phase.is_modifying());
758        assert_eq!(config.phase.name(), "transformation");
759    }
760    #[test]
761    pub(super) fn test_pass_stats() {
762        let mut stats = RubyPassStats::new();
763        stats.record_run(10, 100, 3);
764        stats.record_run(20, 200, 5);
765        assert_eq!(stats.total_runs, 2);
766        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
767        assert!((stats.success_rate() - 1.0).abs() < 0.01);
768        let s = stats.format_summary();
769        assert!(s.contains("Runs: 2/2"));
770    }
771    #[test]
772    pub(super) fn test_pass_registry() {
773        let mut reg = RubyPassRegistry::new();
774        reg.register(RubyPassConfig::new("pass_a", RubyPassPhase::Analysis));
775        reg.register(RubyPassConfig::new("pass_b", RubyPassPhase::Transformation).disabled());
776        assert_eq!(reg.total_passes(), 2);
777        assert_eq!(reg.enabled_count(), 1);
778        reg.update_stats("pass_a", 5, 50, 2);
779        let stats = reg.get_stats("pass_a").expect("stats should exist");
780        assert_eq!(stats.total_changes, 5);
781    }
782    #[test]
783    pub(super) fn test_analysis_cache() {
784        let mut cache = RubyAnalysisCache::new(10);
785        cache.insert("key1".to_string(), vec![1, 2, 3]);
786        assert!(cache.get("key1").is_some());
787        assert!(cache.get("key2").is_none());
788        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
789        cache.invalidate("key1");
790        assert!(!cache.entries["key1"].valid);
791        assert_eq!(cache.size(), 1);
792    }
793    #[test]
794    pub(super) fn test_worklist() {
795        let mut wl = RubyWorklist::new();
796        assert!(wl.push(1));
797        assert!(wl.push(2));
798        assert!(!wl.push(1));
799        assert_eq!(wl.len(), 2);
800        assert_eq!(wl.pop(), Some(1));
801        assert!(!wl.contains(1));
802        assert!(wl.contains(2));
803    }
804    #[test]
805    pub(super) fn test_dominator_tree() {
806        let mut dt = RubyDominatorTree::new(5);
807        dt.set_idom(1, 0);
808        dt.set_idom(2, 0);
809        dt.set_idom(3, 1);
810        assert!(dt.dominates(0, 3));
811        assert!(dt.dominates(1, 3));
812        assert!(!dt.dominates(2, 3));
813        assert!(dt.dominates(3, 3));
814    }
815    #[test]
816    pub(super) fn test_liveness() {
817        let mut liveness = RubyLivenessInfo::new(3);
818        liveness.add_def(0, 1);
819        liveness.add_use(1, 1);
820        assert!(liveness.defs[0].contains(&1));
821        assert!(liveness.uses[1].contains(&1));
822    }
823    #[test]
824    pub(super) fn test_constant_folding() {
825        assert_eq!(RubyConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
826        assert_eq!(RubyConstantFoldingHelper::fold_div_i64(10, 0), None);
827        assert_eq!(RubyConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
828        assert_eq!(
829            RubyConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
830            0b1000
831        );
832        assert_eq!(RubyConstantFoldingHelper::fold_bitnot_i64(0), -1);
833    }
834    #[test]
835    pub(super) fn test_dep_graph() {
836        let mut g = RubyDepGraph::new();
837        g.add_dep(1, 2);
838        g.add_dep(2, 3);
839        g.add_dep(1, 3);
840        assert_eq!(g.dependencies_of(2), vec![1]);
841        let topo = g.topological_sort();
842        assert_eq!(topo.len(), 3);
843        assert!(!g.has_cycle());
844        let pos: std::collections::HashMap<u32, usize> =
845            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
846        assert!(pos[&1] < pos[&2]);
847        assert!(pos[&1] < pos[&3]);
848        assert!(pos[&2] < pos[&3]);
849    }
850}
851/// Ruby concurrent array access pattern
852#[allow(dead_code)]
853pub fn ruby_thread_safe_array_read(arr: &str, idx: &str) -> String {
854    format!("{}.synchronize {{ {}[{}] }}", arr, arr, idx)
855}
856/// Ruby mutable default args (common Ruby gotcha)
857#[allow(dead_code)]
858pub fn ruby_warn_mutable_default(param: &str) -> String {
859    format!("# Warning: mutable default for {}", param)
860}
861/// Ruby inline C extension helper
862#[allow(dead_code)]
863pub fn ruby_inline_c(c_body: &str) -> String {
864    format!(
865        "require 'inline'\ninline do |builder|\n  builder.c <<~C\n    {}\n  C\nend",
866        c_body
867    )
868}