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/frontend/parser/functions.rs
Line
Count
Source
1
//! Function-related parsing (function definitions, lambdas, calls)
2
3
use super::{ParserState, *};
4
use crate::frontend::ast::{DataFrameOp, Literal, Pattern};
5
6
/// # Errors
7
///
8
/// Returns an error if the operation fails
9
/// # Errors
10
///
11
/// Returns an error if the operation fails
12
47
pub fn parse_function(state: &mut ParserState) -> Result<Expr> {
13
47
    parse_function_with_visibility(state, false)
14
47
}
15
16
47
pub fn parse_function_with_visibility(state: &mut ParserState, is_pub: bool) -> Result<Expr> {
17
47
    let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume fun
18
19
    // Check for async modifier - currently not implemented in lexer
20
    // When async keyword is added to lexer, this will be:
21
    // let is_async = state.tokens.check(&Token::Async);
22
47
    let is_async = false;
23
24
    // Parse function name
25
47
    let name = if let Some((Token::Identifier(
n46
), _)) = state.tokens.peek() {
26
46
        let name = n.clone();
27
46
        state.tokens.advance();
28
46
        name
29
    } else {
30
1
        "anonymous".to_string()
31
    };
32
33
    // Parse optional type parameters <T, U, ...>
34
47
    let type_params = if 
matches!44
(state.tokens.peek(), Some((Token::Less, _))) {
35
3
        utils::parse_type_parameters(state)
?0
36
    } else {
37
44
        Vec::new()
38
    };
39
40
    // Parse parameters
41
47
    let 
params46
= utils::parse_params(state)
?1
;
42
43
    // Parse return type if present
44
46
    let return_type = if 
matches!37
(state.tokens.peek(), Some((Token::Arrow, _))) {
45
9
        state.tokens.advance(); // consume ->
46
9
        Some(utils::parse_type(state)
?0
)
47
    } else {
48
37
        None
49
    };
50
51
    // Parse body
52
46
    let body = super::parse_expr_recursive(state)
?0
;
53
54
46
    Ok(Expr::new(
55
46
        ExprKind::Function {
56
46
            name,
57
46
            type_params,
58
46
            params,
59
46
            return_type,
60
46
            body: Box::new(body),
61
46
            is_async,
62
46
            is_pub,
63
46
        },
64
46
        start_span,
65
46
    ))
66
47
}
67
68
0
fn parse_lambda_params(state: &mut ParserState) -> Result<Vec<Param>> {
69
0
    let mut params = Vec::new();
70
71
    // Parse parameters until we hit a pipe or arrow
72
    loop {
73
        // Check if we've reached the end of parameters
74
0
        if matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
75
0
            break;
76
0
        }
77
78
        // Parse parameter name
79
0
        let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
80
0
            let name = n.clone();
81
0
            state.tokens.advance();
82
0
            name
83
        } else {
84
0
            break; // No more parameters
85
        };
86
87
        // Parse optional type annotation
88
0
        let ty = if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
89
0
            state.tokens.advance(); // consume :
90
0
            utils::parse_type(state)?
91
        } else {
92
            // Default to inferred type - use _ as placeholder
93
0
            Type {
94
0
                kind: TypeKind::Named("_".to_string()),
95
0
                span: Span { start: 0, end: 0 },
96
0
            }
97
        };
98
99
0
        params.push(Param {
100
0
            pattern: Pattern::Identifier(name),
101
0
            ty,
102
0
            span: Span { start: 0, end: 0 },
103
0
            is_mutable: false,
104
0
            default_value: None,
105
0
        });
106
107
        // Check for comma
108
0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
109
0
            state.tokens.advance(); // consume comma
110
0
        } else {
111
0
            break;
112
        }
113
    }
114
115
0
    Ok(params)
