espforge_lib/resolver/
ruchy_bridge.rs

1use anyhow::{Result, anyhow};
2use ruchy::backend::Transpiler;
3use ruchy::frontend::Parser;
4use ruchy::frontend::ast::ExprKind;
5
6pub struct RuchyOutput {
7    pub setup: String,
8    pub loop_body: String,
9    pub variables: Vec<String>,
10}
11
12pub fn compile_ruchy_script(raw_source: &str) -> Result<RuchyOutput> {
13    // FIX: Normalize Windows (CRLF) line endings to Unix (LF).
14    // This looks for the specific "\r\n" sequence and makes it "\n".
15    let source = raw_source.replace("\r\n", "\n");
16
17    // 1. Parse
18    let mut parser = Parser::new(&source);
19    let ast = parser
20        .parse()
21        .map_err(|e| anyhow!("Failed to parse Ruchy code: {:?}", e))?;
22
23    // 2. Transpile
24    let mut transpiler = Transpiler::new();
25
26    let mut setup_body = String::new();
27    let mut loop_body = String::new();
28    let mut variables = Vec::new();
29
30    if let ExprKind::Block(exprs) = ast.kind {
31        for expr in exprs {
32            match expr.kind {
33                ExprKind::Function { name, body, .. } => {
34                    // Run Analysis
35                    if let ExprKind::Block(ref stmts) = body.kind {
36                        transpiler.analyze_mutability(stmts);
37                    } else {
38                        transpiler.analyze_mutability(&[body.as_ref().clone()]);
39                    }
40
41                    // Get raw tokens
42                    let token_stream = transpiler.transpile_expr(&body)?;
43                    let raw_code = token_stream.to_string();
44
45                    // Format the code specifically for the target location
46                    match name.as_str() {
47                        "setup" => {
48                            // Indent level 1
49                            setup_body = format_rust_code(&raw_code, 1);
50                        }
51                        "forever" => {
52                            // Indent level 2
53                            loop_body = format_rust_code(&raw_code, 2);
54                        }
55                        _ => {}
56                    }
57                }
58                _ => {
59                    // It is a top-level expression (likely a variable definition)
60                    // In espforge, we treat top-level let bindings as function-local variables in main()
61                    transpiler.analyze_mutability(std::slice::from_ref(&expr));
62                    let token_stream = transpiler.transpile_expr(&expr)?;
63                    let raw_code = token_stream.to_string();
64
65                    let mut stmt = raw_code.trim().to_string();
66
67                    // Ensure semicolon
68                    if !stmt.ends_with(';') {
69                        stmt.push(';');
70                    }
71
72                    // Force 'mut' for state variables defined at top level so they can be updated in the loop
73                    // Ruchy might output "let address = 1;" or "let mut address = 1;"
74                    if stmt.starts_with("let ") {
75                        if !stmt.starts_with("let mut ") {
76                            stmt = stmt.replace("let ", "let mut ");
77                        }
78                    } else {
79                        // Implicit declaration fallback "address = 1;" -> "let mut address = 1;"
80                        stmt = format!("let mut {}", stmt);
81                    }
82
83                    variables.push(stmt);
84                }
85            }
86        }
87    }
88
89    Ok(RuchyOutput {
90        setup: setup_body,
91        loop_body,
92        variables,
93    })
94}
95
96/// Cleans token stream artifacts and applies indentation/newlines
97fn format_rust_code(input: &str, indent_level: usize) -> String {
98    let indent = "    ".repeat(indent_level);
99
100    // 1. Recursively remove outer braces from the block "{ ... }"
101    // This fixes issues where transpiler wraps body in multiple blocks e.g. "{ { code } }"
102    let mut content = input.trim();
103    while content.starts_with('{') && content.ends_with('}') {
104        content = content[1..content.len() - 1].trim();
105    }
106
107    // 2. Fix token spacing artifacts
108    let mut cleaned = content.to_string();
109    cleaned = cleaned.replace(" . ", ".");
110    cleaned = cleaned.replace(" (", "(");
111    cleaned = cleaned.replace(" )", ")");
112    cleaned = cleaned.replace(" ;", ";");
113    cleaned = cleaned.replace(" !", "!");
114    cleaned = cleaned.replace(" :: ", "::");
115
116    // 3. Split by semicolon to create newlines
117    let mut formatted = String::new();
118    let statements: Vec<&str> = cleaned
119        .split(';')
120        .map(|s| s.trim())
121        .filter(|s| !s.is_empty())
122        .collect();
123
124    for (i, stmt) in statements.iter().enumerate() {
125        if i == 0 {
126            // First line: No indentation (handled by template)
127            formatted.push_str(&format!("{};", stmt));
128        } else {
129            // Subsequent lines: Newline + Indent + stmt
130            formatted.push_str(&format!("\n{}{};", indent, stmt));
131        }
132    }
133
134    formatted
135}