/home/noah/src/ruchy/src/testing/ast_builder.rs
Line | Count | Source |
1 | | //! AST Builder for Testing |
2 | | //! |
3 | | //! Provides convenient methods to construct AST nodes directly without parsing. |
4 | | //! This allows testing transpiler functionality that the parser doesn't support yet. |
5 | | |
6 | | use crate::frontend::ast::{ |
7 | | Expr, ExprKind, Literal, Pattern, MatchArm, Type, TypeKind, |
8 | | Param, Span, BinaryOp, UnaryOp, StringPart, StructPatternField, |
9 | | }; |
10 | | |
11 | | /// Builder for creating AST expressions programmatically |
12 | | pub struct AstBuilder { |
13 | | span: Span, |
14 | | } |
15 | | |
16 | | impl AstBuilder { |
17 | | /// Create a new AST builder |
18 | 3 | pub fn new() -> Self { |
19 | 3 | Self { |
20 | 3 | span: Span::default(), |
21 | 3 | } |
22 | 3 | } |
23 | | |
24 | | /// Create an integer literal |
25 | 2 | pub fn int(&self, value: i64) -> Expr { |
26 | 2 | Expr { |
27 | 2 | kind: ExprKind::Literal(Literal::Integer(value)), |
28 | 2 | span: self.span, |
29 | 2 | attributes: vec![], |
30 | 2 | } |
31 | 2 | } |
32 | | |
33 | | /// Create a float literal |
34 | 0 | pub fn float(&self, value: f64) -> Expr { |
35 | 0 | Expr { |
36 | 0 | kind: ExprKind::Literal(Literal::Float(value)), |
37 | 0 | span: self.span, |
38 | 0 | attributes: vec![], |
39 | 0 | } |
40 | 0 | } |
41 | | |
42 | | /// Create a string literal |
43 | 6 | pub fn string(&self, value: &str) -> Expr { |
44 | 6 | Expr { |
45 | 6 | kind: ExprKind::Literal(Literal::String(value.to_string())), |
46 | 6 | span: self.span, |
47 | 6 | attributes: vec![], |
48 | 6 | } |
49 | 6 | } |
50 | | |
51 | | /// Create a boolean literal |
52 | 0 | pub fn bool(&self, value: bool) -> Expr { |
53 | 0 | Expr { |
54 | 0 | kind: ExprKind::Literal(Literal::Bool(value)), |
55 | 0 | span: self.span, |
56 | 0 | attributes: vec![], |
57 | 0 | } |
58 | 0 | } |
59 | | |
60 | | /// Create an identifier expression |
61 | 4 | pub fn ident(&self, name: &str) -> Expr { |
62 | 4 | Expr { |
63 | 4 | kind: ExprKind::Identifier(name.to_string()), |
64 | 4 | span: self.span, |
65 | 4 | attributes: vec![], |
66 | 4 | } |
67 | 4 | } |
68 | | |
69 | | /// Create a binary operation |
70 | 2 | pub fn binary(&self, left: Expr, op: BinaryOp, right: Expr) -> Expr { |
71 | 2 | Expr { |
72 | 2 | kind: ExprKind::Binary { |
73 | 2 | left: Box::new(left), |
74 | 2 | op, |
75 | 2 | right: Box::new(right), |
76 | 2 | }, |
77 | 2 | span: self.span, |
78 | 2 | attributes: vec![], |
79 | 2 | } |
80 | 2 | } |
81 | | |
82 | | /// Create a unary operation |
83 | 0 | pub fn unary(&self, op: UnaryOp, operand: Expr) -> Expr { |
84 | 0 | Expr { |
85 | 0 | kind: ExprKind::Unary { |
86 | 0 | op, |
87 | 0 | operand: Box::new(operand), |
88 | 0 | }, |
89 | 0 | span: self.span, |
90 | 0 | attributes: vec![], |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | /// Create an if expression |
95 | 1 | pub fn if_expr(&self, condition: Expr, then_branch: Expr, else_branch: Option<Expr>) -> Expr { |
96 | 1 | Expr { |
97 | 1 | kind: ExprKind::If { |
98 | 1 | condition: Box::new(condition), |
99 | 1 | then_branch: Box::new(then_branch), |
100 | 1 | else_branch: else_branch.map(Box::new), |
101 | 1 | }, |
102 | 1 | span: self.span, |
103 | 1 | attributes: vec![], |
104 | 1 | } |
105 | 1 | } |
106 | | |
107 | | /// Create a match expression |
108 | 2 | pub fn match_expr(&self, expr: Expr, arms: Vec<MatchArm>) -> Expr { |
109 | 2 | Expr { |
110 | 2 | kind: ExprKind::Match { |
111 | 2 | expr: Box::new(expr), |
112 | 2 | arms, |
113 | 2 | }, |
114 | 2 | span: self.span, |
115 | 2 | attributes: vec![], |
116 | 2 | } |
117 | 2 | } |
118 | | |
119 | | /// Create a match arm |
120 | 4 | pub fn match_arm(&self, pattern: Pattern, guard: Option<Expr>, body: Expr) -> MatchArm { |
121 | 4 | MatchArm { |
122 | 4 | pattern, |
123 | 4 | guard: guard.map(Box::new), |
124 | 4 | body: Box::new(body), |
125 | 4 | span: self.span, |
126 | 4 | } |
127 | 4 | } |
128 | | |
129 | | /// Create a wildcard pattern |
130 | 2 | pub fn pattern_wildcard(&self) -> Pattern { |
131 | 2 | Pattern::Wildcard |
132 | 2 | } |
133 | | |
134 | | /// Create an identifier pattern |
135 | 1 | pub fn pattern_ident(&self, name: &str) -> Pattern { |
136 | 1 | Pattern::Identifier(name.to_string()) |
137 | 1 | } |
138 | | |
139 | | /// Create a literal pattern |
140 | 3 | pub fn pattern_literal(&self, lit: Literal) -> Pattern { |
141 | 3 | Pattern::Literal(lit) |
142 | 3 | } |
143 | | |
144 | | /// Create a tuple pattern |
145 | 0 | pub fn pattern_tuple(&self, patterns: Vec<Pattern>) -> Pattern { |
146 | 0 | Pattern::Tuple(patterns) |
147 | 0 | } |
148 | | |
149 | | /// Create an or pattern (not supported by parser yet) |
150 | 1 | pub fn pattern_or(&self, patterns: Vec<Pattern>) -> Pattern { |
151 | 1 | Pattern::Or(patterns) |
152 | 1 | } |
153 | | |
154 | | /// Create a struct pattern |
155 | 0 | pub fn pattern_struct(&self, name: String, fields: Vec<(String, Pattern)>) -> Pattern { |
156 | 0 | let struct_fields = fields.into_iter().map(|(name, pattern)| { |
157 | 0 | StructPatternField { name, pattern: Some(pattern) } |
158 | 0 | }).collect(); |
159 | 0 | Pattern::Struct { name, fields: struct_fields, has_rest: false } |
160 | 0 | } |
161 | | |
162 | | /// Create a rest pattern (..) |
163 | 0 | pub fn pattern_rest(&self) -> Pattern { |
164 | 0 | Pattern::Rest |
165 | 0 | } |
166 | | |
167 | | /// Create a function call |
168 | 0 | pub fn call(&self, func: Expr, args: Vec<Expr>) -> Expr { |
169 | 0 | Expr { |
170 | 0 | kind: ExprKind::Call { |
171 | 0 | func: Box::new(func), |
172 | 0 | args, |
173 | 0 | }, |
174 | 0 | span: self.span, |
175 | 0 | attributes: vec![], |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | /// Create a lambda expression |
180 | 0 | pub fn lambda(&self, params: Vec<Param>, body: Expr) -> Expr { |
181 | 0 | Expr { |
182 | 0 | kind: ExprKind::Lambda { |
183 | 0 | params, |
184 | 0 | body: Box::new(body), |
185 | 0 | }, |
186 | 0 | span: self.span, |
187 | 0 | attributes: vec![], |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | | /// Create a block expression |
192 | 0 | pub fn block(&self, statements: Vec<Expr>) -> Expr { |
193 | 0 | Expr { |
194 | 0 | kind: ExprKind::Block(statements), |
195 | 0 | span: self.span, |
196 | 0 | attributes: vec![], |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | /// Create a let expression |
201 | 0 | pub fn let_expr(&self, name: String, value: Expr) -> Expr { |
202 | 0 | Expr { |
203 | 0 | kind: ExprKind::Let { |
204 | 0 | name, |
205 | 0 | value: Box::new(value), |
206 | 0 | type_annotation: None, |
207 | 0 | is_mutable: false, |
208 | 0 | body: Box::new(Expr { |
209 | 0 | kind: ExprKind::Literal(Literal::Unit), |
210 | 0 | span: self.span, |
211 | 0 | attributes: vec![], |
212 | 0 | }), |
213 | 0 | }, |
214 | 0 | span: self.span, |
215 | 0 | attributes: vec![], |
216 | 0 | } |
217 | 0 | } |
218 | | |
219 | | /// Create an assignment |
220 | 0 | pub fn assign(&self, target: Expr, value: Expr) -> Expr { |
221 | 0 | Expr { |
222 | 0 | kind: ExprKind::Assign { |
223 | 0 | target: Box::new(target), |
224 | 0 | value: Box::new(value), |
225 | 0 | }, |
226 | 0 | span: self.span, |
227 | 0 | attributes: vec![], |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | | /// Create a `Result::Ok` variant |
232 | 0 | pub fn ok(&self, value: Expr) -> Expr { |
233 | 0 | Expr { |
234 | 0 | kind: ExprKind::Call { |
235 | 0 | func: Box::new(self.ident("Ok")), |
236 | 0 | args: vec![value], |
237 | 0 | }, |
238 | 0 | span: self.span, |
239 | 0 | attributes: vec![], |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | | /// Create a `Result::Err` variant |
244 | 0 | pub fn err(&self, value: Expr) -> Expr { |
245 | 0 | Expr { |
246 | 0 | kind: ExprKind::Call { |
247 | 0 | func: Box::new(self.ident("Err")), |
248 | 0 | args: vec![value], |
249 | 0 | }, |
250 | 0 | span: self.span, |
251 | 0 | attributes: vec![], |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | | /// Create an `Option::Some` variant |
256 | 0 | pub fn some(&self, value: Expr) -> Expr { |
257 | 0 | Expr { |
258 | 0 | kind: ExprKind::Call { |
259 | 0 | func: Box::new(self.ident("Some")), |
260 | 0 | args: vec![value], |
261 | 0 | }, |
262 | 0 | span: self.span, |
263 | 0 | attributes: vec![], |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | | /// Create an `Option::None` variant |
268 | 0 | pub fn none(&self) -> Expr { |
269 | 0 | self.ident("None") |
270 | 0 | } |
271 | | |
272 | | /// Create a list/array literal |
273 | 0 | pub fn list(&self, elements: Vec<Expr>) -> Expr { |
274 | 0 | Expr { |
275 | 0 | kind: ExprKind::List(elements), |
276 | 0 | span: self.span, |
277 | 0 | attributes: vec![], |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | /// Create a tuple literal |
282 | 0 | pub fn tuple(&self, elements: Vec<Expr>) -> Expr { |
283 | 0 | Expr { |
284 | 0 | kind: ExprKind::Tuple(elements), |
285 | 0 | span: self.span, |
286 | 0 | attributes: vec![], |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | /// Create string interpolation |
291 | 0 | pub fn string_interpolation(&self, parts: Vec<StringPart>) -> Expr { |
292 | 0 | Expr { |
293 | 0 | kind: ExprKind::StringInterpolation { parts }, |
294 | 0 | span: self.span, |
295 | 0 | attributes: vec![], |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | | /// Create a for loop |
300 | 0 | pub fn for_loop(&self, var: String, iter: Expr, body: Expr) -> Expr { |
301 | 0 | Expr { |
302 | 0 | kind: ExprKind::For { |
303 | 0 | var, |
304 | 0 | iter: Box::new(iter), |
305 | 0 | body: Box::new(body), |
306 | 0 | pattern: None, |
307 | 0 | }, |
308 | 0 | span: self.span, |
309 | 0 | attributes: vec![], |
310 | 0 | } |
311 | 0 | } |
312 | | |
313 | | /// Create a while loop |
314 | 0 | pub fn while_loop(&self, condition: Expr, body: Expr) -> Expr { |
315 | 0 | Expr { |
316 | 0 | kind: ExprKind::While { |
317 | 0 | condition: Box::new(condition), |
318 | 0 | body: Box::new(body), |
319 | 0 | }, |
320 | 0 | span: self.span, |
321 | 0 | attributes: vec![], |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | /// Create a loop expression |
326 | 0 | pub fn loop_expr(&self, body: Expr) -> Expr { |
327 | 0 | Expr { |
328 | 0 | kind: ExprKind::Loop { |
329 | 0 | body: Box::new(body), |
330 | 0 | }, |
331 | 0 | span: self.span, |
332 | 0 | attributes: vec![], |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | /// Create a break expression |
337 | 0 | pub fn break_expr(&self, label: Option<String>) -> Expr { |
338 | 0 | Expr { |
339 | 0 | kind: ExprKind::Break { label }, |
340 | 0 | span: self.span, |
341 | 0 | attributes: vec![], |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | | /// Create a continue expression |
346 | 0 | pub fn continue_expr(&self, label: Option<String>) -> Expr { |
347 | 0 | Expr { |
348 | 0 | kind: ExprKind::Continue { label }, |
349 | 0 | span: self.span, |
350 | 0 | attributes: vec![], |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | | /// Create a return expression |
355 | 0 | pub fn return_expr(&self, value: Option<Expr>) -> Expr { |
356 | 0 | Expr { |
357 | 0 | kind: ExprKind::Return { value: value.map(Box::new) }, |
358 | 0 | span: self.span, |
359 | 0 | attributes: vec![], |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | | /// Create a type annotation |
364 | 0 | pub fn type_int(&self) -> Type { |
365 | 0 | Type { |
366 | 0 | kind: TypeKind::Named("i32".to_string()), |
367 | 0 | span: self.span, |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | /// Create a Result type |
372 | 0 | pub fn type_result(&self, ok: Type, err: Type) -> Type { |
373 | 0 | Type { |
374 | 0 | kind: TypeKind::Generic { |
375 | 0 | base: "Result".to_string(), |
376 | 0 | params: vec![ok, err], |
377 | 0 | }, |
378 | 0 | span: self.span, |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | /// Create an Option type |
383 | 0 | pub fn type_option(&self, inner: Type) -> Type { |
384 | 0 | Type { |
385 | 0 | kind: TypeKind::Generic { |
386 | 0 | base: "Option".to_string(), |
387 | 0 | params: vec![inner], |
388 | 0 | }, |
389 | 0 | span: self.span, |
390 | 0 | } |
391 | 0 | } |
392 | | } |
393 | | |
394 | | impl Default for AstBuilder { |
395 | 0 | fn default() -> Self { |
396 | 0 | Self::new() |
397 | 0 | } |
398 | | } |
399 | | |
400 | | #[cfg(test)] |
401 | | mod tests { |
402 | | use super::*; |
403 | | use crate::Transpiler; |
404 | | |
405 | | #[test] |
406 | 1 | fn test_ast_builder_basic() { |
407 | 1 | let builder = AstBuilder::new(); |
408 | | |
409 | | // Create: if x > 0 { "positive" } else { "negative" } |
410 | 1 | let ast = builder.if_expr( |
411 | 1 | builder.binary( |
412 | 1 | builder.ident("x"), |
413 | 1 | BinaryOp::Greater, |
414 | 1 | builder.int(0), |
415 | | ), |
416 | 1 | builder.string("positive"), |
417 | 1 | Some(builder.string("negative")), |
418 | | ); |
419 | | |
420 | | // Should be able to transpile |
421 | 1 | let transpiler = Transpiler::new(); |
422 | 1 | let result = transpiler.transpile(&ast); |
423 | 1 | assert!(result.is_ok()); |
424 | 1 | } |
425 | | |
426 | | #[test] |
427 | 1 | fn test_ast_builder_match_with_guard() { |
428 | 1 | let builder = AstBuilder::new(); |
429 | | |
430 | | // Create match with pattern guard (parser doesn't support this) |
431 | 1 | let ast = builder.match_expr( |
432 | 1 | builder.ident("x"), |
433 | 1 | vec![ |
434 | 1 | builder.match_arm( |
435 | 1 | builder.pattern_ident("n"), |
436 | 1 | Some(builder.binary( |
437 | 1 | builder.ident("n"), |
438 | 1 | BinaryOp::Greater, |
439 | 1 | builder.int(0), |
440 | 1 | )), |
441 | 1 | builder.string("positive"), |
442 | | ), |
443 | 1 | builder.match_arm( |
444 | 1 | builder.pattern_wildcard(), |
445 | 1 | None, |
446 | 1 | builder.string("other"), |
447 | | ), |
448 | | ], |
449 | | ); |
450 | | |
451 | | // Should be able to transpile even though parser can't parse this |
452 | 1 | let transpiler = Transpiler::new(); |
453 | 1 | let result = transpiler.transpile(&ast); |
454 | 1 | assert!(result.is_ok()); |
455 | 1 | } |
456 | | |
457 | | #[test] |
458 | 1 | fn test_ast_builder_or_pattern() { |
459 | 1 | let builder = AstBuilder::new(); |
460 | | |
461 | | // Create or-pattern (parser doesn't support this) |
462 | 1 | let ast = builder.match_expr( |
463 | 1 | builder.ident("x"), |
464 | 1 | vec![ |
465 | 1 | builder.match_arm( |
466 | 1 | builder.pattern_or(vec![ |
467 | 1 | builder.pattern_literal(Literal::Integer(1)), |
468 | 1 | builder.pattern_literal(Literal::Integer(2)), |
469 | 1 | builder.pattern_literal(Literal::Integer(3)), |
470 | | ]), |
471 | 1 | None, |
472 | 1 | builder.string("small"), |
473 | | ), |
474 | 1 | builder.match_arm( |
475 | 1 | builder.pattern_wildcard(), |
476 | 1 | None, |
477 | 1 | builder.string("other"), |
478 | | ), |
479 | | ], |
480 | | ); |
481 | | |
482 | 1 | let transpiler = Transpiler::new(); |
483 | 1 | let result = transpiler.transpile(&ast); |
484 | 1 | assert!(result.is_ok()); |
485 | 1 | } |
486 | | } |