116
0
}
117
118
/// # Errors
119
///
120
/// Returns an error if the operation fails
121
/// # Errors
122
///
123
/// Returns an error if the operation fails
124
0
pub fn parse_empty_lambda(state: &mut ParserState) -> Result<Expr> {
125
0
    let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume ||
126
127
    // Lambda syntax: || expr (no => allowed)
128
129
    // Parse the body
130
0
    let body = super::parse_expr_recursive(state)?;
131
132
0
    Ok(Expr::new(
133
0
        ExprKind::Lambda {
134
0
            params: Vec::new(),
135
0
            body: Box::new(body),
136
0
        },
137
0
        start_span,
138
0
    ))
139
0
}
140
141
/// # Errors
142
///
143
/// Returns an error if the operation fails
144
/// # Errors
145
///
146
/// Returns an error if the operation fails
147
0
pub fn parse_lambda(state: &mut ParserState) -> Result<Expr> {
148
0
    let start_span = state
149
0
        .tokens
150
0
        .peek()
151
0
        .map_or(Span { start: 0, end: 0 }, |(_, s)| *s);
152
153
    // Check if it's backslash syntax (\x -> ...) or pipe syntax (|x| ...)
154
0
    if matches!(state.tokens.peek(), Some((Token::Backslash, _))) {
155
0
        state.tokens.advance(); // consume \
156
157
        // Parse parameters (simple identifiers separated by commas)
158
0
        let mut params = Vec::new();
159
160
        // Parse first parameter
161
0
        if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
162
0
            params.push(Param {
163
0
                pattern: Pattern::Identifier(name.clone()),
164
0
                ty: Type {
165
0
                    kind: TypeKind::Named("Any".to_string()),
166
0
                    span: Span { start: 0, end: 0 },
167
0
                },
168
0
                span: Span { start: 0, end: 0 },
169
0
                is_mutable: false,
170
0
                default_value: None,
171
0
            });
172
0
            state.tokens.advance();
173
174
            // Parse additional parameters
175
0
            while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
176
0
                state.tokens.advance(); // consume comma
177
0
                if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
178
0
                    params.push(Param {
179
0
                        pattern: Pattern::Identifier(name.clone()),
180
0
                        ty: Type {
181
0
                            kind: TypeKind::Named("Any".to_string()),
182
0
                            span: Span { start: 0, end: 0 },
183
0
                        },
184
0
                        span: Span { start: 0, end: 0 },
185
0
                        is_mutable: false,
186
0
                        default_value: None,
187
0
                    });
188
0
                    state.tokens.advance();
189
0
                }
190
            }
191
0
        }
192
193
        // Expect arrow
194
0
        state.tokens.expect(&Token::Arrow)?;
195
196
        // Parse body
197
0
        let body = super::parse_expr_recursive(state)?;
198
199
0
        return Ok(Expr::new(
200
0
            ExprKind::Lambda {
201
0
                params,
202
0
                body: Box::new(body),
203
0
            },
204
0
            start_span,
205
0
        ));
206
0
    }
207
208
    // Otherwise, handle pipe syntax |x| ...
209
0
    state.tokens.advance(); // consume |
210
211
    // Handle || as a special case for empty parameter lambdas
212
0
    if matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
213
0
        state.tokens.advance(); // consume second |
214
215
        // Lambda syntax: || expr (no => allowed)
216
217
        // Parse the body
218
0
        let body = super::parse_expr_recursive(state)?;
219
0
        return Ok(Expr::new(
220
0
            ExprKind::Lambda {
221
0
                params: Vec::new(),
222
0
                body: Box::new(body),
223
0
            },
224
0
            start_span,
225
0
        ));
226
0
    }
227
228
    // Parse parameters between pipes: |x, y|
229
0
    let params = parse_lambda_params(state)?;
230
231
    // Check for empty params with single |
232
0
    if !matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
233
0
        bail!("Expected '|' after lambda parameters");
234
0
    }
235
0
    state.tokens.advance(); // consume |
236
237
    // Lambda syntax: |x| expr (no => allowed)
238
239
    // Parse the body
240
0
    let body = super::parse_expr_recursive(state)?;
241
242
0
    Ok(Expr::new(
243
0
        ExprKind::Lambda {
244
0
            params,
245
0
            body: Box::new(body),
246
0
        },
247
0
        start_span,
248
0
    ))
249
0
}
250
251
252
/// # Errors
253
///
254
/// Returns an error if the operation fails
255
/// # Errors
256
///
257
/// Returns an error if the operation fails
258
129
pub fn parse_call(state: &mut ParserState, func: Expr) -> Result<Expr> {
259
129
    state.tokens.advance(); // consume (
260
261
129
    let mut args = Vec::new();
262
156
    while !
matches!151
(state.tokens.peek(), Some((Token::RightParen, _))) {
263
151
        args.push(super::parse_expr_recursive(state)
?0
);
264
265
151
        if 
matches!124
(state.tokens.peek(), Some((Token::Comma, _))) {
266
27
            state.tokens.advance(); // consume comma
267
27
        } else {
268
124
            break;
269
        }
270
    }
271
272
129
    state.tokens.expect(&Token::RightParen)
?0
;
273
274
129
    Ok(Expr {
275
129
        kind: ExprKind::Call {
276
129
            func: Box::new(func),
277
129
            args,
278
129
        },
279
129
        span: Span { start: 0, end: 0 },
280
129
        attributes: Vec::new(),
281
129
    })
282
129
}
283
284
/// # Errors
285
///
286
/// Returns an error if the operation fails
287
/// # Errors
288
///
289
/// Returns an error if the operation fails
290
#[allow(clippy::too_many_lines)]
291
53
pub fn parse_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
292
    // Check for special postfix operators like .await
