1use 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
16pub(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}
166const 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];
173pub(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}
195pub(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}
253pub 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#[allow(dead_code)]
653pub const RUBY_PASS_VERSION: &str = "1.0.0";
654#[allow(dead_code)]
656pub const RUBY_BACKEND_VERSION: &str = "1.0.0";
657#[allow(dead_code)]
659pub const RUBY_MIN_VERSION: &str = "3.0";
660#[allow(dead_code)]
662pub fn ruby_frozen_str(s: &str) -> String {
663 format!("{}.freeze", s)
664}
665#[allow(dead_code)]
667pub fn ruby_safe_nav(obj: &str, method: &str) -> String {
668 format!("{}?.{}", obj, method)
669}
670#[allow(dead_code)]
672pub fn ruby_tap(expr: &str, block: &str) -> String {
673 format!("{}.tap {{ |it| {} }}", expr, block)
674}
675#[allow(dead_code)]
677pub fn ruby_then(expr: &str, block: &str) -> String {
678 format!("{}.then {{ |it| {} }}", expr, block)
679}
680#[allow(dead_code)]
682pub fn ruby_memoize(ivar: &str, expr: &str) -> String {
683 format!("{} ||= {}", ivar, expr)
684}
685#[allow(dead_code)]
687pub fn ruby_double_splat(hash: &str) -> String {
688 format!("**{}", hash)
689}
690#[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#[allow(dead_code)]
698pub fn ruby_heredoc(label: &str, content: &str) -> String {
699 format!("<<~{}\n{}{}\n", label, content, label)
700}
701#[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#[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#[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#[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#[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#[allow(dead_code)]
744pub fn ruby_define_finalizer(var: &str, finalizer: &str) -> String {
745 format!("ObjectSpace.define_finalizer({}, {})", var, finalizer)
746}
747#[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#[allow(dead_code)]
853pub fn ruby_thread_safe_array_read(arr: &str, idx: &str) -> String {
854 format!("{}.synchronize {{ {}[{}] }}", arr, arr, idx)
855}
856#[allow(dead_code)]
858pub fn ruby_warn_mutable_default(param: &str) -> String {
859 format!("# Warning: mutable default for {}", param)
860}
861#[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}