espforge_lib/resolver/
ruchy_bridge.rs

1use anyhow::{Result, anyhow};
2use ruchy::backend::Transpiler;
3use ruchy::frontend::Parser;
4use ruchy::frontend::ast::{ExprKind, TypeKind};
5
6pub struct RuchyOutput {
7    pub setup: String,
8    pub loop_body: String,
9    pub variables: Vec<String>,
10    pub task_definitions: Vec<String>,
11    pub task_names: Vec<String>,
12    pub task_spawns: Vec<String>, // New field to hold full spawn calls
13}
14
15pub fn compile_ruchy_script(raw_source: &str, enable_async: bool) -> Result<RuchyOutput> {
16    // Change Windows (CRLF) line endings to Unix (LF).
17    let source = raw_source.replace("\r\n", "\n");
18
19    let mut parser = Parser::new(&source);
20    let ast = parser
21        .parse()
22        .map_err(|e| anyhow!("Failed to parse Ruchy code: {:?}", e))?;
23    let mut transpiler = Transpiler::new();
24
25    let mut setup_body = String::new();
26    let mut loop_body = String::new();
27    let mut variables = Vec::new();
28    let mut task_definitions = Vec::new();
29    let mut task_names = Vec::new();
30    let mut task_spawns = Vec::new();
31
32    if let ExprKind::Block(exprs) = ast.kind {
33        for expr in exprs {
34            let is_disabled = expr.attributes.iter().any(|attr| attr.name == "disabled");
35            if is_disabled {
36                continue;
37            }
38            let has_task_attr = expr.attributes.iter().any(|attr| attr.name == "task");
39            
40            match expr.kind {
41                ExprKind::Function { name, body, params, .. } => {
42                    let is_task = has_task_attr || name.starts_with("task_");
43
44                    if let ExprKind::Block(ref stmts) = body.kind {
45                        transpiler.analyze_mutability(stmts);
46                    } else {
47                        transpiler.analyze_mutability(&[body.as_ref().clone()]);
48                    }
49                    let token_stream = transpiler.transpile_expr(&body)?;
50                    let raw_code = token_stream.to_string();
51
52                    let indent = if is_task || name == "forever" { 2 } else { 1 };
53                    let formatted_body = format_rust_code(&raw_code, indent, enable_async);
54
55                    if is_task {
56                        if !enable_async {
57                            return Err(anyhow!(
58                                "Function '{}' identified as task but 'enable_async' is false in configuration.",
59                                name
60                            ));
61                        }
62
63                        task_names.push(name.clone());
64
65                        // Build parameter string for the function definition: "arg1: Type1, arg2: Type2"
66                        let mut fn_params = Vec::new();
67                        // Build arguments string for the spawn call: "arg1, arg2"
68                        let mut call_args = Vec::new();
69
70                        for param in &params {
71                            let param_name = param.name();
72                            let type_name = match &param.ty.kind {
73                                TypeKind::Named(n) => n.clone(),
74                                _ => "i32".to_string(), // Default fallback
75                            };
76                            
77                            // For components like Button, we typically pass them by move or mutable reference
78                            // In Embassy, moving the peripheral driver into the task is standard.
79                            fn_params.push(format!("mut {}: {}", param_name, type_name));
80                            call_args.push(param_name);
81                        }
82                        
83                        let fn_params_str = fn_params.join(", ");
84                        let call_args_str = call_args.join(", ");
85
86                        // Wrap the body in an infinite loop and the embassy task macro
87                        let task_code = format!(
88                             "#[embassy_executor::task]\nasync fn {}({}) {{\n{}\n}}",
89                            name, fn_params_str, formatted_body
90                        );
91                        task_definitions.push(task_code);
92                        
93                        // Generate the spawn call
94                        task_spawns.push(format!("spawner.spawn({}({})).ok();", name, call_args_str));
95
96                    } else {
97                        match name.as_str() {
98                            "setup" => {
99                                setup_body = formatted_body;
100                            }
101                            "forever" => {
102                                loop_body = formatted_body;
103                            }
104                            _ => {}
105                        }
106                    }
107                }
108                _ => {
109                    transpiler.analyze_mutability(std::slice::from_ref(&expr));
110                    let token_stream = transpiler.transpile_expr(&expr)?;
111                    let raw_code = token_stream.to_string();
112
113                    let mut stmt = raw_code.trim().to_string();
114                    if !stmt.ends_with(';') {
115                        stmt.push(';');
116                    }
117                    if stmt.starts_with("let ") {
118                        if !stmt.starts_with("let mut ") {
119                            stmt = stmt.replace("let ", "let mut ");
120                        }
121                    } else {
122                        stmt = format!("let mut {}", stmt);
123                    }
124                    variables.push(stmt);
125                }
126            }
127        }
128    }
129
130    Ok(RuchyOutput {
131        setup: setup_body,
132        loop_body,
133        variables,
134        task_definitions,
135        task_names,
136        task_spawns,
137    })
138}
139
140fn apply_async_delay_replacement(input: String) -> String {
141    let search = "delay.delay_millis(";
142    let replacement_start = "Timer::after(Duration::from_millis(";
143    
144    let mut result = String::with_capacity(input.len());
145    let mut last_pos = 0;
146    
147    while let Some(start_idx) = input[last_pos..].find(search) {
148        let abs_start = last_pos + start_idx;
149        result.push_str(&input[last_pos..abs_start]);
150        result.push_str(replacement_start);
151        
152        let args_start = abs_start + search.len();
153        let mut paren_balance = 1;
154        let mut end_idx = args_start;
155        
156        for (i, c) in input[args_start..].char_indices() {
157            if c == '(' { paren_balance += 1; }
158            else if c == ')' { paren_balance -= 1; }
159            
160            if paren_balance == 0 {
161                end_idx = args_start + i;
162                break;
163            }
164        }
165        
166        result.push_str(&input[args_start..end_idx]);
167        result.push_str(")).await");
168        last_pos = end_idx + 1; 
169    }
170    
171    result.push_str(&input[last_pos..]);
172    result
173}
174
175fn format_rust_code(input: &str, indent_level: usize, enable_async: bool) -> String {
176    let indent = "    ".repeat(indent_level);
177    let mut content = input.trim();
178    while content.starts_with('{') && content.ends_with('}') {
179        content = content[1..content.len() - 1].trim();
180    }
181
182    // Fix token spacing artifacts
183    let mut cleaned = content.to_string();
184    cleaned = cleaned.replace(" . ", ".");
185    cleaned = cleaned.replace(" (", "(");
186    cleaned = cleaned.replace(" )", ")");
187    cleaned = cleaned.replace(" ;", ";");
188    cleaned = cleaned.replace(" !", "!");
189    cleaned = cleaned.replace(" :: ", "::");
190
191    if enable_async {
192        cleaned = apply_async_delay_replacement(cleaned);
193    }
194
195    let mut formatted = String::new();
196    let statements: Vec<&str> = cleaned
197        .split(';')
198        .map(|s| s.trim())
199        .filter(|s| !s.is_empty())
200        .collect();
201
202    for (i, stmt) in statements.iter().enumerate() {
203        let  final_stmt = stmt.to_string();
204        
205        // Remove extra braces if present (e.g., from nested loops transpiled as blocks)
206        if final_stmt.starts_with("{") && final_stmt.ends_with("}") {
207             // This is a heuristic to clean up simple double wrapping
208             // Keep content but remove outer braces if it's just a simple block wrapper
209             // However, for loops like `loop { ... }`, we want to keep them if they are semantic.
210             // But here, the task wrapper provides the loop.
211        }
212
213        if i == 0 {
214            formatted.push_str(&format!("{};", final_stmt));
215        } else {
216            formatted.push_str(&format!("\n{}{};", indent, final_stmt));
217        }
218    }
219
220    formatted
221}
222