293
53
    if let Some((Token::Await, _)) = state.tokens.peek() {
294
1
        state.tokens.advance(); // consume await
295
1
        return Ok(Expr {
296
1
            kind: ExprKind::Await {
297
1
                expr: Box::new(receiver),
298
1
            },
299
1
            span: Span { start: 0, end: 0 },
300
1
            attributes: Vec::new(),
301
1
        });
302
52
    }
303
304
    // Parse method name or tuple index
305
52
    match state.tokens.peek() {
306
51
        Some((Token::Identifier(name), _)) => {
307
51
            let method = name.clone();
308
51
            state.tokens.advance();
309
51
            parse_method_or_field_access(state, receiver, method)
310
        }
311
0
        Some((Token::Integer(index), _)) => {
312
            // Handle tuple access like t.0, t.1, etc.
313
0
            let index = *index;
314
0
            state.tokens.advance();
315
0
            Ok(Expr {
316
0
                kind: ExprKind::FieldAccess {
317
0
                    object: Box::new(receiver),
318
0
                    field: index.to_string(),
319
0
                },
320
0
                span: Span { start: 0, end: 0 },
321
0
                attributes: Vec::new(),
322
0
            })
323
        }
324
        _ => {
325
1
            bail!("Expected method name, tuple index, or 'await' after '.'");
326
        }
327
    }
328
53
}
329
330
0
pub fn parse_optional_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
331
    // Parse method name or tuple index for optional chaining
332
0
    match state.tokens.peek() {
333
0
        Some((Token::Identifier(name), _)) => {
334
0
            let method = name.clone();
335
0
            state.tokens.advance();
336
0
            parse_optional_method_or_field_access(state, receiver, method)
337
        }
338
0
        Some((Token::Integer(index), _)) => {
339
            // Handle optional tuple access like t?.0, t?.1, etc.
340
0
            let index = *index;
341
0
            state.tokens.advance();
342
0
            Ok(Expr {
343
0
                kind: ExprKind::OptionalFieldAccess {
344
0
                    object: Box::new(receiver),
345
0
                    field: index.to_string(),
346
0
                },
347
0
                span: Span { start: 0, end: 0 },
348
0
                attributes: Vec::new(),
349
0
            })
350
        }
351
        _ => {
352
0
            bail!("Expected method name or tuple index after '?.'");
353
        }
354
    }
355
0
}
356
357
51
fn parse_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
358
359
    // Check if this is a DataFrame-specific operation method
360
    // Note: filter, map, reduce are array methods, not DataFrame methods
361
    // Only include methods that are DataFrame-exclusive
362
51
    let is_dataframe_method = 
matches!1
(
363
51
        method.as_str(),
364
51
        "select"
365
50
            | "groupby"
366
50
            | "group_by"
367
50
            | "agg"
368
50
            | "pivot"
369
50
            | "melt"
370
50
            | "join"
371
50
            | "rolling"
372
50
            | "shift"
373
50
            | "diff"
374
50
            | "pct_change"
375
50
            | "corr"
376
50
            | "cov"
377
    );
378
379
    // Check if it's a method call (with parentheses) or field access
380
51
    if 
