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::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}
167const 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];
174pub(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}
196pub(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}
254pub 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#[allow(dead_code)]
654pub const RUBY_PASS_VERSION: &str = "1.0.0";
655#[allow(dead_code)]
657pub const RUBY_BACKEND_VERSION: &str = "1.0.0";
658#[allow(dead_code)]
660pub const RUBY_MIN_VERSION: &str = "3.0";
661#[allow(dead_code)]
663pub fn ruby_frozen_str(s: &str) -> String {
664 format!("{}.freeze", s)
665}
666#[allow(dead_code)]
668pub fn ruby_safe_nav(obj: &str, method: &str) -> String {
669 format!("{}?.{}", obj, method)
670}
671#[allow(dead_code)]
673pub fn ruby_tap(expr: &str, block: &str) -> String {
674 format!("{}.tap {{ |it| {} }}", expr, block)
675}
676#[allow(dead_code)]
678pub fn ruby_then(expr: &str, block: &str) -> String {
679 format!("{}.then {{ |it| {} }}", expr, block)
680}
681#[allow(dead_code)]
683pub fn ruby_memoize(ivar: &str, expr: &str) -> String {
684 format!("{} ||= {}", ivar, expr)
685}
686#[allow(dead_code)]
688pub fn ruby_double_splat(hash: &str) -> String {
689 format!("**{}", hash)
690}
691#[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#[allow(dead_code)]
699pub fn ruby_heredoc(label: &str, content: &str) -> String {
700 format!("<<~{}\n{}{}\n", label, content, label)
701}
702#[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#[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#[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#[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#[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#[allow(dead_code)]
745pub fn ruby_define_finalizer(var: &str, finalizer: &str) -> String {
746 format!("ObjectSpace.define_finalizer({}, {})", var, finalizer)
747}
748#[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#[allow(dead_code)]
854pub fn ruby_thread_safe_array_read(arr: &str, idx: &str) -> String {
855 format!("{}.synchronize {{ {}[{}] }}", arr, arr, idx)
856}
857#[allow(dead_code)]
859pub fn ruby_warn_mutable_default(param: &str) -> String {
860 format!("# Warning: mutable default for {}", param)
861}
862#[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}