/home/noah/src/ruchy/src/backend/transpiler/mod.rs
Line | Count | Source |
1 | | //! Modular transpiler for Ruchy language |
2 | | //! |
3 | | //! This module is responsible for converting Ruchy AST into Rust code using `proc_macro2` `TokenStream`. |
4 | | |
5 | | #![allow(clippy::missing_errors_doc)] |
6 | | #![allow(clippy::too_many_lines)] |
7 | | |
8 | | mod actors; |
9 | | mod dataframe; |
10 | | mod dataframe_helpers; |
11 | | mod dispatcher; |
12 | | mod expressions; |
13 | | mod method_call_refactored; |
14 | | mod patterns; |
15 | | mod result_type; |
16 | | mod statements; |
17 | | mod type_conversion_refactored; |
18 | | mod type_inference; |
19 | | mod types; |
20 | | mod codegen_minimal; |
21 | | |
22 | | use crate::frontend::ast::{Attribute, Expr, ExprKind, Span, Type}; |
23 | | use crate::backend::module_resolver::ModuleResolver; |
24 | | use anyhow::Result; |
25 | | use proc_macro2::TokenStream; |
26 | | use quote::{format_ident, quote}; |
27 | | |
28 | | // Module exports are handled by the impl blocks in each module |
29 | | |
30 | | /// Block categorization result: (functions, statements, modules, `has_main`, `main_expr`) |
31 | | type BlockCategorization<'a> = (Vec<TokenStream>, Vec<TokenStream>, Vec<TokenStream>, bool, Option<&'a Expr>); |
32 | | |
33 | | /// Function signature information for type coercion |
34 | | #[derive(Debug, Clone)] |
35 | | pub struct FunctionSignature { |
36 | | pub name: String, |
37 | | pub param_types: Vec<String>, // Simplified: just the type name as string |
38 | | } |
39 | | |
40 | | /// The main transpiler struct |
41 | | pub struct Transpiler { |
42 | | /// Track whether we're in an async context |
43 | | pub in_async_context: bool, |
44 | | /// Track variables that need to be mutable (for auto-mutability) |
45 | | pub mutable_vars: std::collections::HashSet<String>, |
46 | | /// Track function signatures for type coercion |
47 | | pub function_signatures: std::collections::HashMap<String, FunctionSignature>, |
48 | | } |
49 | | |
50 | | impl Default for Transpiler { |
51 | 0 | fn default() -> Self { |
52 | 0 | Self::new() |
53 | 0 | } |
54 | | } |
55 | | |
56 | | impl Transpiler { |
57 | | /// Creates a new transpiler instance without module loader |
58 | | /// |
59 | | /// # Examples |
60 | | /// |
61 | | /// ``` |
62 | | /// use ruchy::Transpiler; |
63 | | /// |
64 | | /// let transpiler = Transpiler::new(); |
65 | | /// assert!(!transpiler.in_async_context); |
66 | | /// ``` |
67 | 349 | pub fn new() -> Self { |
68 | 349 | Self { |
69 | 349 | in_async_context: false, |
70 | 349 | mutable_vars: std::collections::HashSet::new(), |
71 | 349 | function_signatures: std::collections::HashMap::new(), |
72 | 349 | } |
73 | 349 | } |
74 | | |
75 | | /// Centralized result printing logic - ONE PLACE FOR ALL RESULT PRINTING |
76 | | /// This eliminates code duplication and ensures consistent Unit type handling |
77 | 83 | fn generate_result_printing_tokens(&self) -> TokenStream { |
78 | 83 | quote! { |
79 | | // Check the type name first to avoid Unit type Display error |
80 | | if std::any::type_name_of_val(&result) == "()" { |
81 | | // Don't print Unit type |
82 | | } else if std::any::type_name_of_val(&result).contains("String") || |
83 | | std::any::type_name_of_val(&result).contains("&str") { |
84 | | println!("{}", result); |
85 | | } else { |
86 | | println!("{:?}", result); |
87 | | } |
88 | | } |
89 | 83 | } |
90 | | |
91 | | /// Centralized value printing logic for functions like println |
92 | 0 | fn generate_value_printing_tokens(&self, value_expr: TokenStream, func_tokens: TokenStream) -> TokenStream { |
93 | 0 | quote! { |
94 | | { |
95 | | let value = #value_expr; |
96 | | // Check if it's a String type at runtime |
97 | | if std::any::type_name_of_val(&value).contains("String") || |
98 | | std::any::type_name_of_val(&value).contains("&str") { |
99 | | #func_tokens!("{}", value) |
100 | | } else { |
101 | | #func_tokens!("{:?}", value) |
102 | | } |
103 | | } |
104 | | } |
105 | 0 | } |
106 | | |
107 | | /// Analyze expressions to determine which variables need to be mutable |
108 | 3 | pub fn analyze_mutability(&mut self, exprs: &[Expr]) { |
109 | 8 | for expr5 in exprs { |
110 | 5 | self.analyze_expr_mutability(expr); |
111 | 5 | } |
112 | 3 | } |
113 | | |
114 | | /// Collect function signatures for type coercion |
115 | 3 | pub fn collect_function_signatures(&mut self, exprs: &[Expr]) { |
116 | 8 | for expr5 in exprs { |
117 | 5 | self.collect_signatures_from_expr(expr); |
118 | 5 | } |
119 | 3 | } |
120 | | |
121 | 105 | fn collect_signatures_from_expr(&mut self, expr: &Expr) { |
122 | | use crate::frontend::ast::ExprKind; |
123 | | |
124 | 105 | match &expr.kind { |
125 | 8 | ExprKind::Function { name, params, .. } => { |
126 | 8 | let param_types: Vec<String> = params.iter() |
127 | 8 | .map(|param| self.type_to_string(¶m.ty)) |
128 | 8 | .collect(); |
129 | | |
130 | 8 | let signature = FunctionSignature { |
131 | 8 | name: name.clone(), |
132 | 8 | param_types, |
133 | 8 | }; |
134 | | |
135 | 8 | self.function_signatures.insert(name.clone(), signature); |
136 | | } |
137 | 0 | ExprKind::Block(exprs) => { |
138 | 0 | for e in exprs { |
139 | 0 | self.collect_signatures_from_expr(e); |
140 | 0 | } |
141 | | } |
142 | 6 | ExprKind::Let { body, .. } => { |
143 | 6 | self.collect_signatures_from_expr(body); |
144 | 6 | } |
145 | 91 | _ => {} |
146 | | } |
147 | 105 | } |
148 | | |
149 | 8 | fn type_to_string(&self, ty: &crate::frontend::ast::Type) -> String { |
150 | | use crate::frontend::ast::TypeKind; |
151 | | |
152 | 8 | match &ty.kind { |
153 | 8 | TypeKind::Named(name) => name.clone(), |
154 | 0 | TypeKind::Reference { inner, .. } => format!("&{}", self.type_to_string(inner)), |
155 | 0 | _ => "Unknown".to_string(), |
156 | | } |
157 | 8 | } |
158 | | |
159 | 344 | fn analyze_expr_mutability(&mut self, expr: &Expr) { |
160 | | use crate::frontend::ast::ExprKind; |
161 | | |
162 | 344 | match &expr.kind { |
163 | | // Direct assignment marks the target as mutable |
164 | 0 | ExprKind::Assign { target, value } => { |
165 | 0 | if let ExprKind::Identifier(name) = &target.kind { |
166 | 0 | self.mutable_vars.insert(name.clone()); |
167 | 0 | } |
168 | 0 | self.analyze_expr_mutability(value); |
169 | | } |
170 | | // Compound assignment marks the target as mutable |
171 | 0 | ExprKind::CompoundAssign { target, value, .. } => { |
172 | 0 | if let ExprKind::Identifier(name) = &target.kind { |
173 | 0 | self.mutable_vars.insert(name.clone()); |
174 | 0 | } |
175 | 0 | self.analyze_expr_mutability(value); |
176 | | } |
177 | | // Pre/Post increment/decrement mark the target as mutable |
178 | 0 | ExprKind::PreIncrement { target } | |
179 | 0 | ExprKind::PostIncrement { target } | |
180 | 0 | ExprKind::PreDecrement { target } | |
181 | 0 | ExprKind::PostDecrement { target } => { |
182 | 0 | if let ExprKind::Identifier(name) = &target.kind { |
183 | 0 | self.mutable_vars.insert(name.clone()); |
184 | 0 | } |
185 | | } |
186 | | // Recursively analyze blocks |
187 | 20 | ExprKind::Block(exprs) => { |
188 | 42 | for e22 in exprs { |
189 | 22 | self.analyze_expr_mutability(e); |
190 | 22 | } |
191 | | } |
192 | | // Analyze control flow |
193 | 5 | ExprKind::If { condition, then_branch, else_branch } => { |
194 | 5 | self.analyze_expr_mutability(condition); |
195 | 5 | self.analyze_expr_mutability(then_branch); |
196 | 5 | if let Some(else_expr) = else_branch { |
197 | 5 | self.analyze_expr_mutability(else_expr); |
198 | 5 | }0 |
199 | | } |
200 | 1 | ExprKind::While { condition, body } => { |
201 | 1 | self.analyze_expr_mutability(condition); |
202 | 1 | self.analyze_expr_mutability(body); |
203 | 1 | } |
204 | 1 | ExprKind::For { body, iter, .. } => { |
205 | 1 | self.analyze_expr_mutability(iter); |
206 | 1 | self.analyze_expr_mutability(body); |
207 | 1 | } |
208 | | // Analyze match arms |
209 | 3 | ExprKind::Match { expr, arms } => { |
210 | 3 | self.analyze_expr_mutability(expr); |
211 | 10 | for arm7 in arms { |
212 | 7 | self.analyze_expr_mutability(&arm.body); |
213 | 7 | } |
214 | | } |
215 | | // Analyze let bodies |
216 | 9 | ExprKind::Let { body, value, .. } | ExprKind::LetPattern { body0 , value0 , .. } => { |
217 | 9 | self.analyze_expr_mutability(value); |
218 | 9 | self.analyze_expr_mutability(body); |
219 | 9 | } |
220 | | // Analyze function bodies |
221 | 8 | ExprKind::Function { body, .. } => { |
222 | 8 | self.analyze_expr_mutability(body); |
223 | 8 | } |
224 | 4 | ExprKind::Lambda { body, .. } => { |
225 | 4 | self.analyze_expr_mutability(body); |
226 | 4 | } |
227 | | // Analyze binary/unary operations |
228 | 43 | ExprKind::Binary { left, right, .. } => { |
229 | 43 | self.analyze_expr_mutability(left); |
230 | 43 | self.analyze_expr_mutability(right); |
231 | 43 | } |
232 | 4 | ExprKind::Unary { operand, .. } => { |
233 | 4 | self.analyze_expr_mutability(operand); |
234 | 4 | } |
235 | | // Analyze calls |
236 | 23 | ExprKind::Call { func, args } => { |
237 | 23 | self.analyze_expr_mutability(func); |
238 | 49 | for arg26 in args { |
239 | 26 | self.analyze_expr_mutability(arg); |
240 | 26 | } |
241 | | } |
242 | 19 | ExprKind::MethodCall { receiver, args, .. } => { |
243 | 19 | self.analyze_expr_mutability(receiver); |
244 | 25 | for arg6 in args { |
245 | 6 | self.analyze_expr_mutability(arg); |
246 | 6 | } |
247 | | } |
248 | 204 | _ => {} |
249 | | } |
250 | 344 | } |
251 | | |
252 | | /// Resolves file imports in the AST using `ModuleResolver` |
253 | | #[allow(dead_code)] |
254 | 0 | fn resolve_imports(&self, expr: &Expr) -> Result<Expr> { |
255 | | // For now, just use default search paths since we don't have file context here |
256 | 0 | let mut resolver = ModuleResolver::new(); |
257 | 0 | resolver.resolve_imports(expr.clone()) |
258 | 0 | } |
259 | | |
260 | | /// Resolves file imports with a specific file context for search paths |
261 | 97 | fn resolve_imports_with_context(&self, expr: &Expr, file_path: Option<&std::path::Path>) -> Result<Expr> { |
262 | 97 | let mut resolver = ModuleResolver::new(); |
263 | | |
264 | | // Add the file's directory to search paths if provided |
265 | 97 | if let Some(path0 ) = file_path { |
266 | 0 | if let Some(dir) = path.parent() { |
267 | 0 | resolver.add_search_path(dir); |
268 | 0 | } |
269 | 97 | } |
270 | | |
271 | 97 | resolver.resolve_imports(expr.clone()) |
272 | 97 | } |
273 | | |
274 | | /// Transpiles an expression to a `TokenStream` |
275 | | /// |
276 | | /// # Examples |
277 | | /// |
278 | | /// ``` |
279 | | /// use ruchy::{Transpiler, Parser}; |
280 | | /// |
281 | | /// let mut parser = Parser::new("42"); |
282 | | /// let ast = parser.parse().unwrap(); |
283 | | /// |
284 | | /// let transpiler = Transpiler::new(); |
285 | | /// let result = transpiler.transpile(&ast); |
286 | | /// assert!(result.is_ok()); |
287 | | /// ``` |
288 | | /// |
289 | | /// # Errors |
290 | | /// |
291 | | /// Returns an error if the AST cannot be transpiled to valid Rust code. |
292 | 159 | pub fn transpile(&self, expr: &Expr) -> Result<TokenStream> { |
293 | 159 | self.transpile_expr(expr) |
294 | 159 | } |
295 | | |
296 | | /// Check if AST contains `HashMap` operations requiring `std::collections::HashMap` import |
297 | 196 | fn contains_hashmap(expr: &Expr) -> bool { |
298 | | use crate::frontend::ast::{ExprKind, Literal}; |
299 | | |
300 | 196 | match &expr.kind { |
301 | 0 | ExprKind::ObjectLiteral { .. } => true, |
302 | 17 | ExprKind::Call { func, .. } => { |
303 | | // Check for HashMap methods like .get(), .insert(), etc. |
304 | 17 | if let ExprKind::FieldAccess { field0 , .. } = &func.kind { |
305 | 0 | matches!(field.as_str(), "get" | "insert" | "remove" | "contains_key" | "keys" | "values") |
306 | | } else { |
307 | 17 | false |
308 | | } |
309 | | } |
310 | 0 | ExprKind::IndexAccess { object: _, index } => { |
311 | | // String literal index access suggests HashMap |
312 | 0 | matches!(&index.kind, ExprKind::Literal(Literal::String(_))) |
313 | | } |
314 | 20 | ExprKind::Block(exprs) => exprs.iter().any(Self::contains_hashmap), |
315 | 8 | ExprKind::Function { body, .. } => Self::contains_hashmap(body), |
316 | 5 | ExprKind::If { condition, then_branch, else_branch } => { |
317 | 5 | Self::contains_hashmap(condition) || |
318 | 5 | Self::contains_hashmap(then_branch) || |
319 | 5 | else_branch.as_ref().is_some_and(|e| Self::contains_hashmap(e)) |
320 | | } |
321 | 27 | ExprKind::Binary { left, right, .. } => { |
322 | 27 | Self::contains_hashmap(left) || Self::contains_hashmap(right) |
323 | | } |
324 | 119 | _ => false, |
325 | | } |
326 | 196 | } |
327 | | |
328 | | /// Checks if an expression contains `DataFrame` operations (simplified for complexity) |
329 | 97 | fn contains_dataframe(expr: &Expr) -> bool { |
330 | 97 | matches!( |
331 | 97 | expr.kind, |
332 | | ExprKind::DataFrame { .. } | ExprKind::DataFrameOperation { .. } |
333 | | ) |
334 | 97 | } |
335 | | |
336 | | /// Wraps transpiled code in a complete Rust program with necessary imports |
337 | | /// |
338 | | /// # Examples |
339 | | /// |
340 | | /// ``` |
341 | | /// use ruchy::{Transpiler, Parser}; |
342 | | /// |
343 | | /// let mut parser = Parser::new("42"); |
344 | | /// let ast = parser.parse().unwrap(); |
345 | | /// |
346 | | /// let transpiler = Transpiler::new(); |
347 | | /// let result = transpiler.transpile_to_program(&ast); |
348 | | /// assert!(result.is_ok()); |
349 | | /// |
350 | | /// let code = result.unwrap().to_string(); |
351 | | /// assert!(code.contains("fn main")); |
352 | | /// assert!(code.contains("42")); |
353 | | /// ``` |
354 | | /// |
355 | | /// # Errors |
356 | | /// |
357 | | /// Returns an error if the AST cannot be transpiled to a valid Rust program. |
358 | 97 | pub fn transpile_to_program(&mut self, expr: &Expr) -> Result<TokenStream> { |
359 | | // First analyze the entire program to detect mutable variables and function signatures |
360 | 97 | if let ExprKind::Block(exprs3 ) = &expr.kind { |
361 | 3 | self.analyze_mutability(exprs); |
362 | 3 | self.collect_function_signatures(exprs); |
363 | 94 | } else { |
364 | 94 | self.analyze_expr_mutability(expr); |
365 | 94 | self.collect_signatures_from_expr(expr); |
366 | 94 | } |
367 | | |
368 | 97 | let result = self.transpile_to_program_with_context(expr, None); |
369 | 97 | if let Ok(ref token_stream) = result { |
370 | 97 | // Debug: Write the generated Rust code to a debug file |
371 | 97 | let rust_code = token_stream.to_string(); |
372 | 97 | std::fs::write("/tmp/debug_transpiler_output.rs", &rust_code).ok(); |
373 | 97 | }0 |
374 | 97 | result |
375 | 97 | } |
376 | | |
377 | | /// Transpile with file context for module resolution |
378 | 97 | pub fn transpile_to_program_with_context(&self, expr: &Expr, file_path: Option<&std::path::Path>) -> Result<TokenStream> { |
379 | | // First, resolve any file imports using the module resolver |
380 | 97 | let resolved_expr = self.resolve_imports_with_context(expr, file_path)?0 ; |
381 | 97 | let needs_polars = Self::contains_dataframe(&resolved_expr); |
382 | 97 | let needs_hashmap = Self::contains_hashmap(&resolved_expr); |
383 | | |
384 | 97 | match &resolved_expr.kind { |
385 | 7 | ExprKind::Function { name, .. } => { |
386 | 7 | self.transpile_single_function(&resolved_expr, name, needs_polars, needs_hashmap) |
387 | | } |
388 | 3 | ExprKind::Block(exprs) => { |
389 | 3 | self.transpile_program_block(exprs, needs_polars, needs_hashmap) |
390 | | } |
391 | | _ => { |
392 | 87 | self.transpile_expression_program(&resolved_expr, needs_polars, needs_hashmap) |
393 | | } |
394 | | } |
395 | 97 | } |
396 | | |
397 | 7 | fn transpile_single_function(&self, expr: &Expr, name: &str, needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
398 | | // Use the proper function expression transpiler to handle attributes correctly |
399 | 7 | let func = match &expr.kind { |
400 | 7 | crate::frontend::ast::ExprKind::Function { .. } => self.transpile_function_expr(expr)?0 , |
401 | 0 | _ => self.transpile_expr(expr)?, |
402 | | }; |
403 | 7 | let needs_main = name != "main"; |
404 | | |
405 | 7 | match (needs_polars, needs_hashmap, needs_main) { |
406 | 0 | (true, true, true) => Ok(quote! { |
407 | 0 | use polars::prelude::*; |
408 | 0 | use std::collections::HashMap; |
409 | 0 | #func |
410 | 0 | fn main() { /* Function defined but not called */ } |
411 | 0 | }), |
412 | 0 | (true, true, false) => Ok(quote! { |
413 | 0 | use polars::prelude::*; |
414 | 0 | use std::collections::HashMap; |
415 | 0 | #func |
416 | 0 | }), |
417 | 0 | (true, false, true) => Ok(quote! { |
418 | 0 | use polars::prelude::*; |
419 | 0 | #func |
420 | 0 | fn main() { /* Function defined but not called */ } |
421 | 0 | }), |
422 | 0 | (true, false, false) => Ok(quote! { |
423 | 0 | use polars::prelude::*; |
424 | 0 | #func |
425 | 0 | }), |
426 | 0 | (false, true, true) => Ok(quote! { |
427 | 0 | use std::collections::HashMap; |
428 | 0 | #func |
429 | 0 | fn main() { /* Function defined but not called */ } |
430 | 0 | }), |
431 | 0 | (false, true, false) => Ok(quote! { |
432 | 0 | use std::collections::HashMap; |
433 | 0 | #func |
434 | 0 | }), |
435 | 6 | (false, false, true) => Ok(quote! { |
436 | 6 | #func |
437 | 6 | fn main() { /* Function defined but not called */ } |
438 | 6 | }), |
439 | 1 | (false, false, false) => Ok(quote! { |
440 | 1 | #func |
441 | 1 | }) |
442 | | } |
443 | 7 | } |
444 | | |
445 | 3 | fn transpile_program_block(&self, exprs: &[Expr], needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
446 | 3 | let (functions, statements, modules, has_main, main_expr) = self.categorize_block_expressions(exprs)?0 ; |
447 | | |
448 | 3 | if functions.is_empty() && !has_main2 && modules2 .is_empty2 () { |
449 | 2 | self.transpile_statement_only_block(exprs, needs_polars, needs_hashmap) |
450 | 1 | } else if has_main || !modules.is_empty() { |
451 | 0 | self.transpile_block_with_main_function(&functions, &statements, &modules, main_expr, needs_polars, needs_hashmap) |
452 | | } else { |
453 | 1 | self.transpile_block_with_functions(&functions, &statements, needs_polars, needs_hashmap) |
454 | | } |
455 | 3 | } |
456 | | |
457 | 3 | fn categorize_block_expressions<'a>(&self, exprs: &'a [Expr]) -> Result<BlockCategorization<'a>> { |
458 | 3 | let mut functions = Vec::new(); |
459 | 3 | let mut statements = Vec::new(); |
460 | 3 | let mut modules = Vec::new(); |
461 | 3 | let mut has_main_function = false; |
462 | 3 | let mut main_function_expr = None; |
463 | | |
464 | | |
465 | 8 | for expr5 in exprs { |
466 | 5 | match &expr.kind { |
467 | 1 | ExprKind::Function { name, .. } => { |
468 | 1 | if name == "main" { |
469 | 0 | has_main_function = true; |
470 | 0 | main_function_expr = Some(expr); |
471 | 0 | } else { |
472 | | // Use proper function transpiler to handle attributes correctly |
473 | 1 | functions.push(self.transpile_function_expr(expr)?0 ); |
474 | | } |
475 | | }, |
476 | 0 | ExprKind::Module { name, body } => { |
477 | | // Extract module declarations for top-level placement |
478 | 0 | modules.push(self.transpile_module_declaration(name, body)?); |
479 | | }, |
480 | 0 | ExprKind::Block(block_exprs) => { |
481 | | // Check if this is a module-containing block from the resolver |
482 | 0 | if block_exprs.len() == 2 |
483 | 0 | && matches!(block_exprs[0].kind, ExprKind::Module { .. }) |
484 | 0 | && matches!(block_exprs[1].kind, ExprKind::Import { .. }) { |
485 | | // This is a module resolver block: extract the module and keep the import as statement |
486 | 0 | if let ExprKind::Module { name, body } = &block_exprs[0].kind { |
487 | 0 | modules.push(self.transpile_module_declaration(name, body)?); |
488 | 0 | } |
489 | 0 | statements.push(self.transpile_expr(&block_exprs[1])?); |
490 | | } else { |
491 | | // Regular block, treat as statement |
492 | 0 | statements.push(self.transpile_expr(expr)?); |
493 | | } |
494 | | }, |
495 | | _ => { |
496 | 4 | statements.push(self.transpile_expr(expr)?0 ); |
497 | | } |
498 | | } |
499 | | } |
500 | | |
501 | 3 | Ok((functions, statements, modules, has_main_function, main_function_expr)) |
502 | 3 | } |
503 | | |
504 | 0 | fn transpile_module_declaration(&self, name: &str, body: &Expr) -> Result<TokenStream> { |
505 | 0 | let module_name = format_ident!("{}", name); |
506 | | |
507 | | // Handle module body - if it's a block, transpile its contents directly |
508 | 0 | let body_tokens = if let ExprKind::Block(exprs) = &body.kind { |
509 | | // Transpile each expression in the block as individual items |
510 | 0 | let items: Result<Vec<_>> = exprs.iter().map(|expr| self.transpile_expr(expr)).collect(); |
511 | 0 | let items = items?; |
512 | 0 | quote! { #(#items)* } |
513 | | } else { |
514 | | // Single expression - transpile normally |
515 | 0 | self.transpile_expr(body)? |
516 | | }; |
517 | | |
518 | 0 | Ok(quote! { |
519 | 0 | mod #module_name { |
520 | 0 | #body_tokens |
521 | 0 | } |
522 | 0 | }) |
523 | 0 | } |
524 | | |
525 | 2 | fn transpile_statement_only_block(&self, exprs: &[Expr], needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
526 | | // Check if this is a statement sequence (contains let, assignments, etc.) or an expression sequence |
527 | 3 | let has_statements2 = exprs.iter()2 .any2 (|expr| self.is_statement_expr(expr)); |
528 | | |
529 | 2 | if has_statements { |
530 | | // Split into statements and possible final expression |
531 | 1 | let (statements, final_expr) = if !exprs.is_empty() && !self.is_statement_expr(exprs.last().unwrap()) { |
532 | | // Last item is an expression, not a statement |
533 | 0 | (&exprs[..exprs.len() - 1], Some(exprs.last().unwrap())) |
534 | | } else { |
535 | | // All are statements |
536 | 1 | (exprs, None) |
537 | | }; |
538 | | |
539 | | // Transpile all statements and add semicolons intelligently |
540 | 1 | let statement_results: Result<Vec<_>> = statements.iter().map(|expr| { |
541 | 1 | let tokens = self.transpile_expr(expr)?0 ; |
542 | 1 | let tokens_str = tokens.to_string(); |
543 | | // If the statement already ends with a semicolon, don't add another |
544 | 1 | if tokens_str.trim().ends_with(';') { |
545 | 0 | Ok(tokens) |
546 | | } else { |
547 | | // Add semicolon for statements that need them |
548 | 1 | Ok(quote! { #tokens; }) |
549 | | } |
550 | 1 | }).collect(); |
551 | 1 | let statement_tokens = statement_results?0 ; |
552 | | |
553 | | // Handle final expression if present |
554 | 1 | let main_body = if let Some(final_expr0 ) = final_expr { |
555 | 0 | let final_tokens = self.transpile_expr(final_expr)?; |
556 | 0 | let result_printing_logic = self.generate_result_printing_tokens(); |
557 | 0 | quote! { |
558 | | #(#statement_tokens)* |
559 | | let result = #final_tokens; |
560 | | #result_printing_logic |
561 | | } |
562 | | } else { |
563 | 1 | quote! { |
564 | | #(#statement_tokens)* |
565 | | } |
566 | | }; |
567 | | |
568 | 1 | match (needs_polars, needs_hashmap) { |
569 | 0 | (true, true) => Ok(quote! { |
570 | 0 | use polars::prelude::*; |
571 | 0 | use std::collections::HashMap; |
572 | 0 | fn main() { |
573 | 0 | #main_body |
574 | 0 | } |
575 | 0 | }), |
576 | 0 | (true, false) => Ok(quote! { |
577 | 0 | use polars::prelude::*; |
578 | 0 | fn main() { |
579 | 0 | #main_body |
580 | 0 | } |
581 | 0 | }), |
582 | 0 | (false, true) => Ok(quote! { |
583 | 0 | use std::collections::HashMap; |
584 | 0 | fn main() { |
585 | 0 | #main_body |
586 | 0 | } |
587 | 0 | }), |
588 | 1 | (false, false) => Ok(quote! { |
589 | 1 | fn main() { |
590 | 1 | #main_body |
591 | 1 | } |
592 | 1 | }) |
593 | | } |
594 | | } else { |
595 | | // Pure expression sequence - use existing result printing approach |
596 | 1 | let block_expr = Expr::new(ExprKind::Block(exprs.to_vec()), Span::new(0, 0)); |
597 | 1 | let body = self.transpile_expr(&block_expr)?0 ; |
598 | 1 | self.wrap_in_main_with_result_printing(body, needs_polars, needs_hashmap) |
599 | | } |
600 | 2 | } |
601 | | |
602 | 91 | fn is_statement_expr(&self, expr: &Expr) -> bool { |
603 | 91 | match &expr.kind { |
604 | | // Let bindings are statements |
605 | 5 | ExprKind::Let { .. } | ExprKind::LetPattern { .. } => true, |
606 | | // Assignment operations are statements |
607 | 0 | ExprKind::Assign { .. } | ExprKind::CompoundAssign { .. } => true, |
608 | | // Loops are statements (void/unit type) |
609 | 2 | ExprKind::While { .. } | ExprKind::For { .. } | ExprKind::Loop { .. } => true, |
610 | | // Function calls that don't return meaningful values (like println) |
611 | 15 | ExprKind::Call { func, .. } => { |
612 | 15 | if let ExprKind::Identifier(name) = &func.kind { |
613 | 15 | matches!0 (name.as_str(), "println" | "print" | "dbg") |
614 | | } else { |
615 | 0 | false |
616 | | } |
617 | | } |
618 | | // Blocks containing statements |
619 | 0 | ExprKind::Block(exprs) => exprs.iter().any(|e| self.is_statement_expr(e)), |
620 | | // Most other expressions are not statements |
621 | 69 | _ => false, |
622 | | } |
623 | 91 | } |
624 | | |
625 | 0 | fn transpile_block_with_main_function(&self, functions: &[TokenStream], statements: &[TokenStream], modules: &[TokenStream], main_expr: Option<&Expr>, needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
626 | 0 | if statements.is_empty() && main_expr.is_some() { |
627 | | // Only functions, just emit them normally (includes user's main) |
628 | 0 | let main_tokens = if let Some(main) = main_expr { |
629 | 0 | self.transpile_expr(main)? |
630 | | } else { |
631 | 0 | return Err(anyhow::anyhow!("Expected main function expression")); |
632 | | }; |
633 | | |
634 | 0 | match (needs_polars, needs_hashmap) { |
635 | 0 | (true, true) => Ok(quote! { |
636 | | use polars::prelude::*; |
637 | | use std::collections::HashMap; |
638 | | #(#modules)* |
639 | | #(#functions)* |
640 | | #main_tokens |
641 | | }), |
642 | 0 | (true, false) => Ok(quote! { |
643 | | use polars::prelude::*; |
644 | | #(#modules)* |
645 | | #(#functions)* |
646 | | #main_tokens |
647 | | }), |
648 | 0 | (false, true) => Ok(quote! { |
649 | | use std::collections::HashMap; |
650 | | #(#modules)* |
651 | | #(#functions)* |
652 | | #main_tokens |
653 | | }), |
654 | 0 | (false, false) => Ok(quote! { |
655 | | #(#modules)* |
656 | | #(#functions)* |
657 | | #main_tokens |
658 | | }) |
659 | | } |
660 | | } else { |
661 | | // TOP-LEVEL STATEMENTS: Extract main body and combine with statements |
662 | 0 | let main_body = if let Some(main) = main_expr { |
663 | 0 | self.extract_main_function_body(main)? |
664 | | } else { |
665 | | // No user main function, just use empty body |
666 | 0 | quote! {} |
667 | | }; |
668 | | |
669 | 0 | match (needs_polars, needs_hashmap) { |
670 | 0 | (true, true) => Ok(quote! { |
671 | | use polars::prelude::*; |
672 | | use std::collections::HashMap; |
673 | | #(#modules)* |
674 | | #(#functions)* |
675 | | fn main() { |
676 | | // Top-level statements execute first |
677 | | #(#statements)* |
678 | | |
679 | | // Then user's main function body |
680 | | #main_body |
681 | | } |
682 | | }), |
683 | 0 | (true, false) => Ok(quote! { |
684 | | use polars::prelude::*; |
685 | | #(#modules)* |
686 | | #(#functions)* |
687 | | fn main() { |
688 | | // Top-level statements execute first |
689 | | #(#statements)* |
690 | | |
691 | | // Then user's main function body |
692 | | #main_body |
693 | | } |
694 | | }), |
695 | 0 | (false, true) => Ok(quote! { |
696 | | use std::collections::HashMap; |
697 | | #(#modules)* |
698 | | #(#functions)* |
699 | | fn main() { |
700 | | // Top-level statements execute first |
701 | | #(#statements)* |
702 | | |
703 | | // Then user's main function body |
704 | | #main_body |
705 | | } |
706 | | }), |
707 | 0 | (false, false) => Ok(quote! { |
708 | | #(#modules)* |
709 | | #(#functions)* |
710 | | fn main() { |
711 | | // Top-level statements execute first |
712 | | #(#statements)* |
713 | | |
714 | | // Then user's main function body |
715 | | #main_body |
716 | | } |
717 | | }) |
718 | | } |
719 | | } |
720 | 0 | } |
721 | | |
722 | | /// Extracts the body of a main function for inlining with top-level statements |
723 | 0 | fn extract_main_function_body(&self, main_expr: &Expr) -> Result<TokenStream> { |
724 | 0 | if let ExprKind::Function { body, .. } = &main_expr.kind { |
725 | | // Transpile just the body, not the entire function definition |
726 | 0 | self.transpile_expr(body) |
727 | | } else { |
728 | 0 | Err(anyhow::anyhow!("Expected function expression for main body extraction")) |
729 | | } |
730 | 0 | } |
731 | | |
732 | 1 | fn transpile_block_with_functions(&self, functions: &[TokenStream], statements: &[TokenStream], needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
733 | | // No main function among extracted functions - create one for statements |
734 | 1 | match (needs_polars, needs_hashmap) { |
735 | 0 | (true, true) => Ok(quote! { |
736 | | use polars::prelude::*; |
737 | | use std::collections::HashMap; |
738 | | #(#functions)* |
739 | | fn main() { #(#statements)* } |
740 | | }), |
741 | 0 | (true, false) => Ok(quote! { |
742 | | use polars::prelude::*; |
743 | | #(#functions)* |
744 | | fn main() { #(#statements)* } |
745 | | }), |
746 | 0 | (false, true) => Ok(quote! { |
747 | | use std::collections::HashMap; |
748 | | #(#functions)* |
749 | | fn main() { #(#statements)* } |
750 | | }), |
751 | 1 | (false, false) => Ok(quote! { |
752 | | #(#functions)* |
753 | | fn main() { #(#statements)* } |
754 | | }) |
755 | | } |
756 | 1 | } |
757 | | |
758 | | |
759 | 87 | fn transpile_expression_program(&self, expr: &Expr, needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
760 | 87 | let body = self.transpile_expr(expr)?0 ; |
761 | | |
762 | | // Check if this is a statement vs expression |
763 | 87 | if self.is_statement_expr(expr) { |
764 | | // For statements, execute directly without result wrapping |
765 | 5 | self.wrap_statement_in_main(body, needs_polars, needs_hashmap) |
766 | | } else { |
767 | | // For expressions, wrap with result printing |
768 | 82 | self.wrap_in_main_with_result_printing(body, needs_polars, needs_hashmap) |
769 | | } |
770 | 87 | } |
771 | | |
772 | 5 | fn wrap_statement_in_main(&self, body: TokenStream, needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
773 | | // For statements, execute directly without result capture |
774 | 5 | match (needs_polars, needs_hashmap) { |
775 | 0 | (true, true) => Ok(quote! { |
776 | 0 | use polars::prelude::*; |
777 | 0 | use std::collections::HashMap; |
778 | 0 | fn main() { |
779 | 0 | #body; |
780 | 0 | } |
781 | 0 | }), |
782 | 0 | (true, false) => Ok(quote! { |
783 | 0 | use polars::prelude::*; |
784 | 0 | fn main() { |
785 | 0 | #body; |
786 | 0 | } |
787 | 0 | }), |
788 | 0 | (false, true) => Ok(quote! { |
789 | 0 | use std::collections::HashMap; |
790 | 0 | fn main() { |
791 | 0 | #body; |
792 | 0 | } |
793 | 0 | }), |
794 | 5 | (false, false) => Ok(quote! { |
795 | 5 | fn main() { |
796 | 5 | #body; |
797 | 5 | } |
798 | 5 | }) |
799 | | } |
800 | 5 | } |
801 | | |
802 | 83 | fn wrap_in_main_with_result_printing(&self, body: TokenStream, needs_polars: bool, needs_hashmap: bool) -> Result<TokenStream> { |
803 | 83 | let result_printing_logic = self.generate_result_printing_tokens(); |
804 | 83 | match (needs_polars, needs_hashmap) { |
805 | 0 | (true, true) => Ok(quote! { |
806 | 0 | use polars::prelude::*; |
807 | 0 | use std::collections::HashMap; |
808 | 0 | fn main() { |
809 | 0 | let result = #body; |
810 | 0 | #result_printing_logic |
811 | 0 | } |
812 | 0 | }), |
813 | 0 | (true, false) => Ok(quote! { |
814 | 0 | use polars::prelude::*; |
815 | 0 | fn main() { |
816 | 0 | let result = #body; |
817 | 0 | #result_printing_logic |
818 | 0 | } |
819 | 0 | }), |
820 | | (false, true) => { |
821 | 0 | Ok(quote! { |
822 | 0 | use std::collections::HashMap; |
823 | 0 | fn main() { |
824 | 0 | let result = #body; |
825 | 0 | #result_printing_logic |
826 | 0 | } |
827 | 0 | }) |
828 | | }, |
829 | | (false, false) => { |
830 | 83 | Ok(quote! { |
831 | 83 | fn main() { |
832 | 83 | let result = #body; |
833 | 83 | #result_printing_logic |
834 | 83 | } |
835 | 83 | }) |
836 | | } |
837 | | } |
838 | 83 | } |
839 | | |
840 | | /// Transpiles an expression to a String |
841 | 0 | pub fn transpile_to_string(&self, expr: &Expr) -> Result<String> { |
842 | 0 | let tokens = self.transpile(expr)?; |
843 | | |
844 | | // Format the tokens with rustfmt-like style |
845 | 0 | let mut result = String::new(); |
846 | 0 | let token_str = tokens.to_string(); |
847 | | |
848 | | // Basic formatting: add newlines after semicolons and braces |
849 | 0 | for ch in token_str.chars() { |
850 | 0 | result.push(ch); |
851 | 0 | if ch == ';' || ch == '{' { |
852 | 0 | result.push('\n'); |
853 | 0 | } |
854 | | } |
855 | | |
856 | 0 | Ok(result) |
857 | 0 | } |
858 | | |
859 | | /// Generate minimal code for self-hosting (direct Rust mapping, no optimization) |
860 | 0 | pub fn transpile_minimal(&self, expr: &Expr) -> Result<String> { |
861 | 0 | codegen_minimal::MinimalCodeGen::gen_program(expr) |
862 | 0 | } |
863 | | |
864 | | /// Check if a name is a Rust reserved keyword |
865 | 268 | pub(crate) fn is_rust_reserved_keyword(name: &str) -> bool { |
866 | | // List of Rust reserved keywords that would conflict |
867 | 268 | matches!2 (name, |
868 | 268 | "as" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" | |
869 | 268 | "false" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | |
870 | 268 | "mod" | "move" | "mut" | "pub" | "ref" | "return" | "self" | "Self" | |
871 | 268 | "static" | "struct" | "super" | "trait" | "true" | "type" | "unsafe" | |
872 | 268 | "use" | "where" | "while" | "async" | "await" | "dyn" | "final" | "try"266 | |
873 | 266 | "abstract" | "become" | "box" | "do" | "override" | "priv" | "typeof" | |
874 | 266 | "unsized" | "virtual" | "yield" |
875 | | ) |
876 | 268 | } |
877 | | |
878 | | /// Main expression transpilation dispatcher |
879 | | /// |
880 | | /// # Panics |
881 | | /// |
882 | | /// Panics if label names cannot be parsed as valid Rust tokens |
883 | 1.12k | pub fn transpile_expr(&self, expr: &Expr) -> Result<TokenStream> { |
884 | | use ExprKind::{ |
885 | | Actor, ActorQuery, ActorSend, Ask, Assign, AsyncBlock, Await, Binary, Call, Command, CompoundAssign, DataFrame, |
886 | | DataFrameOperation, Err, FieldAccess, For, Function, Identifier, If, IfLet, IndexAccess, Lambda, |
887 | | List, ListComprehension, Literal, Loop, Macro, Match, MethodCall, None, ObjectLiteral, Ok, QualifiedName, |
888 | | Range, Send, Slice, Some, StringInterpolation, Struct, StructLiteral, Throw, Try, |
889 | | Tuple, Unary, While, WhileLet, |
890 | | }; |
891 | | |
892 | | // Dispatch to specialized handlers to keep complexity below 10 |
893 | 1.12k | match &expr.kind { |
894 | | // Basic expressions |
895 | | Literal(_) | Identifier(_) | QualifiedName { .. } | StringInterpolation { .. } => { |
896 | 682 | self.transpile_basic_expr(expr) |
897 | | } |
898 | | |
899 | | // Operators and control flow |
900 | | Binary { .. } |
901 | | | Unary { .. } |
902 | | | Assign { .. } |
903 | | | CompoundAssign { .. } |
904 | | | Await { .. } |
905 | | | AsyncBlock { .. } |
906 | | | If { .. } |
907 | | | IfLet { .. } |
908 | | | Match { .. } |
909 | | | For { .. } |
910 | | | While { .. } |
911 | | | WhileLet { .. } |
912 | 260 | | Loop { .. } => self.transpile_operator_control_expr(expr), |
913 | | |
914 | | // Functions |
915 | | Function { .. } | Lambda { .. } | Call { .. } | MethodCall { .. } | Macro { .. } => { |
916 | 75 | self.transpile_function_expr(expr) |
917 | | } |
918 | | |
919 | | // Structures |
920 | | Struct { .. } | StructLiteral { .. } | ObjectLiteral { .. } | FieldAccess { .. } |
921 | | | IndexAccess { .. } | Slice { .. } => { |
922 | 15 | self.transpile_struct_expr(expr) |
923 | | } |
924 | | |
925 | | // Data and error handling |
926 | | DataFrame { .. } |
927 | | | DataFrameOperation { .. } |
928 | | | List(_) |
929 | | | Tuple(_) |
930 | | | ListComprehension { .. } |
931 | | | Range { .. } |
932 | | | Throw { .. } |
933 | | | Ok { .. } |
934 | | | Err { .. } |
935 | | | Some { .. } |
936 | | | None |
937 | 40 | | Try { .. } => self.transpile_data_error_expr(expr), |
938 | | |
939 | | // Actor system and process execution |
940 | 3 | Actor { .. } | Send { .. } | Ask { .. } | ActorSend { .. } | ActorQuery { .. } | Command { .. } => self.transpile_actor_expr(expr), |
941 | | |
942 | | // Everything else |
943 | 46 | _ => self.transpile_misc_expr(expr), |
944 | | } |
945 | 1.12k | } |
946 | | } |