/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 | 444 | pub fn transpile_literal(lit: &Literal) -> TokenStream { |
15 | 444 | match lit { |
16 | 306 | Literal::Integer(i) => Self::transpile_integer(*i), |
17 | 20 | Literal::Float(f) => quote! { #f }, |
18 | 20 | Literal::Unit => quote! { () }, |
19 | 98 | _ => Self::transpile_simple_literal(lit), |
20 | | } |
21 | 444 | } |
22 | | |
23 | 98 | fn transpile_simple_literal(lit: &Literal) -> TokenStream { |
24 | 98 | match lit { |
25 | 65 | Literal::String(s) => quote! { #s }, |
26 | 33 | Literal::Bool(b) => quote! { #b }, |
27 | 0 | Literal::Char(c) => quote! { #c }, |
28 | 0 | _ => unreachable!(), |
29 | | } |
30 | 98 | } |
31 | | |
32 | 309 | 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 | 309 | if let Ok(i32_val239 ) = i32::try_from(i) { |
36 | | // Use i32 suffix for clarity and to match struct field types |
37 | 239 | let literal = proc_macro2::Literal::i32_suffixed(i32_val); |
38 | 239 | quote! { #literal } |
39 | | } else { |
40 | | // For large integers, we need i64 suffix |
41 | 70 | let literal = proc_macro2::Literal::i64_suffixed(i); |
42 | 70 | quote! { #literal } |
43 | | } |
44 | 309 | } |
45 | | |
46 | | /// Transpiles string interpolation |
47 | | /// |
48 | | /// # Errors |
49 | | /// Returns an error if expression transpilation fails |
50 | 5 | pub fn transpile_string_interpolation(&self, parts: &[StringPart]) -> Result<TokenStream> { |
51 | 5 | if parts.is_empty() { |
52 | 0 | return Ok(quote! { "" }); |
53 | 5 | } |
54 | | |
55 | 5 | let mut format_string = String::new(); |
56 | 5 | let mut args = Vec::new(); |
57 | | |
58 | 10 | for part5 in parts { |
59 | 5 | match part { |
60 | 5 | StringPart::Text(s) => { |
61 | 5 | // Escape any format specifiers in literal parts |
62 | 5 | format_string.push_str(&s.replace('{', "{{").replace('}', "}}")); |
63 | 5 | } |
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 | 5 | Ok(quote! { |
81 | | format!(#format_string #(, #args)*) |
82 | | }) |
83 | 5 | } |
84 | | |
85 | | /// Transpiles binary operations |
86 | 151 | 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 | 151 | if op == BinaryOp::Add && (Self::is_definitely_string52 (left52 ) || Self::is_definitely_string51 (right51 )) { |
90 | 1 | return self.transpile_string_concatenation(left, right); |
91 | 150 | } |
92 | | |
93 | | // Transpile operands with precedence-aware parentheses |
94 | 150 | let left_tokens = self.transpile_expr_with_precedence(left, op, true)?0 ; |
95 | 150 | let right_tokens = self.transpile_expr_with_precedence(right, op, false)?0 ; |
96 | | |
97 | 150 | Ok(Self::transpile_binary_op(left_tokens, op, right_tokens)) |
98 | 151 | } |
99 | | |
100 | | /// Transpile expression with precedence-aware parentheses |
101 | | /// |
102 | | /// Adds parentheses around sub-expressions when needed to preserve precedence |
103 | 300 | fn transpile_expr_with_precedence(&self, expr: &Expr, parent_op: BinaryOp, is_left_operand: bool) -> Result<TokenStream> { |
104 | 300 | let tokens = self.transpile_expr(expr)?0 ; |
105 | | |
106 | | // Check if we need parentheses |
107 | 300 | if let ExprKind::Binary { op: child_op30 , .. } = &expr.kind { |
108 | 30 | let parent_prec = Self::get_operator_precedence(parent_op); |
109 | 30 | 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 | 30 | let needs_parens = child_prec < parent_prec || |
114 | 21 | (!is_left_operand && child_prec == parent_prec10 && Self::is_right_associative1 (parent_op1 )); |
115 | | |
116 | 30 | if needs_parens { |
117 | 9 | return Ok(quote! { (#tokens) }); |
118 | 21 | } |
119 | 270 | } |
120 | | |
121 | 291 | Ok(tokens) |
122 | 300 | } |
123 | | |
124 | | /// Get operator precedence (higher number = higher precedence) |
125 | 60 | fn get_operator_precedence(op: BinaryOp) -> i32 { |
126 | 60 | match op { |
127 | 4 | BinaryOp::Or => 10, |
128 | 5 | BinaryOp::And => 20, |
129 | 1 | BinaryOp::Equal | BinaryOp::NotEqual => 30, |
130 | 6 | BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual => 40, |
131 | 18 | BinaryOp::Add | BinaryOp::Subtract => 50, |
132 | 13 | BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 60, |
133 | 2 | BinaryOp::Power => 70, |
134 | 11 | _ => 0, // Default for other operators |
135 | | } |
136 | 60 | } |
137 | | |
138 | | /// Check if operator is right-associative |
139 | 1 | fn is_right_associative(op: BinaryOp) -> bool { |
140 | 1 | matches!(op, BinaryOp::Power) // Only power is right-associative in most languages |
141 | 1 | } |
142 | | |
143 | 150 | 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 | 150 | match op { |
150 | | // Arithmetic operations |
151 | | Add | Subtract | Multiply | Divide | Modulo | Power => { |
152 | 90 | Self::transpile_arithmetic_op(left, op, right) |
153 | | } |
154 | | // Comparison operations |
155 | | Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual => { |
156 | 34 | Self::transpile_comparison_op(left, op, right) |
157 | | } |
158 | | // Logical operations |
159 | 10 | And | Or | NullCoalesce => Self::transpile_logical_op(left, op, right), |
160 | | // Bitwise operations |
161 | | BitwiseAnd | BitwiseOr | BitwiseXor | LeftShift => { |
162 | 16 | Self::transpile_bitwise_op(left, op, right) |
163 | | } |
164 | | } |
165 | 150 | } |
166 | | |
167 | 90 | fn transpile_arithmetic_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
168 | | use BinaryOp::{Add, Divide, Modulo, Multiply, Power, Subtract}; |
169 | 90 | match op { |
170 | | Add | Subtract | Multiply | Divide | Modulo => { |
171 | 86 | Self::transpile_basic_arithmetic(left, op, right) |
172 | | } |
173 | 4 | Power => quote! { #left.pow(#right) }, |
174 | 0 | _ => unreachable!(), |
175 | | } |
176 | 90 | } |
177 | | |
178 | 86 | fn transpile_basic_arithmetic( |
179 | 86 | left: TokenStream, |
180 | 86 | op: BinaryOp, |
181 | 86 | right: TokenStream, |
182 | 86 | ) -> TokenStream { |
183 | | // Reduce complexity by splitting into smaller functions |
184 | 86 | match op { |
185 | 51 | BinaryOp::Add => quote! { #left + #right }, |
186 | 10 | BinaryOp::Subtract => quote! { #left - #right }, |
187 | 16 | BinaryOp::Multiply => quote! { #left * #right }, |
188 | 9 | _ => Self::transpile_division_mod(left, op, right), |
189 | | } |
190 | 86 | } |
191 | | |
192 | 9 | fn transpile_division_mod(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
193 | 9 | match op { |
194 | 4 | BinaryOp::Divide => quote! { #left / #right }, |
195 | 5 | BinaryOp::Modulo => quote! { #left % #right }, |
196 | 0 | _ => unreachable!(), |
197 | | } |
198 | 9 | } |
199 | | |
200 | 34 | fn transpile_comparison_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
201 | | use BinaryOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual}; |
202 | 34 | match op { |
203 | 8 | Equal | NotEqual => Self::transpile_equality(left, op, right), |
204 | 26 | Less | LessEqual | Greater | GreaterEqual => Self::transpile_ordering(left, op, right), |
205 | 0 | _ => unreachable!(), |
206 | | } |
207 | 34 | } |
208 | | |
209 | 8 | fn transpile_equality(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
210 | 8 | match op { |
211 | 5 | BinaryOp::Equal => quote! { #left == #right }, |
212 | 3 | BinaryOp::NotEqual => quote! { #left != #right }, |
213 | 0 | _ => unreachable!(), |
214 | | } |
215 | 8 | } |
216 | | |
217 | 26 | fn transpile_ordering(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
218 | 26 | match op { |
219 | 6 | BinaryOp::Less => quote! { #left < #right }, |
220 | 4 | BinaryOp::LessEqual => quote! { #left <= #right }, |
221 | 16 | _ => Self::transpile_greater_ops(left, op, right), |
222 | | } |
223 | 26 | } |
224 | | |
225 | 16 | fn transpile_greater_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
226 | 16 | match op { |
227 | 8 | BinaryOp::Greater => quote! { #left > #right }, |
228 | 8 | BinaryOp::GreaterEqual => quote! { #left >= #right }, |
229 | 0 | _ => unreachable!(), |
230 | | } |
231 | 16 | } |
232 | | |
233 | 10 | fn transpile_logical_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
234 | 10 | match op { |
235 | 5 | BinaryOp::And => quote! { #left && #right }, |
236 | 5 | BinaryOp::Or => quote! { #left || #right }, |
237 | 0 | _ => unreachable!(), |
238 | | } |
239 | 10 | } |
240 | | |
241 | 16 | fn transpile_bitwise_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
242 | | use BinaryOp::{BitwiseAnd, BitwiseOr, BitwiseXor}; |
243 | 16 | match op { |
244 | 6 | BitwiseAnd => quote! { #left & #right }, |
245 | 1 | BitwiseOr => quote! { #left | #right }, |
246 | 3 | BitwiseXor => quote! { #left ^ #right }, |
247 | 6 | _ => Self::transpile_shift_ops(left, op, right), |
248 | | } |
249 | 16 | } |
250 | | |
251 | 6 | fn transpile_shift_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream { |
252 | 6 | match op { |
253 | 6 | BinaryOp::LeftShift => quote! { #left << #right }, |
254 | 0 | _ => unreachable!(), |
255 | | } |
256 | 6 | } |
257 | | |
258 | | /// Transpiles unary operations |
259 | 50 | pub fn transpile_unary(&self, op: UnaryOp, operand: &Expr) -> Result<TokenStream> { |
260 | 50 | let operand_tokens = self.transpile_expr(operand)?0 ; |
261 | | |
262 | 50 | Ok(match op { |
263 | 23 | UnaryOp::Not | UnaryOp::BitwiseNot => quote! { !#operand_tokens }, |
264 | 12 | UnaryOp::Negate => quote! { -#operand_tokens }, |
265 | 15 | UnaryOp::Reference => quote! { &#operand_tokens }, |
266 | | }) |
267 | 50 | } |
268 | | |
269 | | |
270 | | /// Transpiles await expressions |
271 | 1 | pub fn transpile_await(&self, expr: &Expr) -> Result<TokenStream> { |
272 | 1 | let expr_tokens = self.transpile_expr(expr)?0 ; |
273 | 1 | Ok(quote! { #expr_tokens.await }) |
274 | 1 | } |
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 | 2 | pub fn transpile_field_access(&self, object: &Expr, field: &str) -> Result<TokenStream> { |
292 | | use crate::frontend::ast::ExprKind; |
293 | | |
294 | 2 | let obj_tokens = self.transpile_expr(object)?0 ; |
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 | 1 | let field_ident = format_ident!("{}", field); |
309 | 1 | 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 | 1 | Ok(quote! { |
319 | 1 | #obj_tokens.get(#field) |
320 | 1 | .cloned() |
321 | 1 | .unwrap_or_else(|| panic!("Field '{}' not found", #field)) |
322 | 1 | }) |
323 | | } |
324 | | } |
325 | 2 | } |
326 | | |
327 | | /// Transpiles index access (array[index]) |
328 | 1 | pub fn transpile_index_access(&self, object: &Expr, index: &Expr) -> Result<TokenStream> { |
329 | | use crate::frontend::ast::{ExprKind, Literal}; |
330 | | |
331 | 1 | let obj_tokens = self.transpile_expr(object)?0 ; |
332 | 1 | let index_tokens = self.transpile_expr(index)?0 ; |
333 | | |
334 | | // Smart index access: HashMap.get() for string keys, array indexing for numeric |
335 | 1 | 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 | 1 | Ok(quote! { #obj_tokens[#index_tokens as usize] }) |
347 | | } |
348 | | } |
349 | 1 | } |
350 | | |
351 | | /// Transpiles slice access (array[start:end]) |
352 | 2 | pub fn transpile_slice(&self, object: &Expr, start: Option<&Expr>, end: Option<&Expr>) -> Result<TokenStream> { |
353 | 2 | let obj_tokens = self.transpile_expr(object)?0 ; |
354 | | |
355 | 2 | 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 | 2 | (Some(start), None) => { |
366 | | // Slice to end [start..] |
367 | 2 | let start_tokens = self.transpile_expr(start)?0 ; |
368 | 2 | 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 | 2 | } |
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 | 32 | pub fn transpile_list(&self, elements: &[Expr]) -> Result<TokenStream> { |
467 | 32 | let element_tokens: Result<Vec<_>> = |
468 | 68 | elements32 .iter32 ().map32 (|e| self.transpile_expr(e)).collect32 (); |
469 | 32 | let element_tokens = element_tokens?0 ; |
470 | 32 | Ok(quote! { vec![#(#element_tokens),*] }) |
471 | 32 | } |
472 | | |
473 | | /// Transpiles tuple literals |
474 | 1 | pub fn transpile_tuple(&self, elements: &[Expr]) -> Result<TokenStream> { |
475 | 1 | let element_tokens: Result<Vec<_>> = |
476 | 2 | elements1 .iter1 ().map1 (|e| self.transpile_expr(e)).collect1 (); |
477 | 1 | let element_tokens = element_tokens?0 ; |
478 | 1 | Ok(quote! { (#(#element_tokens),*) }) |
479 | 1 | } |
480 | | |
481 | | /// Transpiles range expressions |
482 | 4 | pub fn transpile_range( |
483 | 4 | &self, |
484 | 4 | start: &Expr, |
485 | 4 | end: &Expr, |
486 | 4 | inclusive: bool, |
487 | 4 | ) -> Result<TokenStream> { |
488 | 4 | let start_tokens = self.transpile_expr(start)?0 ; |
489 | 4 | let end_tokens = self.transpile_expr(end)?0 ; |
490 | | |
491 | 4 | if inclusive { |
492 | 1 | Ok(quote! { #start_tokens..=#end_tokens }) |
493 | | } else { |
494 | 3 | Ok(quote! { #start_tokens..#end_tokens }) |
495 | | } |
496 | 4 | } |
497 | | |
498 | | /// Transpiles object literals |
499 | 4 | pub fn transpile_object_literal( |
500 | 4 | &self, |
501 | 4 | fields: &[crate::frontend::ast::ObjectField], |
502 | 4 | ) -> Result<TokenStream> { |
503 | 4 | let field_tokens = self.collect_hashmap_field_tokens(fields)?0 ; |
504 | 4 | Ok(quote! { |
505 | | { |
506 | | let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new(); |
507 | | #(#field_tokens)* |
508 | | map |
509 | | } |
510 | | }) |
511 | 4 | } |
512 | | |
513 | 4 | fn collect_hashmap_field_tokens( |
514 | 4 | &self, |
515 | 4 | fields: &[crate::frontend::ast::ObjectField], |
516 | 4 | ) -> Result<Vec<TokenStream>> { |
517 | | use crate::frontend::ast::ObjectField; |
518 | 4 | let mut field_tokens = Vec::new(); |
519 | | |
520 | 8 | for field4 in fields { |
521 | 4 | let token = match field { |
522 | 4 | ObjectField::KeyValue { key, value } => { |
523 | 4 | let value_tokens = self.transpile_expr(value)?0 ; |
524 | 4 | quote! { map.insert(#key.to_string(), (#value_tokens).to_string()); } |
525 | | } |
526 | 0 | ObjectField::Spread { expr } => { |
527 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
528 | | // For spread syntax, merge the other map into this one |
529 | 0 | quote! { |
530 | | for (k, v) in #expr_tokens { |
531 | | map.insert(k, v); |
532 | | } |
533 | | } |
534 | | } |
535 | | }; |
536 | 4 | field_tokens.push(token); |
537 | | } |
538 | | |
539 | 4 | Ok(field_tokens) |
540 | 4 | } |
541 | | |
542 | | |
543 | | /// Transpiles struct literals |
544 | 1 | pub fn transpile_struct_literal( |
545 | 1 | &self, |
546 | 1 | name: &str, |
547 | 1 | fields: &[(String, Expr)], |
548 | 1 | ) -> Result<TokenStream> { |
549 | 1 | let struct_name = format_ident!("{}", name); |
550 | 1 | let mut field_tokens = Vec::new(); |
551 | | |
552 | 3 | for (field_name2 , value2 ) in fields { |
553 | 2 | let field_ident = format_ident!("{}", field_name); |
554 | 2 | let value_tokens = match &value.kind { |
555 | | // Convert string literals to String for struct fields |
556 | 0 | ExprKind::Literal(Literal::String(s)) => { |
557 | 0 | quote! { #s.to_string() } |
558 | | } |
559 | 2 | _ => self.transpile_expr(value)?0 , |
560 | | }; |
561 | 2 | field_tokens.push(quote! { #field_ident: #value_tokens }); |
562 | | } |
563 | | |
564 | 1 | Ok(quote! { |
565 | | #struct_name { |
566 | | #(#field_tokens,)* |
567 | | } |
568 | | }) |
569 | 1 | } |
570 | | |
571 | | /// Check if an expression is definitely a string (conservative detection) |
572 | 105 | fn is_definitely_string(expr: &Expr) -> bool { |
573 | 65 | match &expr.kind { |
574 | | // String literals are definitely strings |
575 | 1 | ExprKind::Literal(Literal::String(_)) => true, |
576 | | // String interpolation is definitely strings |
577 | 0 | ExprKind::StringInterpolation { .. } => true, |
578 | | // Binary expressions with + that involve strings are string concatenations |
579 | 1 | ExprKind::Binary { op: BinaryOp::Add, left, right } => { |
580 | 1 | Self::is_definitely_string(left) || Self::is_definitely_string(right) |
581 | | }, |
582 | | // Method calls on strings that return strings |
583 | 0 | ExprKind::MethodCall { receiver, method, .. } => { |
584 | 0 | matches!(method.as_str(), "to_string" | "trim" | "to_uppercase" | "to_lowercase") || |
585 | 0 | Self::is_definitely_string(receiver) |
586 | | }, |
587 | | // Variables could be strings, but we can't be sure without type info |
588 | | // For now, be conservative and don't assume variables are strings |
589 | 26 | ExprKind::Identifier(_) => false, |
590 | | // Function calls are NOT definitely strings - they could return any type |
591 | 5 | ExprKind::Call { .. } => false, |
592 | | // Other expressions are not strings |
593 | 72 | _ => false, |
594 | | } |
595 | 105 | } |
596 | | |
597 | | |
598 | | /// Transpile string concatenation using proper Rust string operations |
599 | 1 | fn transpile_string_concatenation(&self, left: &Expr, right: &Expr) -> Result<TokenStream> { |
600 | 1 | let left_tokens = self.transpile_expr(left)?0 ; |
601 | 1 | let right_tokens = self.transpile_expr(right)?0 ; |
602 | | |
603 | | // Use format! with proper string handling - convert both to strings to avoid type mismatches |
604 | | // This avoids the String + String issue in Rust by using format! exclusively |
605 | 1 | Ok(quote! { format!("{}{}", #left_tokens, #right_tokens) }) |
606 | 1 | } |
607 | | } |
608 | | |
609 | | #[cfg(test)] |
610 | | #[allow(clippy::single_char_pattern)] |
611 | | mod tests { |
612 | | use super::*; |
613 | | use crate::{Parser, frontend::ast::ExprKind}; |
614 | | |
615 | 31 | fn create_transpiler() -> Transpiler { |
616 | 31 | Transpiler::new() |
617 | 31 | } |
618 | | |
619 | | #[test] |
620 | 1 | fn test_transpile_integer_literal() { |
621 | 1 | let transpiler = create_transpiler(); |
622 | 1 | let code = "42"; |
623 | 1 | let mut parser = Parser::new(code); |
624 | 1 | let ast = parser.parse().unwrap(); |
625 | | |
626 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
627 | 1 | let rust_str = result.to_string(); |
628 | | |
629 | 1 | assert!(rust_str.contains("42")); |
630 | 1 | } |
631 | | |
632 | | #[test] |
633 | 1 | fn test_transpile_float_literal() { |
634 | 1 | let transpiler = create_transpiler(); |
635 | 1 | let code = "3.14"; |
636 | 1 | let mut parser = Parser::new(code); |
637 | 1 | let ast = parser.parse().unwrap(); |
638 | | |
639 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
640 | 1 | let rust_str = result.to_string(); |
641 | | |
642 | 1 | assert!(rust_str.contains("3.14")); |
643 | 1 | } |
644 | | |
645 | | #[test] |
646 | 1 | fn test_transpile_string_literal() { |
647 | 1 | let transpiler = create_transpiler(); |
648 | 1 | let code = "\"hello\""; |
649 | 1 | let mut parser = Parser::new(code); |
650 | 1 | let ast = parser.parse().unwrap(); |
651 | | |
652 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
653 | 1 | let rust_str = result.to_string(); |
654 | | |
655 | 1 | assert!(rust_str.contains("hello")); |
656 | 1 | } |
657 | | |
658 | | #[test] |
659 | 1 | fn test_transpile_boolean_literal() { |
660 | 1 | let transpiler = create_transpiler(); |
661 | 1 | let code = "true"; |
662 | 1 | let mut parser = Parser::new(code); |
663 | 1 | let ast = parser.parse().unwrap(); |
664 | | |
665 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
666 | 1 | let rust_str = result.to_string(); |
667 | | |
668 | 1 | assert!(rust_str.contains("true")); |
669 | 1 | } |
670 | | |
671 | | #[test] |
672 | 1 | fn test_transpile_unit_literal() { |
673 | 1 | let transpiler = create_transpiler(); |
674 | 1 | let code = "()"; |
675 | 1 | let mut parser = Parser::new(code); |
676 | 1 | let ast = parser.parse().unwrap(); |
677 | | |
678 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
679 | 1 | let rust_str = result.to_string(); |
680 | | |
681 | 1 | assert!(rust_str.contains("()")); |
682 | 1 | } |
683 | | |
684 | | #[test] |
685 | 1 | fn test_transpile_binary_addition() { |
686 | 1 | let transpiler = create_transpiler(); |
687 | 1 | let code = "5 + 3"; |
688 | 1 | let mut parser = Parser::new(code); |
689 | 1 | let ast = parser.parse().unwrap(); |
690 | | |
691 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
692 | 1 | let rust_str = result.to_string(); |
693 | | |
694 | 1 | assert!(rust_str.contains("5") && rust_str.contains("3")); |
695 | 1 | assert!(rust_str.contains("+")); |
696 | 1 | } |
697 | | |
698 | | #[test] |
699 | 1 | fn test_transpile_binary_subtraction() { |
700 | 1 | let transpiler = create_transpiler(); |
701 | 1 | let code = "10 - 4"; |
702 | 1 | let mut parser = Parser::new(code); |
703 | 1 | let ast = parser.parse().unwrap(); |
704 | | |
705 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
706 | 1 | let rust_str = result.to_string(); |
707 | | |
708 | 1 | assert!(rust_str.contains("10") && rust_str.contains("4")); |
709 | 1 | assert!(rust_str.contains("-")); |
710 | 1 | } |
711 | | |
712 | | #[test] |
713 | 1 | fn test_transpile_binary_multiplication() { |
714 | 1 | let transpiler = create_transpiler(); |
715 | 1 | let code = "6 * 7"; |
716 | 1 | let mut parser = Parser::new(code); |
717 | 1 | let ast = parser.parse().unwrap(); |
718 | | |
719 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
720 | 1 | let rust_str = result.to_string(); |
721 | | |
722 | 1 | assert!(rust_str.contains("6") && rust_str.contains("7")); |
723 | 1 | assert!(rust_str.contains("*")); |
724 | 1 | } |
725 | | |
726 | | #[test] |
727 | 1 | fn test_transpile_binary_division() { |
728 | 1 | let transpiler = create_transpiler(); |
729 | 1 | let code = "15 / 3"; |
730 | 1 | let mut parser = Parser::new(code); |
731 | 1 | let ast = parser.parse().unwrap(); |
732 | | |
733 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
734 | 1 | let rust_str = result.to_string(); |
735 | | |
736 | 1 | assert!(rust_str.contains("15") && rust_str.contains("3")); |
737 | 1 | assert!(rust_str.contains("/")); |
738 | 1 | } |
739 | | |
740 | | #[test] |
741 | 1 | fn test_transpile_binary_modulo() { |
742 | 1 | let transpiler = create_transpiler(); |
743 | 1 | let code = "10 % 3"; |
744 | 1 | let mut parser = Parser::new(code); |
745 | 1 | let ast = parser.parse().unwrap(); |
746 | | |
747 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
748 | 1 | let rust_str = result.to_string(); |
749 | | |
750 | 1 | assert!(rust_str.contains("10") && rust_str.contains("3")); |
751 | 1 | assert!(rust_str.contains("%")); |
752 | 1 | } |
753 | | |
754 | | // Note: String concatenation test removed due to parser limitations with string + operator |
755 | | |
756 | | #[test] |
757 | 1 | fn test_transpile_comparison_operators() { |
758 | 1 | let operators = vec!["<", ">", "<=", ">=", "==", "!="]; |
759 | | |
760 | 7 | for op6 in operators { |
761 | 6 | let transpiler = create_transpiler(); |
762 | 6 | let code = format!("5 {op} 3"); |
763 | 6 | let mut parser = Parser::new(&code); |
764 | 6 | let ast = parser.parse().unwrap(); |
765 | | |
766 | 6 | let result = transpiler.transpile(&ast).unwrap(); |
767 | 6 | let rust_str = result.to_string(); |
768 | | |
769 | 6 | assert!(rust_str.contains("5") && rust_str.contains("3"), |
770 | 0 | "Failed for operator {op}: {rust_str}"); |
771 | | } |
772 | 1 | } |
773 | | |
774 | | #[test] |
775 | 1 | fn test_transpile_logical_operators() { |
776 | 1 | let operators = vec!["&&", "||"]; |
777 | | |
778 | 3 | for op2 in operators { |
779 | 2 | let transpiler = create_transpiler(); |
780 | 2 | let code = format!("true {op} false"); |
781 | 2 | let mut parser = Parser::new(&code); |
782 | 2 | let ast = parser.parse().unwrap(); |
783 | | |
784 | 2 | let result = transpiler.transpile(&ast).unwrap(); |
785 | 2 | let rust_str = result.to_string(); |
786 | | |
787 | 2 | assert!(rust_str.contains("true") && rust_str.contains("false"), |
788 | 0 | "Failed for operator {op}: {rust_str}"); |
789 | | } |
790 | 1 | } |
791 | | |
792 | | #[test] |
793 | 1 | fn test_transpile_unary_operators() { |
794 | 1 | let test_cases = vec![ |
795 | 1 | ("!true", "true"), |
796 | 1 | ("-5", "5"), |
797 | | ]; |
798 | | |
799 | 3 | for (code2 , expected2 ) in test_cases { |
800 | 2 | let transpiler = create_transpiler(); |
801 | 2 | let mut parser = Parser::new(code); |
802 | 2 | let ast = parser.parse().unwrap(); |
803 | | |
804 | 2 | let result = transpiler.transpile(&ast).unwrap(); |
805 | 2 | let rust_str = result.to_string(); |
806 | | |
807 | 2 | assert!(rust_str.contains(expected), |
808 | 0 | "Failed for {code}: {rust_str}"); |
809 | | } |
810 | 1 | } |
811 | | |
812 | | #[test] |
813 | 1 | fn test_transpile_identifier() { |
814 | 1 | let transpiler = create_transpiler(); |
815 | 1 | let code = "variable_name"; |
816 | 1 | let mut parser = Parser::new(code); |
817 | 1 | let ast = parser.parse().unwrap(); |
818 | | |
819 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
820 | 1 | let rust_str = result.to_string(); |
821 | | |
822 | 1 | assert!(rust_str.contains("variable_name")); |
823 | 1 | } |
824 | | |
825 | | #[test] |
826 | 1 | fn test_transpile_function_call() { |
827 | 1 | let transpiler = create_transpiler(); |
828 | 1 | let code = "func_name(arg1, arg2)"; |
829 | 1 | let mut parser = Parser::new(code); |
830 | 1 | let ast = parser.parse().unwrap(); |
831 | | |
832 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
833 | 1 | let rust_str = result.to_string(); |
834 | | |
835 | 1 | assert!(rust_str.contains("func_name")); |
836 | 1 | assert!(rust_str.contains("arg1")); |
837 | 1 | assert!(rust_str.contains("arg2")); |
838 | 1 | } |
839 | | |
840 | | #[test] |
841 | 1 | fn test_transpile_function_call_no_args() { |
842 | 1 | let transpiler = create_transpiler(); |
843 | 1 | let code = "func_name()"; |
844 | 1 | let mut parser = Parser::new(code); |
845 | 1 | let ast = parser.parse().unwrap(); |
846 | | |
847 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
848 | 1 | let rust_str = result.to_string(); |
849 | | |
850 | 1 | assert!(rust_str.contains("func_name")); |
851 | 1 | assert!(rust_str.contains("()")); |
852 | 1 | } |
853 | | |
854 | | #[test] |
855 | 1 | fn test_transpile_list() { |
856 | 1 | let transpiler = create_transpiler(); |
857 | 1 | let code = "[1, 2, 3]"; |
858 | 1 | let mut parser = Parser::new(code); |
859 | 1 | let ast = parser.parse().unwrap(); |
860 | | |
861 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
862 | 1 | let rust_str = result.to_string(); |
863 | | |
864 | 1 | assert!(rust_str.contains("vec!") || rust_str.contains("[")); |
865 | 1 | assert!(rust_str.contains("1") && rust_str.contains("2") && rust_str.contains("3")); |
866 | 1 | } |
867 | | |
868 | | #[test] |
869 | 1 | fn test_transpile_empty_list() { |
870 | 1 | let transpiler = create_transpiler(); |
871 | 1 | let code = "[]"; |
872 | 1 | let mut parser = Parser::new(code); |
873 | 1 | let ast = parser.parse().unwrap(); |
874 | | |
875 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
876 | 1 | let rust_str = result.to_string(); |
877 | | |
878 | 1 | assert!(rust_str.contains("vec!") || rust_str.contains("[]")); |
879 | 1 | } |
880 | | |
881 | | #[test] |
882 | 1 | fn test_transpile_range() { |
883 | 1 | let transpiler = create_transpiler(); |
884 | 1 | let code = "1..10"; |
885 | 1 | let mut parser = Parser::new(code); |
886 | 1 | let ast = parser.parse().unwrap(); |
887 | | |
888 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
889 | 1 | let rust_str = result.to_string(); |
890 | | |
891 | 1 | assert!(rust_str.contains("1") && rust_str.contains("10")); |
892 | 1 | assert!(rust_str.contains("..") || rust_str.contains("range")0 ); |
893 | 1 | } |
894 | | |
895 | | #[test] |
896 | 1 | fn test_transpile_inclusive_range() { |
897 | 1 | let transpiler = create_transpiler(); |
898 | 1 | let code = "1..=10"; |
899 | 1 | let mut parser = Parser::new(code); |
900 | 1 | let ast = parser.parse().unwrap(); |
901 | | |
902 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
903 | 1 | let rust_str = result.to_string(); |
904 | | |
905 | 1 | assert!(rust_str.contains("1") && rust_str.contains("10")); |
906 | 1 | assert!(rust_str.contains("..=") || rust_str.contains("inclusive")0 ); |
907 | 1 | } |
908 | | |
909 | | #[test] |
910 | 1 | fn test_transpile_block_expression() { |
911 | 1 | let transpiler = create_transpiler(); |
912 | 1 | let code = "{ let x = 5; x }"; |
913 | 1 | let mut parser = Parser::new(code); |
914 | 1 | let ast = parser.parse().unwrap(); |
915 | | |
916 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
917 | 1 | let rust_str = result.to_string(); |
918 | | |
919 | 1 | assert!(rust_str.contains("{")); |
920 | 1 | assert!(rust_str.contains("}")); |
921 | 1 | assert!(rust_str.contains("let")); |
922 | 1 | assert!(rust_str.contains("x")); |
923 | 1 | assert!(rust_str.contains("5")); |
924 | 1 | } |
925 | | |
926 | | #[test] |
927 | 1 | fn test_definitely_string_detection() { |
928 | | // String literals should be definitely strings |
929 | 1 | let code1 = "\"hello\""; |
930 | 1 | let mut parser1 = Parser::new(code1); |
931 | 1 | let ast1 = parser1.parse().unwrap(); |
932 | 1 | if let ExprKind::Block(exprs0 ) = &ast1.kind { |
933 | 0 | if let Some(expr) = exprs.first() { |
934 | 0 | assert!(Transpiler::is_definitely_string(expr)); |
935 | 0 | } |
936 | 1 | } |
937 | | |
938 | | // Numbers should not be definitely strings |
939 | 1 | let code2 = "42"; |
940 | 1 | let mut parser2 = Parser::new(code2); |
941 | 1 | let ast2 = parser2.parse().unwrap(); |
942 | 1 | if let ExprKind::Block(exprs0 ) = &ast2.kind { |
943 | 0 | if let Some(expr) = exprs.first() { |
944 | 0 | assert!(!Transpiler::is_definitely_string(expr)); |
945 | 0 | } |
946 | 1 | } |
947 | 1 | } |
948 | | |
949 | | #[test] |
950 | 1 | fn test_complex_nested_expressions() { |
951 | 1 | let transpiler = create_transpiler(); |
952 | 1 | let code = "(5 + 3) * (10 - 2)"; |
953 | 1 | let mut parser = Parser::new(code); |
954 | 1 | let ast = parser.parse().unwrap(); |
955 | | |
956 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
957 | 1 | let rust_str = result.to_string(); |
958 | | |
959 | | // Should handle nested arithmetic with parentheses |
960 | 1 | assert!(rust_str.contains("5") && rust_str.contains("3")); |
961 | 1 | assert!(rust_str.contains("10") && rust_str.contains("2")); |
962 | 1 | assert!(rust_str.contains("+") && rust_str.contains("-") && rust_str.contains("*")); |
963 | 1 | } |
964 | | |
965 | | #[test] |
966 | 1 | fn test_integer_literal_size_handling() { |
967 | | // Small integers |
968 | 1 | assert_eq!( |
969 | 1 | Transpiler::transpile_integer(42).to_string(), |
970 | | "42i32" |
971 | | ); |
972 | | |
973 | | // Large integers |
974 | | #[allow(clippy::unreadable_literal)] |
975 | 1 | let large_int = 9223372036854775807; |
976 | 1 | assert_eq!( |
977 | 1 | Transpiler::transpile_integer(large_int).to_string(), |
978 | | "9223372036854775807i64" |
979 | | ); |
980 | | |
981 | | // Negative integers |
982 | 1 | assert_eq!( |
983 | 1 | Transpiler::transpile_integer(-42).to_string(), |
984 | | "- 42i32" |
985 | | ); |
986 | 1 | } |
987 | | |
988 | | #[test] |
989 | 1 | fn test_method_call_transpilation() { |
990 | 1 | let transpiler = create_transpiler(); |
991 | 1 | let code = "obj.method(arg)"; |
992 | 1 | let mut parser = Parser::new(code); |
993 | 1 | let ast = parser.parse().unwrap(); |
994 | | |
995 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
996 | 1 | let rust_str = result.to_string(); |
997 | | |
998 | 1 | assert!(rust_str.contains("obj")); |
999 | 1 | assert!(rust_str.contains("method")); |
1000 | 1 | assert!(rust_str.contains("arg")); |
1001 | 1 | } |
1002 | | |
1003 | | #[test] |
1004 | 1 | fn test_string_interpolation_transpilation() { |
1005 | 1 | let transpiler = create_transpiler(); |
1006 | 1 | let code = "f\"Hello {name}\""; |
1007 | 1 | let mut parser = Parser::new(code); |
1008 | 1 | let ast = parser.parse().unwrap(); |
1009 | | |
1010 | 1 | let result = transpiler.transpile(&ast).unwrap(); |
1011 | 1 | let rust_str = result.to_string(); |
1012 | | |
1013 | | // String interpolation should use format! |
1014 | 1 | assert!(rust_str.contains("format!") || rust_str.contains("Hello")); |
1015 | 1 | assert!(rust_str.contains("name")); |
1016 | 1 | } |
1017 | | } |