Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}