matches!2
(state.tokens.peek(), Some((Token::LeftParen, _))) {
381
        // Method call
382
49
        state.tokens.advance(); // consume (
383
384
49
        let mut args = Vec::new();
385
51
        while !
matches!27
(state.tokens.peek(), Some((Token::RightParen, _))) {
386
27
            args.push(super::parse_expr_recursive(state)
?0
);
387
388
27
            if 
matches!25
(state.tokens.peek(), Some((Token::Comma, _))) {
389
2
                state.tokens.advance(); // consume comma
390
2
            } else {
391
25
                break;
392
            }
393
        }
394
395
49
        state.tokens.expect(&Token::RightParen)
?0
;
396
397
        // Check if this is a DataFrame operation
398
49
        if is_dataframe_method {
399
            // Convert to DataFrame operation based on method name
400
1
            let operation = match method.as_str() {
401
1
                "select" => {
402
                    // Extract column names from arguments
403
1
                    let mut columns = Vec::new();
404
2
                    for 
arg1
in args {
405
0
                        match arg.kind {
406
                            // Handle bare identifiers: .select(age, name)
407
0
                            ExprKind::Identifier(name) => {
408
0
                                columns.push(name);
409
0
                            }
410
                            // Handle list literals: .select(["age", "name"])
411
1
                            ExprKind::List(items) => {
412
2
                                for 
item1
in items {
413
1
                                    if let ExprKind::Literal(Literal::String(col_name)) = item.kind
414
1
                                    {
415
1
                                        columns.push(col_name);
416
1
                                    
}0
417
                                }
418
                            }
419
                            // Handle single string literals: .select("age")
420
0
                            ExprKind::Literal(Literal::String(col_name)) => {
421
0
                                columns.push(col_name);
422
0
                            }
423
0
                            _ => {}
424
                        }
425
                    }
426
1
                    DataFrameOp::Select(columns)
427
                }
428
0
                "groupby" | "group_by" => {
429
0
                    let columns = args
430
0
                        .into_iter()
431
0
                        .filter_map(|arg| {
432
0
                            if let ExprKind::Identifier(name) = arg.kind {
433
0
                                Some(name)
434
                            } else {
435
0
                                None
436
                            }
437
0
                        })
438
0
                        .collect();
439
0
                    DataFrameOp::GroupBy(columns)
440
                }
441
                _ => {
442
                    // For other methods, fall back to regular method call
443
0
                    return Ok(Expr {
444
0
                        kind: ExprKind::MethodCall {
445
0
                            receiver: Box::new(receiver),
446
0
                            method,
447
0
                            args,
448
0
                        },
449
0
                        span: Span { start: 0, end: 0 },
450
0
                        attributes: Vec::new(),
451
0
                    });
452
                }
453
            };
454
455
1
            Ok(Expr {
456
1
                kind: ExprKind::DataFrameOperation {
457
1
                    source: Box::new(receiver),
458
1
                    operation,
459
1
                },
460
1
                span: Span { start: 0, end: 0 },
461
1
                attributes: Vec::new(),
462
1
            })
463
        } else {
464
48
            Ok(Expr {
465
48
                kind: ExprKind::MethodCall {
466
48
                    receiver: Box::new(receiver),
467
48
                    method,
468
48
                    args,
469
48
                },
470
48
                span: Span { start: 0, end: 0 },
471
48
                attributes: Vec::new(),
472
48
            })
473
        }
474
    } else {
475
        // Field access
476
2
        Ok(Expr {
477
2
            kind: ExprKind::FieldAccess {
478
2
                object: Box::new(receiver),
479
2
                field: method,
480
2
            },
481
2
            span: Span { start: 0, end: 0 },
482
2
            attributes: Vec::new(),
483
2
        })
484
    }
485
51
}
486
487
0
fn parse_optional_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
488
    // Check if it's a method call (with parentheses) or field access
489
0
    if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
490
        // Optional method call - convert to OptionalMethodCall AST node
491
        // For now, we'll just parse as regular method call but with optional semantics
492
0
        state.tokens.advance(); // consume (
493
494
0
        let mut args = Vec::new();
495
0
        while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
496
0
            args.push(super::parse_expr_recursive(state)?);
497
498
0
            if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
499
0
                state.tokens.advance(); // consume comma
500
0
            } else {
501
0
                break;
502
            }
503
        }
504
505
0
        state.tokens.expect(&Token::RightParen)?;
506
507
        // Create an OptionalMethodCall expression
508
0
        Ok(Expr {
509
0
            kind: ExprKind::OptionalMethodCall {
510
0
                receiver: Box::new(receiver),
511
0
                method,
512
0
                args,
513
0
            },
514
0
            span: Span { start: 0, end: 0 },
515
0
            attributes: Vec::new(),
516
0
        })
517
    } else {
518
        // Optional field access
519
0
        Ok(Expr {
520
0
            kind: ExprKind::OptionalFieldAccess {
521
0
                object: Box::new(receiver),
522
0
                field: method,
523
0
            },
524
0
            span: Span { start: 0, end: 0 },
525
0
            attributes: Vec::new(),
526
0
        })
527
    }
528
0
}