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