/home/noah/src/ruchy/src/backend/transpiler/expressions.rs
Line | Count | Source |
1 | | //! Expression transpilation methods |
2 | | |
3 | | #![allow(clippy::missing_errors_doc)] |
4 | | #![allow(clippy::needless_pass_by_value)] // TokenStream by value is intentional for quote! macro |
5 | | |
6 | | use super::Transpiler; |
7 | | use crate::frontend::ast::{BinaryOp::{self, NullCoalesce}, Expr, ExprKind, Literal, StringPart, UnaryOp}; |
8 | | use anyhow::{bail, Result}; |
9 | | use proc_macro2::TokenStream; |
10 | | use quote::{format_ident, quote}; |
11 | | |
12 | | impl Transpiler { |
13 | | /// Transpiles literal values |
14 | 0 | pub fn transpile_literal(lit: &Literal) -> TokenStream { |
15 | 0 | match lit { |
16 | 0 | Literal::Integer(i) => Self::transpile_integer(*i), |
17 | 0 | Literal::Float(f) => quote! { #f }, |
18 | 0 | Literal::Unit => quote! { () }, |
19 | 0 | _ => Self::transpile_simple_literal(lit), |
20 | | } |
21 | 0 | } |
22 | | |
23 | 0 | fn transpile_simple_literal(lit: &Literal) -> TokenStream { |
24 | 0 | match lit { |
25 | 0 | Literal::String(s) => quote! { #s }, |
26 | 0 | Literal::Bool(b) => quote! { #b }, |
27 | 0 | Literal::Char(c) => quote! { #c }, |
28 | 0 | _ => unreachable!(), |
29 | | } |
30 | 0 | } |
31 | | |
32 | 0 | fn transpile_integer(i: i64) -> TokenStream { |
33 | | // Integer literals in Rust need proper type handling |
34 | | // Use i32 for values that fit, i64 otherwise |
35 | 0 | if let Ok(i32_val) = i32::try_from(i) { |
36 | | // Use i32 suffix for clarity and to match struct field types |
37 | 0 | let literal = proc_macro2::Literal::i32_suffixed(i32_val); |
38 | 0 | quote! { #literal } |
39 | | } else { |
40 | | // For large integers, we need i64 suffix |
41 | 0 | let literal = proc_macro2::Literal::i64_suffixed(i); |
42 | 0 | quote! { #literal } |
43 | | } |
44 | 0 | } |
45 | | |
46 | | /// Transpiles string interpolation |
47 | | /// |
48 | | /// # Errors |
49 | | /// Returns an error if expression transpilation fails |
50 | 0 | pub fn transpile_string_interpolation(&self, parts: &[StringPart]) -> Result<TokenStream> { |
51 | 0 | if parts.is_empty() { |
52 | 0 | return Ok(quote! { "" }); |
53 | 0 | } |
54 | | |
55 | 0 | let mut format_string = String::new(); |
56 | 0 | let mut args = Vec::new(); |
57 | | |
58 | 0 | for part in parts { |
59 | 0 | match part { |
60 | 0 | StringPart::Text(s) => { |
61 | 0 | // Escape any format specifiers in literal parts |
62 | 0 | format_string.push_str(&s.replace('{', "{{").replace('}', "}}")); |
63 | 0 | } |
64 | 0 | StringPart::Expr(expr) => { |
65 | 0 | format_string.push_str("{}"); |
66 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
67 | 0 | args.push(expr_tokens); |
68 | | } |
69 | 0 | StringPart::ExprWithFormat { expr, format_spec } => { |
70 | | // Include the format specifier in the format string |
71 | 0 | format_string.push('{'); |
72 | 0 | format_string.push_str(format_spec); |
73 | 0 | format_string.push('}'); |
74 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
75 | 0 | args.push(expr_tokens); |
76 | | } |
77 | | } |
78 | | } |
79 | | |
80 | 0 | Ok(quote! { |
81 | | format!(#format_string #(, #args)*) |
82 | | }) |
83 | 0 | } |
84 | | |
85 | | /// Transpiles binary operations |
86 | 0 | pub fn transpile_binary(&self, left: &Expr, op: BinaryOp, right: &Expr) -> Result<TokenStream> { |
87 | | // Special handling for string concatenation |
88 | | // Only treat as string concatenation if at least one operand is definitely a string |
89 | 0 | if op == BinaryOp::Add && (Self::is_definitely_string(left) || Self::is_definitely_string(right)) { |
90 | 0 | return self.transpile_string_concatenation(left, right); |
91 | 0 | } |
92 | | |
93 | | // Transpile operands with precedence-aware parentheses |
94 | 0 | let left_tokens = self.transpile_expr_with_precedence(left, op, true)?; |
95 | 0 | let right_tokens = self.transpile_expr_with_precedence(right, op, false)?; |
96 | | |
97 | 0 | Ok(Self::transpile_binary_op(left_tokens, op, right_tokens)) |
98 | 0 | } |
99 | | |
100 | | /// Transpile expression with precedence-aware parentheses |
101 | | /// |
102 | | /// Adds parentheses around sub-expressions when needed to preserve precedence |
103 | 0 | fn transpile_expr_with_precedence(&self, expr: &Expr, parent_op: BinaryOp, is_left_operand: bool) -> Result<TokenStream> { |
104 | 0 | let tokens = self.transpile_expr(expr)?; |
105 | | |
106 | | // Check if we need parentheses |
107 | 0 | if let ExprKind::Binary { op: child_op, .. } = &expr.kind { |
108 | 0 | let parent_prec = Self::get_operator_precedence(parent_op); |
109 | 0 | let child_prec = Self::get_operator_precedence(*child_op); |
110 | | |
111 | | // Add parentheses if child has lower precedence |
112 | | // For right operands, also add parentheses if precedence is equal and parent is right-associative |
113 | 0 | let needs_parens = child_prec < parent_prec || |
114 | 0 | (!is_left_operand && child_prec == parent_prec && Self::is_right_associative(parent_op)); |
115 | | |
116 | 0 | if needs_parens { |
117 | 0 | return Ok(quote! { (#tokens) }); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | 0 | Ok(tokens) |
122 | 0 | } |
123 | | |
124 | | /// Get operator precedence (higher number = higher precedence) |
125 | 0 | fn get_operator_precedence(op: BinaryOp) -> i32 { |
126 | 0 | match op { |
127 | 0 | BinaryOp::Or => 10, |
128 | 0 | BinaryOp::And => 20, |
129 | 0 | BinaryOp::Equal | BinaryOp::NotEqual => 30, |
130 | 0 | BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual => 40, |
131 | 0 | BinaryOp::Add | BinaryOp::Subtract => 50, |
132 | 0 | BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 60, |
133 | 0 | BinaryOp::Power => 70, |
134 | 0 | _ => 0, // Default for other operators |
135 | | } |
136 | 0 | } |
137 | | |
138 | | /// Check if operator is right-associative |
139 | 0 | fn is_right_associative(op: BinaryOp) -> bool { |
140 | 0 | matches!(op, BinaryOp::Power) // Only power is right-associative in most languages |
141 | 0 | } |
142 | | |
143 | 0 | fn transpile_binary_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
144 | | use BinaryOp::{ |
145 | | Add, And, BitwiseAnd, BitwiseOr, BitwiseXor, Divide, Equal, Greater, GreaterEqual, |
146 | | LeftShift, Less, LessEqual, Modulo, Multiply, NotEqual, Or, Power, |
147 | | Subtract, |
148 | | }; |
149 | 0 | match op { |
150 | | // Arithmetic operations |
151 | | Add | Subtract | Multiply | Divide | Modulo | Power => { |
152 | 0 | Self::transpile_arithmetic_op(left, op, right) |
153 | | } |
154 | | // Comparison operations |
155 | | Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual => { |
156 | 0 | Self::transpile_comparison_op(left, op, right) |
157 | | } |
158 | | // Logical operations |
159 | 0 | And | Or | NullCoalesce => Self::transpile_logical_op(left, op, right), |
160 | | // Bitwise operations |
161 | | BitwiseAnd | BitwiseOr | BitwiseXor | LeftShift => { |
162 | 0 | Self::transpile_bitwise_op(left, op, right) |
163 | | } |
164 | | } |
165 | 0 | } |
166 | | |
167 | 0 | fn transpile_arithmetic_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
168 | | use BinaryOp::{Add, Divide, Modulo, Multiply, Power, Subtract}; |
169 | 0 | match op { |
170 | | Add | Subtract | Multiply | Divide | Modulo => { |
171 | 0 | Self::transpile_basic_arithmetic(left, op, right) |
172 | | } |
173 | 0 | Power => quote! { #left.pow(#right) }, |
174 | 0 | _ => unreachable!(), |
175 | | } |
176 | 0 | } |
177 | | |
178 | 0 | fn transpile_basic_arithmetic( |
179 | 0 | left: TokenStream, |
180 | 0 | op: BinaryOp, |
181 | 0 | right: TokenStream, |
182 | 0 | ) -> TokenStream { |
183 | | // Reduce complexity by splitting into smaller functions |
184 | 0 | match op { |
185 | 0 | BinaryOp::Add => quote! { #left + #right }, |
186 | 0 | BinaryOp::Subtract => quote! { #left - #right }, |
187 | 0 | BinaryOp::Multiply => quote! { #left * #right }, |
188 | 0 | _ => Self::transpile_division_mod(left, op, right), |
189 | | } |
190 | 0 | } |
191 | | |
192 | 0 | fn transpile_division_mod(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
193 | 0 | match op { |
194 | 0 | BinaryOp::Divide => quote! { #left / #right }, |
195 | 0 | BinaryOp::Modulo => quote! { #left % #right }, |
196 | 0 | _ => unreachable!(), |
197 | | } |
198 | 0 | } |
199 | | |
200 | 0 | fn transpile_comparison_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
201 | | use BinaryOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual}; |
202 | 0 | match op { |
203 | 0 | Equal | NotEqual => Self::transpile_equality(left, op, right), |
204 | 0 | Less | LessEqual | Greater | GreaterEqual => Self::transpile_ordering(left, op, right), |
205 | 0 | _ => unreachable!(), |
206 | | } |
207 | 0 | } |
208 | | |
209 | 0 | fn transpile_equality(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
210 | 0 | match op { |
211 | 0 | BinaryOp::Equal => quote! { #left == #right }, |
212 | 0 | BinaryOp::NotEqual => quote! { #left != #right }, |
213 | 0 | _ => unreachable!(), |
214 | | } |
215 | 0 | } |
216 | | |
217 | 0 | fn transpile_ordering(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
218 | 0 | match op { |
219 | 0 | BinaryOp::Less => quote! { #left < #right }, |
220 | 0 | BinaryOp::LessEqual => quote! { #left <= #right }, |
221 | 0 | _ => Self::transpile_greater_ops(left, op, right), |
222 | | } |
223 | 0 | } |
224 | | |
225 | 0 | fn transpile_greater_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
226 | 0 | match op { |
227 | 0 | BinaryOp::Greater => quote! { #left > #right }, |
228 | 0 | BinaryOp::GreaterEqual => quote! { #left >= #right }, |
229 | 0 | _ => unreachable!(), |
230 | | } |
231 | 0 | } |
232 | | |
233 | 0 | fn transpile_logical_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
234 | 0 | match op { |
235 | 0 | BinaryOp::And => quote! { #left && #right }, |
236 | 0 | BinaryOp::Or => quote! { #left || #right }, |
237 | 0 | _ => unreachable!(), |
238 | | } |
239 | 0 | } |
240 | | |
241 | 0 | fn transpile_bitwise_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
242 | | use BinaryOp::{BitwiseAnd, BitwiseOr, BitwiseXor}; |
243 | 0 | match op { |
244 | 0 | BitwiseAnd => quote! { #left & #right }, |
245 | 0 | BitwiseOr => quote! { #left | #right }, |
246 | 0 | BitwiseXor => quote! { #left ^ #right }, |
247 | 0 | _ => Self::transpile_shift_ops(left, op, right), |
248 | | } |
249 | 0 | } |
250 | | |
251 | 0 | fn transpile_shift_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
252 | 0 | match op { |
253 | 0 | BinaryOp::LeftShift => quote! { #left << #right }, |
254 | 0 | _ => unreachable!(), |
255 | | } |
256 | 0 | } |
257 | | |
258 | | /// Transpiles unary operations |
259 | 0 | pub fn transpile_unary(&self, op: UnaryOp, operand: &Expr) -> Result<TokenStream> { |
260 | 0 | let operand_tokens = self.transpile_expr(operand)?; |
261 | | |
262 | 0 | Ok(match op { |
263 | 0 | UnaryOp::Not | UnaryOp::BitwiseNot => quote! { !#operand_tokens }, |
264 | 0 | UnaryOp::Negate => quote! { -#operand_tokens }, |
265 | 0 | UnaryOp::Reference => quote! { &#operand_tokens }, |
266 | | }) |
267 | 0 | } |
268 | | |
269 | | |
270 | | /// Transpiles await expressions |
271 | 0 | pub fn transpile_await(&self, expr: &Expr) -> Result<TokenStream> { |
272 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
273 | 0 | Ok(quote! { #expr_tokens.await }) |
274 | 0 | } |
275 | | |
276 | | /// Transpiles async blocks |
277 | 0 | pub fn transpile_async_block(&self, body: &Expr) -> Result<TokenStream> { |
278 | 0 | let body_tokens = self.transpile_expr(body)?; |
279 | 0 | Ok(quote! { async { #body_tokens } }) |
280 | 0 | } |
281 | | |
282 | | /// Transpiles throw expressions (panic in Rust) |
283 | 0 | pub fn transpile_throw(&self, expr: &Expr) -> Result<TokenStream> { |
284 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
285 | 0 | Ok(quote! { |
286 | 0 | panic!(#expr_tokens) |
287 | 0 | }) |
288 | 0 | } |
289 | | |
290 | | /// Transpiles field access |
291 | 0 | pub fn transpile_field_access(&self, object: &Expr, field: &str) -> Result<TokenStream> { |
292 | | use crate::frontend::ast::ExprKind; |
293 | | |
294 | 0 | let obj_tokens = self.transpile_expr(object)?; |
295 | | |
296 | | // Check if the object is an ObjectLiteral (HashMap) or module path |
297 | 0 | match &object.kind { |
298 | | ExprKind::ObjectLiteral { .. } => { |
299 | | // Direct object literal access - use get() |
300 | 0 | Ok(quote! { |
301 | 0 | #obj_tokens.get(#field) |
302 | 0 | .cloned() |
303 | 0 | .unwrap_or_else(|| panic!("Field '{}' not found", #field)) |
304 | 0 | }) |
305 | | } |
306 | | ExprKind::FieldAccess { .. } => { |
307 | | // Nested field access like net::TcpListener - use :: syntax |
308 | 0 | let field_ident = format_ident!("{}", field); |
309 | 0 | Ok(quote! { #obj_tokens::#field_ident }) |
310 | | } |
311 | 0 | ExprKind::Identifier(name) if name.contains("::") => { |
312 | | // Module path identifier - use :: syntax |
313 | 0 | let field_ident = format_ident!("{}", field); |
314 | 0 | Ok(quote! { #obj_tokens::#field_ident }) |
315 | | } |
316 | | _ => { |
317 | | // For other cases, assume HashMap access |
318 | 0 | Ok(quote! { |
319 | 0 | #obj_tokens.get(#field) |
320 | 0 | .cloned() |
321 | 0 | .unwrap_or_else(|| panic!("Field '{}' not found", #field)) |
322 | 0 | }) |
323 | | } |
324 | | } |
325 | 0 | } |
326 | | |
327 | | /// Transpiles index access `(array[index])` |
328 | 0 | pub fn transpile_index_access(&self, object: &Expr, index: &Expr) -> Result<TokenStream> { |
329 | | use crate::frontend::ast::{ExprKind, Literal}; |
330 | | |
331 | 0 | let obj_tokens = self.transpile_expr(object)?; |
332 | 0 | let index_tokens = self.transpile_expr(index)?; |
333 | | |
334 | | // Smart index access: HashMap.get() for string keys, array indexing for numeric |
335 | 0 | match &index.kind { |
336 | | // String literal keys use HashMap.get() |
337 | | ExprKind::Literal(Literal::String(_)) => { |
338 | 0 | Ok(quote! { |
339 | 0 | #obj_tokens.get(#index_tokens) |
340 | 0 | .cloned() |
341 | 0 | .unwrap_or_else(|| panic!("Key not found")) |
342 | 0 | }) |
343 | | } |
344 | | // Numeric and other keys use array indexing |
345 | | _ => { |
346 | 0 | Ok(quote! { #obj_tokens[#index_tokens as usize] }) |
347 | | } |
348 | | } |
349 | 0 | } |
350 | | |
351 | | /// Transpiles slice access `(array[start:end])` |
352 | 0 | pub fn transpile_slice(&self, object: &Expr, start: Option<&Expr>, end: Option<&Expr>) -> Result<TokenStream> { |
353 | 0 | let obj_tokens = self.transpile_expr(object)?; |
354 | | |
355 | 0 | match (start, end) { |
356 | | (None, None) => { |
357 | | // Full slice [..] |
358 | 0 | Ok(quote! { &#obj_tokens[..] }) |
359 | | } |
360 | 0 | (None, Some(end)) => { |
361 | | // Slice from beginning [..end] |
362 | 0 | let end_tokens = self.transpile_expr(end)?; |
363 | 0 | Ok(quote! { &#obj_tokens[..#end_tokens as usize] }) |
364 | | } |
365 | 0 | (Some(start), None) => { |
366 | | // Slice to end [start..] |
367 | 0 | let start_tokens = self.transpile_expr(start)?; |
368 | 0 | Ok(quote! { &#obj_tokens[#start_tokens as usize..] }) |
369 | | } |
370 | 0 | (Some(start), Some(end)) => { |
371 | | // Full range slice [start..end] |
372 | 0 | let start_tokens = self.transpile_expr(start)?; |
373 | 0 | let end_tokens = self.transpile_expr(end)?; |
374 | 0 | Ok(quote! { &#obj_tokens[#start_tokens as usize..#end_tokens as usize] }) |
375 | | } |
376 | | } |
377 | 0 | } |
378 | | |
379 | | /// Transpiles assignment |
380 | 0 | pub fn transpile_assign(&self, target: &Expr, value: &Expr) -> Result<TokenStream> { |
381 | 0 | let target_tokens = self.transpile_expr(target)?; |
382 | 0 | let value_tokens = self.transpile_expr(value)?; |
383 | 0 | Ok(quote! { #target_tokens = #value_tokens }) |
384 | 0 | } |
385 | | |
386 | | /// Transpiles compound assignment |
387 | 0 | pub fn transpile_compound_assign( |
388 | 0 | &self, |
389 | 0 | target: &Expr, |
390 | 0 | op: BinaryOp, |
391 | 0 | value: &Expr, |
392 | 0 | ) -> Result<TokenStream> { |
393 | 0 | let target_tokens = self.transpile_expr(target)?; |
394 | 0 | let value_tokens = self.transpile_expr(value)?; |
395 | 0 | let op_tokens = Self::get_compound_op_token(op)?; |
396 | | |
397 | 0 | Ok(quote! { #target_tokens #op_tokens #value_tokens }) |
398 | 0 | } |
399 | | |
400 | 0 | fn get_compound_op_token(op: BinaryOp) -> Result<TokenStream> { |
401 | | use BinaryOp::{Add, Divide, Modulo, Multiply, Subtract}; |
402 | 0 | match op { |
403 | 0 | Add | Subtract | Multiply => Ok(Self::get_basic_compound_token(op)), |
404 | 0 | Divide | Modulo => Ok(Self::get_division_compound_token(op)), |
405 | | _ => { |
406 | | use anyhow::bail; |
407 | 0 | bail!("Invalid operator for compound assignment: {:?}", op) |
408 | | } |
409 | | } |
410 | 0 | } |
411 | | |
412 | 0 | fn get_basic_compound_token(op: BinaryOp) -> TokenStream { |
413 | 0 | match op { |
414 | 0 | BinaryOp::Add => quote! { += }, |
415 | 0 | BinaryOp::Subtract => quote! { -= }, |
416 | 0 | BinaryOp::Multiply => quote! { *= }, |
417 | 0 | _ => unreachable!(), |
418 | | } |
419 | 0 | } |
420 | | |
421 | 0 | fn get_division_compound_token(op: BinaryOp) -> TokenStream { |
422 | 0 | match op { |
423 | 0 | BinaryOp::Divide => quote! { /= }, |
424 | 0 | BinaryOp::Modulo => quote! { %= }, |
425 | 0 | _ => unreachable!(), |
426 | | } |
427 | 0 | } |
428 | | |
429 | | /// Transpiles pre-increment |
430 | 0 | pub fn transpile_pre_increment(&self, target: &Expr) -> Result<TokenStream> { |
431 | 0 | let target_tokens = self.transpile_expr(target)?; |
432 | 0 | Ok(quote! { { #target_tokens += 1; #target_tokens } }) |
433 | 0 | } |
434 | | |
435 | | /// Transpiles post-increment |
436 | 0 | pub fn transpile_post_increment(&self, target: &Expr) -> Result<TokenStream> { |
437 | 0 | let target_tokens = self.transpile_expr(target)?; |
438 | 0 | Ok(quote! { |
439 | 0 | { |
440 | 0 | let _tmp = #target_tokens; |
441 | 0 | #target_tokens += 1; |
442 | 0 | _tmp |
443 | 0 | } |
444 | 0 | }) |
445 | 0 | } |
446 | | |
447 | | /// Transpiles pre-decrement |
448 | 0 | pub fn transpile_pre_decrement(&self, target: &Expr) -> Result<TokenStream> { |
449 | 0 | let target_tokens = self.transpile_expr(target)?; |
450 | 0 | Ok(quote! { { #target_tokens -= 1; #target_tokens } }) |
451 | 0 | } |
452 | | |
453 | | /// Transpiles post-decrement |
454 | 0 | pub fn transpile_post_decrement(&self, target: &Expr) -> Result<TokenStream> { |
455 | 0 | let target_tokens = self.transpile_expr(target)?; |
456 | 0 | Ok(quote! { |
457 | 0 | { |
458 | 0 | let _tmp = #target_tokens; |
459 | 0 | #target_tokens -= 1; |
460 | 0 | _tmp |
461 | 0 | } |
462 | 0 | }) |
463 | 0 | } |
464 | | |
465 | | /// Transpiles list literals |
466 | 0 | pub fn transpile_list(&self, elements: &[Expr]) -> Result<TokenStream> { |
467 | | // Check if any elements are spread expressions |
468 | 0 | let has_spread = elements.iter().any(|e| matches!(e.kind, crate::frontend::ast::ExprKind::Spread { .. })); |
469 | | |
470 | 0 | if has_spread { |
471 | | // Handle spread expressions by building vector with extends |
472 | 0 | let mut statements = Vec::new(); |
473 | 0 | statements.push(quote! { let mut __temp_vec = Vec::new(); }); |
474 | | |
475 | 0 | for element in elements { |
476 | 0 | if let crate::frontend::ast::ExprKind::Spread { expr } = &element.kind { |
477 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
478 | 0 | statements.push(quote! { __temp_vec.extend(#expr_tokens); }); |
479 | | } else { |
480 | 0 | let expr_tokens = self.transpile_expr(element)?; |
481 | 0 | statements.push(quote! { __temp_vec.push(#expr_tokens); }); |
482 | | } |
483 | | } |
484 | | |
485 | 0 | statements.push(quote! { __temp_vec }); |
486 | 0 | Ok(quote! { { #(#statements)* } }) |
487 | | } else { |
488 | | // No spread expressions, use simple vec![] macro |
489 | 0 | let element_tokens: Result<Vec<_>> = |
490 | 0 | elements.iter().map(|e| self.transpile_expr(e)).collect(); |
491 | 0 | let element_tokens = element_tokens?; |
492 | 0 | Ok(quote! { vec![#(#element_tokens),*] }) |
493 | | } |
494 | 0 | } |
495 | | |
496 | | /// Transpiles tuple literals |
497 | 0 | pub fn transpile_tuple(&self, elements: &[Expr]) -> Result<TokenStream> { |
498 | 0 | let element_tokens: Result<Vec<_>> = |
499 | 0 | elements.iter().map(|e| self.transpile_expr(e)).collect(); |
500 | 0 | let element_tokens = element_tokens?; |
501 | 0 | Ok(quote! { (#(#element_tokens),*) }) |
502 | 0 | } |
503 | | |
504 | | /// Transpiles range expressions |
505 | 0 | pub fn transpile_range( |
506 | 0 | &self, |
507 | 0 | start: &Expr, |
508 | 0 | end: &Expr, |
509 | 0 | inclusive: bool, |
510 | 0 | ) -> Result<TokenStream> { |
511 | 0 | let start_tokens = self.transpile_expr(start)?; |
512 | 0 | let end_tokens = self.transpile_expr(end)?; |
513 | | |
514 | 0 | if inclusive { |
515 | 0 | Ok(quote! { #start_tokens..=#end_tokens }) |
516 | | } else { |
517 | 0 | Ok(quote! { #start_tokens..#end_tokens }) |
518 | | } |
519 | 0 | } |
520 | | |
521 | | /// Transpiles object literals |
522 | 0 | pub fn transpile_object_literal( |
523 | 0 | &self, |
524 | 0 | fields: &[crate::frontend::ast::ObjectField], |
525 | 0 | ) -> Result<TokenStream> { |
526 | 0 | let field_tokens = self.collect_hashmap_field_tokens(fields)?; |
527 | 0 | Ok(quote! { |
528 | | { |
529 | | let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new(); |
530 | | #(#field_tokens)* |
531 | | map |
532 | | } |
533 | | }) |
534 | 0 | } |
535 | | |
536 | 0 | fn collect_hashmap_field_tokens( |
537 | 0 | &self, |
538 | 0 | fields: &[crate::frontend::ast::ObjectField], |
539 | 0 | ) -> Result<Vec<TokenStream>> { |
540 | | use crate::frontend::ast::ObjectField; |
541 | 0 | let mut field_tokens = Vec::new(); |
542 | | |
543 | 0 | for field in fields { |
544 | 0 | let token = match field { |
545 | 0 | ObjectField::KeyValue { key, value } => { |
546 | 0 | let value_tokens = self.transpile_expr(value)?; |
547 | 0 | quote! { map.insert(#key.to_string(), (#value_tokens).to_string()); } |
548 | | } |
549 | 0 | ObjectField::Spread { expr } => { |
550 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
551 | | // For spread syntax, merge the other map into this one |
552 | 0 | quote! { |
553 | | for (k, v) in #expr_tokens { |
554 | | map.insert(k, v); |
555 | | } |
556 | | } |
557 | | } |
558 | | }; |
559 | 0 | field_tokens.push(token); |
560 | | } |
561 | | |
562 | 0 | Ok(field_tokens) |
563 | 0 | } |
564 | | |
565 | | |
566 | | /// Transpiles struct literals |
567 | 0 | pub fn transpile_struct_literal( |
568 | 0 | &self, |
569 | 0 | name: &str, |
570 | 0 | fields: &[(String, Expr)], |
571 | 0 | ) -> Result<TokenStream> { |
572 | 0 | let struct_name = format_ident!("{}", name); |
573 | 0 | let mut field_tokens = Vec::new(); |
574 | | |
575 | 0 | for (field_name, value) in fields { |
576 | 0 | let field_ident = format_ident!("{}", field_name); |
577 | 0 | let value_tokens = match &value.kind { |
578 | | // Convert string literals to String for struct fields |
579 | 0 | ExprKind::Literal(Literal::String(s)) => { |
580 | 0 | quote! { #s.to_string() } |
581 | | } |
582 | 0 | _ => self.transpile_expr(value)?, |
583 | | }; |
584 | 0 | field_tokens.push(quote! { #field_ident: #value_tokens }); |
585 | | } |
586 | | |
587 | 0 | Ok(quote! { |
588 | | #struct_name { |
589 | | #(#field_tokens,)* |
590 | | } |
591 | | }) |
592 | 0 | } |
593 | | |
594 | | /// Check if an expression is definitely a string (conservative detection) |
595 | 0 | fn is_definitely_string(expr: &Expr) -> bool { |
596 | 0 | match &expr.kind { |
597 | | // String literals are definitely strings |
598 | 0 | ExprKind::Literal(Literal::String(_)) => true, |
599 | | // String interpolation is definitely strings |
600 | 0 | ExprKind::StringInterpolation { .. } => true, |
601 | | // Binary expressions with + that involve strings are string concatenations |
602 | 0 | ExprKind::Binary { op: BinaryOp::Add, left, right } => { |
603 | 0 | Self::is_definitely_string(left) || Self::is_definitely_string(right) |
604 | | }, |
605 | | // Method calls on strings that return strings |
606 | 0 | ExprKind::MethodCall { receiver, method, .. } => { |
607 | 0 | matches!(method.as_str(), "to_string" | "trim" | "to_uppercase" | "to_lowercase") || |
608 | 0 | Self::is_definitely_string(receiver) |
609 | | }, |
610 | | // Variables could be strings, but we can't be sure without type info |
611 | | // For now, be conservative and don't assume variables are strings |
612 | 0 | ExprKind::Identifier(_) => false, |
613 | | // Function calls are NOT definitely strings - they could return any type |
614 | 0 | ExprKind::Call { .. } => false, |
615 | | // Other expressions are not strings |
616 | 0 | _ => false, |
617 | | } |
618 | 0 | } |
619 | | |
620 | | |
621 | | /// Transpile string concatenation using proper Rust string operations |
622 | 0 | fn transpile_string_concatenation(&self, left: &Expr, right: &Expr) -> Result<TokenStream> { |
623 | 0 | let left_tokens = self.transpile_expr(left)?; |
624 | 0 | let right_tokens = self.transpile_expr(right)?; |
625 | | |
626 | | // Use format! with proper string handling - convert both to strings to avoid type mismatches |
627 | | // This avoids the String + String issue in Rust by using format! exclusively |
628 | 0 | Ok(quote! { format!("{}{}", #left_tokens, #right_tokens) }) |
629 | 0 | } |
630 | | } |
631 | | |
632 | | #[cfg(test)] |
633 | | #[allow(clippy::single_char_pattern)] |
634 | | mod tests { |
635 | | use super::*; |
636 | | use crate::{Parser, frontend::ast::ExprKind}; |
637 | | |
638 | | fn create_transpiler() -> Transpiler { |
639 | | Transpiler::new() |
640 | | } |
641 | | |
642 | | #[test] |
643 | | fn test_transpile_integer_literal() { |
644 | | let transpiler = create_transpiler(); |
645 | | let code = "42"; |
646 | | let mut parser = Parser::new(code); |
647 | | let ast = parser.parse().unwrap(); |
648 | | |
649 | | let result = transpiler.transpile(&ast).unwrap(); |
650 | | let rust_str = result.to_string(); |
651 | | |
652 | | assert!(rust_str.contains("42")); |
653 | | } |
654 | | |
655 | | #[test] |
656 | | fn test_transpile_float_literal() { |
657 | | let transpiler = create_transpiler(); |
658 | | let code = "3.14"; |
659 | | let mut parser = Parser::new(code); |
660 | | let ast = parser.parse().unwrap(); |
661 | | |
662 | | let result = transpiler.transpile(&ast).unwrap(); |
663 | | let rust_str = result.to_string(); |
664 | | |
665 | | assert!(rust_str.contains("3.14")); |
666 | | } |
667 | | |
668 | | #[test] |
669 | | fn test_transpile_string_literal() { |
670 | | let transpiler = create_transpiler(); |
671 | | let code = "\"hello\""; |
672 | | let mut parser = Parser::new(code); |
673 | | let ast = parser.parse().unwrap(); |
674 | | |
675 | | let result = transpiler.transpile(&ast).unwrap(); |
676 | | let rust_str = result.to_string(); |
677 | | |
678 | | assert!(rust_str.contains("hello")); |
679 | | } |
680 | | |
681 | | #[test] |
682 | | fn test_transpile_boolean_literal() { |
683 | | let transpiler = create_transpiler(); |
684 | | let code = "true"; |
685 | | let mut parser = Parser::new(code); |
686 | | let ast = parser.parse().unwrap(); |
687 | | |
688 | | let result = transpiler.transpile(&ast).unwrap(); |
689 | | let rust_str = result.to_string(); |
690 | | |
691 | | assert!(rust_str.contains("true")); |
692 | | } |
693 | | |
694 | | #[test] |
695 | | fn test_transpile_unit_literal() { |
696 | | let transpiler = create_transpiler(); |
697 | | let code = "()"; |
698 | | let mut parser = Parser::new(code); |
699 | | let ast = parser.parse().unwrap(); |
700 | | |
701 | | let result = transpiler.transpile(&ast).unwrap(); |
702 | | let rust_str = result.to_string(); |
703 | | |
704 | | assert!(rust_str.contains("()")); |
705 | | } |
706 | | |
707 | | #[test] |
708 | | fn test_transpile_binary_addition() { |
709 | | let transpiler = create_transpiler(); |
710 | | let code = "5 + 3"; |
711 | | let mut parser = Parser::new(code); |
712 | | let ast = parser.parse().unwrap(); |
713 | | |
714 | | let result = transpiler.transpile(&ast).unwrap(); |
715 | | let rust_str = result.to_string(); |
716 | | |
717 | | assert!(rust_str.contains("5") && rust_str.contains("3")); |
718 | | assert!(rust_str.contains("+")); |
719 | | } |
720 | | |
721 | | #[test] |
722 | | fn test_transpile_binary_subtraction() { |
723 | | let transpiler = create_transpiler(); |
724 | | let code = "10 - 4"; |
725 | | let mut parser = Parser::new(code); |
726 | | let ast = parser.parse().unwrap(); |
727 | | |
728 | | let result = transpiler.transpile(&ast).unwrap(); |
729 | | let rust_str = result.to_string(); |
730 | | |
731 | | assert!(rust_str.contains("10") && rust_str.contains("4")); |
732 | | assert!(rust_str.contains("-")); |
733 | | } |
734 | | |
735 | | #[test] |
736 | | fn test_transpile_binary_multiplication() { |
737 | | let transpiler = create_transpiler(); |
738 | | let code = "6 * 7"; |
739 | | let mut parser = Parser::new(code); |
740 | | let ast = parser.parse().unwrap(); |
741 | | |
742 | | let result = transpiler.transpile(&ast).unwrap(); |
743 | | let rust_str = result.to_string(); |
744 | | |
745 | | assert!(rust_str.contains("6") && rust_str.contains("7")); |
746 | | assert!(rust_str.contains("*")); |
747 | | } |
748 | | |
749 | | #[test] |
750 | | fn test_transpile_binary_division() { |
751 | | let transpiler = create_transpiler(); |
752 | | let code = "15 / 3"; |
753 | | let mut parser = Parser::new(code); |
754 | | let ast = parser.parse().unwrap(); |
755 | | |
756 | | let result = transpiler.transpile(&ast).unwrap(); |
757 | | let rust_str = result.to_string(); |
758 | | |
759 | | assert!(rust_str.contains("15") && rust_str.contains("3")); |
760 | | assert!(rust_str.contains("/")); |
761 | | } |
762 | | |
763 | | #[test] |
764 | | fn test_transpile_binary_modulo() { |
765 | | let transpiler = create_transpiler(); |
766 | | let code = "10 % 3"; |
767 | | let mut parser = Parser::new(code); |
768 | | let ast = parser.parse().unwrap(); |
769 | | |
770 | | let result = transpiler.transpile(&ast).unwrap(); |
771 | | let rust_str = result.to_string(); |
772 | | |
773 | | assert!(rust_str.contains("10") && rust_str.contains("3")); |
774 | | assert!(rust_str.contains("%")); |
775 | | } |
776 | | |
777 | | // Note: String concatenation test removed due to parser limitations with string + operator |
778 | | |
779 | | #[test] |
780 | | fn test_transpile_comparison_operators() { |
781 | | let operators = vec!["<", ">", "<=", ">=", "==", "!="]; |
782 | | |
783 | | for op in operators { |
784 | | let transpiler = create_transpiler(); |
785 | | let code = format!("5 {op} 3"); |
786 | | let mut parser = Parser::new(&code); |
787 | | let ast = parser.parse().unwrap(); |
788 | | |
789 | | let result = transpiler.transpile(&ast).unwrap(); |
790 | | let rust_str = result.to_string(); |
791 | | |
792 | | assert!(rust_str.contains("5") && rust_str.contains("3"), |
793 | | "Failed for operator {op}: {rust_str}"); |
794 | | } |
795 | | } |
796 | | |
797 | | #[test] |
798 | | fn test_transpile_logical_operators() { |
799 | | let operators = vec!["&&", "||"]; |
800 | | |
801 | | for op in operators { |
802 | | let transpiler = create_transpiler(); |
803 | | let code = format!("true {op} false"); |
804 | | let mut parser = Parser::new(&code); |
805 | | let ast = parser.parse().unwrap(); |
806 | | |
807 | | let result = transpiler.transpile(&ast).unwrap(); |
808 | | let rust_str = result.to_string(); |
809 | | |
810 | | assert!(rust_str.contains("true") && rust_str.contains("false"), |
811 | | "Failed for operator {op}: {rust_str}"); |
812 | | } |
813 | | } |
814 | | |
815 | | #[test] |
816 | | fn test_transpile_unary_operators() { |
817 | | let test_cases = vec![ |
818 | | ("!true", "true"), |
819 | | ("-5", "5"), |
820 | | ]; |
821 | | |
822 | | for (code, expected) in test_cases { |
823 | | let transpiler = create_transpiler(); |
824 | | let mut parser = Parser::new(code); |
825 | | let ast = parser.parse().unwrap(); |
826 | | |
827 | | let result = transpiler.transpile(&ast).unwrap(); |
828 | | let rust_str = result.to_string(); |
829 | | |
830 | | assert!(rust_str.contains(expected), |
831 | | "Failed for {code}: {rust_str}"); |
832 | | } |
833 | | } |
834 | | |
835 | | #[test] |
836 | | fn test_transpile_identifier() { |
837 | | let transpiler = create_transpiler(); |
838 | | let code = "variable_name"; |
839 | | let mut parser = Parser::new(code); |
840 | | let ast = parser.parse().unwrap(); |
841 | | |
842 | | let result = transpiler.transpile(&ast).unwrap(); |
843 | | let rust_str = result.to_string(); |
844 | | |
845 | | assert!(rust_str.contains("variable_name")); |
846 | | } |
847 | | |
848 | | #[test] |
849 | | fn test_transpile_function_call() { |
850 | | let transpiler = create_transpiler(); |
851 | | let code = "func_name(arg1, arg2)"; |
852 | | let mut parser = Parser::new(code); |
853 | | let ast = parser.parse().unwrap(); |
854 | | |
855 | | let result = transpiler.transpile(&ast).unwrap(); |
856 | | let rust_str = result.to_string(); |
857 | | |
858 | | assert!(rust_str.contains("func_name")); |
859 | | assert!(rust_str.contains("arg1")); |
860 | | assert!(rust_str.contains("arg2")); |
861 | | } |
862 | | |
863 | | #[test] |
864 | | fn test_transpile_function_call_no_args() { |
865 | | let transpiler = create_transpiler(); |
866 | | let code = "func_name()"; |
867 | | let mut parser = Parser::new(code); |
868 | | let ast = parser.parse().unwrap(); |
869 | | |
870 | | let result = transpiler.transpile(&ast).unwrap(); |
871 | | let rust_str = result.to_string(); |
872 | | |
873 | | assert!(rust_str.contains("func_name")); |
874 | | assert!(rust_str.contains("()")); |
875 | | } |
876 | | |
877 | | #[test] |
878 | | fn test_transpile_list() { |
879 | | let transpiler = create_transpiler(); |
880 | | let code = "[1, 2, 3]"; |
881 | | let mut parser = Parser::new(code); |
882 | | let ast = parser.parse().unwrap(); |
883 | | |
884 | | let result = transpiler.transpile(&ast).unwrap(); |
885 | | let rust_str = result.to_string(); |
886 | | |
887 | | assert!(rust_str.contains("vec!") || rust_str.contains("[")); |
888 | | assert!(rust_str.contains("1") && rust_str.contains("2") && rust_str.contains("3")); |
889 | | } |
890 | | |
891 | | #[test] |
892 | | fn test_transpile_empty_list() { |
893 | | let transpiler = create_transpiler(); |
894 | | let code = "[]"; |
895 | | let mut parser = Parser::new(code); |
896 | | let ast = parser.parse().unwrap(); |
897 | | |
898 | | let result = transpiler.transpile(&ast).unwrap(); |
899 | | let rust_str = result.to_string(); |
900 | | |
901 | | assert!(rust_str.contains("vec!") || rust_str.contains("[]")); |
902 | | } |
903 | | |
904 | | #[test] |
905 | | fn test_transpile_range() { |
906 | | let transpiler = create_transpiler(); |
907 | | let code = "1..10"; |
908 | | let mut parser = Parser::new(code); |
909 | | let ast = parser.parse().unwrap(); |
910 | | |
911 | | let result = transpiler.transpile(&ast).unwrap(); |
912 | | let rust_str = result.to_string(); |
913 | | |
914 | | assert!(rust_str.contains("1") && rust_str.contains("10")); |
915 | | assert!(rust_str.contains("..") || rust_str.contains("range")); |
916 | | } |
917 | | |
918 | | #[test] |
919 | | fn test_transpile_inclusive_range() { |
920 | | let transpiler = create_transpiler(); |
921 | | let code = "1..=10"; |
922 | | let mut parser = Parser::new(code); |
923 | | let ast = parser.parse().unwrap(); |
924 | | |
925 | | let result = transpiler.transpile(&ast).unwrap(); |
926 | | let rust_str = result.to_string(); |
927 | | |
928 | | assert!(rust_str.contains("1") && rust_str.contains("10")); |
929 | | assert!(rust_str.contains("..=") || rust_str.contains("inclusive")); |
930 | | } |
931 | | |
932 | | #[test] |
933 | | fn test_transpile_block_expression() { |
934 | | let transpiler = create_transpiler(); |
935 | | let code = "{ let x = 5; x }"; |
936 | | let mut parser = Parser::new(code); |
937 | | let ast = parser.parse().unwrap(); |
938 | | |
939 | | let result = transpiler.transpile(&ast).unwrap(); |
940 | | let rust_str = result.to_string(); |
941 | | |
942 | | assert!(rust_str.contains("{")); |
943 | | assert!(rust_str.contains("}")); |
944 | | assert!(rust_str.contains("let")); |
945 | | assert!(rust_str.contains("x")); |
946 | | assert!(rust_str.contains("5")); |
947 | | } |
948 | | |
949 | | #[test] |
950 | | fn test_definitely_string_detection() { |
951 | | // String literals should be definitely strings |
952 | | let code1 = "\"hello\""; |
953 | | let mut parser1 = Parser::new(code1); |
954 | | let ast1 = parser1.parse().unwrap(); |
955 | | if let ExprKind::Block(exprs) = &ast1.kind { |
956 | | if let Some(expr) = exprs.first() { |
957 | | assert!(Transpiler::is_definitely_string(expr)); |
958 | | } |
959 | | } |
960 | | |
961 | | // Numbers should not be definitely strings |
962 | | let code2 = "42"; |
963 | | let mut parser2 = Parser::new(code2); |
964 | | let ast2 = parser2.parse().unwrap(); |
965 | | if let ExprKind::Block(exprs) = &ast2.kind { |
966 | | if let Some(expr) = exprs.first() { |
967 | | assert!(!Transpiler::is_definitely_string(expr)); |
968 | | } |
969 | | } |
970 | | } |
971 | | |
972 | | #[test] |
973 | | fn test_complex_nested_expressions() { |
974 | | let transpiler = create_transpiler(); |
975 | | let code = "(5 + 3) * (10 - 2)"; |
976 | | let mut parser = Parser::new(code); |
977 | | let ast = parser.parse().unwrap(); |
978 | | |
979 | | let result = transpiler.transpile(&ast).unwrap(); |
980 | | let rust_str = result.to_string(); |
981 | | |
982 | | // Should handle nested arithmetic with parentheses |
983 | | assert!(rust_str.contains("5") && rust_str.contains("3")); |
984 | | assert!(rust_str.contains("10") && rust_str.contains("2")); |
985 | | assert!(rust_str.contains("+") && rust_str.contains("-") && rust_str.contains("*")); |
986 | | } |
987 | | |
988 | | #[test] |
989 | | fn test_integer_literal_size_handling() { |
990 | | // Small integers |
991 | | assert_eq!( |
992 | | Transpiler::transpile_integer(42).to_string(), |
993 | | "42i32" |
994 | | ); |
995 | | |
996 | | // Large integers |
997 | | #[allow(clippy::unreadable_literal)] |
998 | | let large_int = 9223372036854775807; |
999 | | assert_eq!( |
1000 | | Transpiler::transpile_integer(large_int).to_string(), |
1001 | | "9223372036854775807i64" |
1002 | | ); |
1003 | | |
1004 | | // Negative integers |
1005 | | assert_eq!( |
1006 | | Transpiler::transpile_integer(-42).to_string(), |
1007 | | "- 42i32" |
1008 | | ); |
1009 | | } |
1010 | | |
1011 | | #[test] |
1012 | | fn test_method_call_transpilation() { |
1013 | | let transpiler = create_transpiler(); |
1014 | | let code = "obj.method(arg)"; |
1015 | | let mut parser = Parser::new(code); |
1016 | | let ast = parser.parse().unwrap(); |
1017 | | |
1018 | | let result = transpiler.transpile(&ast).unwrap(); |
1019 | | let rust_str = result.to_string(); |
1020 | | |
1021 | | assert!(rust_str.contains("obj")); |
1022 | | assert!(rust_str.contains("method")); |
1023 | | assert!(rust_str.contains("arg")); |
1024 | | } |
1025 | | |
1026 | | #[test] |
1027 | | fn test_string_interpolation_transpilation() { |
1028 | | let transpiler = create_transpiler(); |
1029 | | let code = "f\"Hello {name}\""; |
1030 | | let mut parser = Parser::new(code); |
1031 | | let ast = parser.parse().unwrap(); |
1032 | | |
1033 | | let result = transpiler.transpile(&ast).unwrap(); |
1034 | | let rust_str = result.to_string(); |
1035 | | |
1036 | | // String interpolation should use format! |
1037 | | assert!(rust_str.contains("format!") || rust_str.contains("Hello")); |
1038 | | assert!(rust_str.contains("name")); |
1039 | | } |
1040 | | } |