Coverage Report

Created: 2025-09-08 21: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
0
pub fn parse_function(state: &mut ParserState) -> Result<Expr> {
13
0
    parse_function_with_visibility(state, false)
14
0
}
15
16
0
pub fn parse_function_with_visibility(state: &mut ParserState, is_pub: bool) -> Result<Expr> {
17
0
    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
0
    let is_async = false;
23
24
    // Parse function name
25
0
    let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
26
0
        let name = n.clone();
27
0
        state.tokens.advance();
28
0
        name
29
    } else {
30
0
        "anonymous".to_string()
31
    };
32
33
    // Parse optional type parameters <T, U, ...>
34
0
    let type_params = if matches!(state.tokens.peek(), Some((Token::Less, _))) {
35
0
        utils::parse_type_parameters(state)?
36
    } else {
37
0
        Vec::new()
38
    };
39
40
    // Parse parameters
41
0
    let params = utils::parse_params(state)?;
42
43
    // Parse return type if present
44
0
    let return_type = if matches!(state.tokens.peek(), Some((Token::Arrow, _))) {
45
0
        state.tokens.advance(); // consume ->
46
0
        Some(utils::parse_type(state)?)
47
    } else {
48
0
        None
49
    };
50
51
    // Parse body
52
0
    let body = super::parse_expr_recursive(state)?;
53
54
0
    Ok(Expr::new(
55
0
        ExprKind::Function {
56
0
            name,
57
0
            type_params,
58
0
            params,
59
0
            return_type,
60
0
            body: Box::new(body),
61
0
            is_async,
62
0
            is_pub,
63
0
        },
64
0
        start_span,
65
0
    ))
66
0
}
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 syntax type and parse accordingly
154
0
    let params = if matches!(state.tokens.peek(), Some((Token::Backslash, _))) {
155
0
        parse_backslash_lambda(state)?
156
    } else {
157
0
        parse_pipe_lambda(state)?
158
    };
159
160
    // Parse body
161
0
    let body = super::parse_expr_recursive(state)?;
162
163
0
    Ok(Expr::new(
164
0
        ExprKind::Lambda {
165
0
            params,
166
0
            body: Box::new(body),
167
0
        },
168
0
        start_span,
169
0
    ))
170
0
}
171
172
/// Parse backslash-style lambda: \x, y -> body (complexity: 6)
173
0
fn parse_backslash_lambda(state: &mut ParserState) -> Result<Vec<Param>> {
174
0
    state.tokens.advance(); // consume \
175
    
176
0
    let params = parse_simple_params(state)?;
177
    
178
    // Expect arrow
179
0
    state.tokens.expect(&Token::Arrow)?;
180
    
181
0
    Ok(params)
182
0
}
183
184
/// Parse pipe-style lambda: |x, y| body (complexity: 5)
185
0
fn parse_pipe_lambda(state: &mut ParserState) -> Result<Vec<Param>> {
186
0
    state.tokens.advance(); // consume |
187
    
188
    // Handle || as empty params
189
0
    if matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
190
0
        state.tokens.advance(); // consume second |
191
0
        return Ok(Vec::new());
192
0
    }
193
    
194
    // Parse parameters between pipes
195
0
    let params = parse_lambda_params(state)?;
196
    
197
    // Expect closing pipe
198
0
    if !matches!(state.tokens.peek(), Some((Token::Pipe, _))) {
199
0
        bail!("Expected '|' after lambda parameters");
200
0
    }
201
0
    state.tokens.advance(); // consume |
202
    
203
0
    Ok(params)
204
0
}
205
206
/// Parse simple comma-separated parameters (complexity: 6)
207
0
fn parse_simple_params(state: &mut ParserState) -> Result<Vec<Param>> {
208
0
    let mut params = Vec::new();
209
    
210
    // Parse first parameter if present
211
0
    if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
212
0
        params.push(create_simple_param(name.clone()));
213
0
        state.tokens.advance();
214
        
215
        // Parse additional parameters
216
0
        while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
217
0
            state.tokens.advance(); // consume comma
218
0
            if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
219
0
                params.push(create_simple_param(name.clone()));
220
0
                state.tokens.advance();
221
0
            }
222
        }
223
0
    }
224
    
225
0
    Ok(params)
226
0
}
227
228
/// Create a simple parameter with default type (complexity: 1)
229
0
fn create_simple_param(name: String) -> Param {
230
0
    Param {
231
0
        pattern: Pattern::Identifier(name),
232
0
        ty: Type {
233
0
            kind: TypeKind::Named("Any".to_string()),
234
0
            span: Span { start: 0, end: 0 },
235
0
        },
236
0
        span: Span { start: 0, end: 0 },
237
0
        is_mutable: false,
238
0
        default_value: None,
239
0
    }
240
0
}
241
242
243
/// # Errors
244
///
245
/// Returns an error if the operation fails
246
/// # Errors
247
///
248
/// Returns an error if the operation fails
249
0
pub fn parse_call(state: &mut ParserState, func: Expr) -> Result<Expr> {
250
0
    state.tokens.advance(); // consume (
251
252
0
    let mut args = Vec::new();
253
0
    while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
254
0
        args.push(super::parse_expr_recursive(state)?);
255
256
0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
257
0
            state.tokens.advance(); // consume comma
258
0
        } else {
259
0
            break;
260
        }
261
    }
262
263
0
    state.tokens.expect(&Token::RightParen)?;
264
265
0
    Ok(Expr {
266
0
        kind: ExprKind::Call {
267
0
            func: Box::new(func),
268
0
            args,
269
0
        },
270
0
        span: Span { start: 0, end: 0 },
271
0
        attributes: Vec::new(),
272
0
    })
273
0
}
274
275
/// # Errors
276
///
277
/// Returns an error if the operation fails
278
/// # Errors
279
///
280
/// Returns an error if the operation fails
281
#[allow(clippy::too_many_lines)]
282
0
pub fn parse_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
283
    // Check for special postfix operators like .await
284
0
    if let Some((Token::Await, _)) = state.tokens.peek() {
285
0
        state.tokens.advance(); // consume await
286
0
        return Ok(Expr {
287
0
            kind: ExprKind::Await {
288
0
                expr: Box::new(receiver),
289
0
            },
290
0
            span: Span { start: 0, end: 0 },
291
0
            attributes: Vec::new(),
292
0
        });
293
0
    }
294
295
    // Parse method name or tuple index
296
0
    match state.tokens.peek() {
297
0
        Some((Token::Identifier(name), _)) => {
298
0
            let method = name.clone();
299
0
            state.tokens.advance();
300
0
            parse_method_or_field_access(state, receiver, method)
301
        }
302
0
        Some((Token::Integer(index), _)) => {
303
            // Handle tuple access like t.0, t.1, etc.
304
0
            let index = *index;
305
0
            state.tokens.advance();
306
0
            Ok(Expr {
307
0
                kind: ExprKind::FieldAccess {
308
0
                    object: Box::new(receiver),
309
0
                    field: index.to_string(),
310
0
                },
311
0
                span: Span { start: 0, end: 0 },
312
0
                attributes: Vec::new(),
313
0
            })
314
        }
315
        _ => {
316
0
            bail!("Expected method name, tuple index, or 'await' after '.'");
317
        }
318
    }
319
0
}
320
321
0
pub fn parse_optional_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> {
322
    // Parse method name or tuple index for optional chaining
323
0
    match state.tokens.peek() {
324
0
        Some((Token::Identifier(name), _)) => {
325
0
            let method = name.clone();
326
0
            state.tokens.advance();
327
0
            parse_optional_method_or_field_access(state, receiver, method)
328
        }
329
0
        Some((Token::Integer(index), _)) => {
330
            // Handle optional tuple access like t?.0, t?.1, etc.
331
0
            let index = *index;
332
0
            state.tokens.advance();
333
0
            Ok(Expr {
334
0
                kind: ExprKind::OptionalFieldAccess {
335
0
                    object: Box::new(receiver),
336
0
                    field: index.to_string(),
337
0
                },
338
0
                span: Span { start: 0, end: 0 },
339
0
                attributes: Vec::new(),
340
0
            })
341
        }
342
        _ => {
343
0
            bail!("Expected method name or tuple index after '?.'");
344
        }
345
    }
346
0
}
347
348
0
fn parse_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
349
    // Check if it's a method call (with parentheses) or field access
350
0
    if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
351
0
        parse_method_call_access(state, receiver, method)
352
    } else {
353
        // Field access
354
0
        Ok(create_field_access(receiver, method))
355
    }
356
0
}
357
358
/// Parse method call with arguments (complexity: 6)
359
0
fn parse_method_call_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
360
0
    state.tokens.advance(); // consume (
361
0
    let args = parse_method_arguments(state)?;
362
0
    state.tokens.expect(&Token::RightParen)?;
363
364
    // Check if this is a DataFrame operation
365
0
    if is_dataframe_method(&method) {
366
0
        handle_dataframe_method(receiver, method, args)
367
    } else {
368
0
        Ok(create_method_call(receiver, method, args))
369
    }
370
0
}
371
372
/// Parse method arguments (complexity: 5)
373
0
fn parse_method_arguments(state: &mut ParserState) -> Result<Vec<Expr>> {
374
0
    let mut args = Vec::new();
375
0
    while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
376
0
        args.push(super::parse_expr_recursive(state)?);
377
378
0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
379
0
            state.tokens.advance(); // consume comma
380
0
        } else {
381
0
            break;
382
        }
383
    }
384
0
    Ok(args)
385
0
}
386
387
/// Check if method is DataFrame-specific (complexity: 1)
388
0
fn is_dataframe_method(method: &str) -> bool {
389
0
    matches!(
390
0
        method,
391
0
        "select" | "groupby" | "group_by" | "agg" | "pivot" | "melt" | 
392
0
        "join" | "rolling" | "shift" | "diff" | "pct_change" | "corr" | "cov"
393
    )
394
0
}
395
396
/// Handle DataFrame-specific methods (complexity: 4)
397
0
fn handle_dataframe_method(receiver: Expr, method: String, args: Vec<Expr>) -> Result<Expr> {
398
0
    let operation = match method.as_str() {
399
0
        "select" => DataFrameOp::Select(extract_select_columns(args)),
400
0
        "groupby" | "group_by" => DataFrameOp::GroupBy(extract_groupby_columns(args)),
401
0
        _ => return Ok(create_method_call(receiver, method, args)),
402
    };
403
404
0
    Ok(Expr {
405
0
        kind: ExprKind::DataFrameOperation {
406
0
            source: Box::new(receiver),
407
0
            operation,
408
0
        },
409
0
        span: Span { start: 0, end: 0 },
410
0
        attributes: Vec::new(),
411
0
    })
412
0
}
413
414
/// Extract column names from select arguments (complexity: 8)
415
0
fn extract_select_columns(args: Vec<Expr>) -> Vec<String> {
416
0
    let mut columns = Vec::new();
417
0
    for arg in args {
418
0
        match arg.kind {
419
            // Handle bare identifiers: .select(age, name)
420
0
            ExprKind::Identifier(name) => {
421
0
                columns.push(name);
422
0
            }
423
            // Handle list literals: .select(["age", "name"])
424
0
            ExprKind::List(items) => {
425
0
                for item in items {
426
0
                    if let ExprKind::Literal(Literal::String(col_name)) = item.kind {
427
0
                        columns.push(col_name);
428
0
                    }
429
                }
430
            }
431
            // Handle single string literals: .select("age")
432
0
            ExprKind::Literal(Literal::String(col_name)) => {
433
0
                columns.push(col_name);
434
0
            }
435
0
            _ => {}
436
        }
437
    }
438
0
    columns
439
0
}
440
441
/// Extract column names from groupby arguments (complexity: 3)
442
0
fn extract_groupby_columns(args: Vec<Expr>) -> Vec<String> {
443
0
    args.into_iter()
444
0
        .filter_map(|arg| {
445
0
            if let ExprKind::Identifier(name) = arg.kind {
446
0
                Some(name)
447
            } else {
448
0
                None
449
            }
450
0
        })
451
0
        .collect()
452
0
}
453
454
/// Create a method call expression (complexity: 1)
455
0
fn create_method_call(receiver: Expr, method: String, args: Vec<Expr>) -> Expr {
456
0
    Expr {
457
0
        kind: ExprKind::MethodCall {
458
0
            receiver: Box::new(receiver),
459
0
            method,
460
0
            args,
461
0
        },
462
0
        span: Span { start: 0, end: 0 },
463
0
        attributes: Vec::new(),
464
0
    }
465
0
}
466
467
/// Create a field access expression (complexity: 1)
468
0
fn create_field_access(receiver: Expr, field: String) -> Expr {
469
0
    Expr {
470
0
        kind: ExprKind::FieldAccess {
471
0
            object: Box::new(receiver),
472
0
            field,
473
0
        },
474
0
        span: Span { start: 0, end: 0 },
475
0
        attributes: Vec::new(),
476
0
    }
477
0
}
478
479
0
fn parse_optional_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
480
    // Check if it's a method call (with parentheses) or field access
481
0
    if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
482
        // Optional method call - convert to OptionalMethodCall AST node
483
        // For now, we'll just parse as regular method call but with optional semantics
484
0
        state.tokens.advance(); // consume (
485
486
0
        let mut args = Vec::new();
487
0
        while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
488
0
            args.push(super::parse_expr_recursive(state)?);
489
490
0
            if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
491
0
                state.tokens.advance(); // consume comma
492
0
            } else {
493
0
                break;
494
            }
495
        }
496
497
0
        state.tokens.expect(&Token::RightParen)?;
498
499
        // Create an OptionalMethodCall expression
500
0
        Ok(Expr {
501
0
            kind: ExprKind::OptionalMethodCall {
502
0
                receiver: Box::new(receiver),
503
0
                method,
504
0
                args,
505
0
            },
506
0
            span: Span { start: 0, end: 0 },
507
0
            attributes: Vec::new(),
508
0
        })
509
    } else {
510
        // Optional field access
511
0
        Ok(Expr {
512
0
            kind: ExprKind::OptionalFieldAccess {
513
0
                object: Box::new(receiver),
514
0
                field: method,
515
0
            },
516
0
            span: Span { start: 0, end: 0 },
517
0
            attributes: Vec::new(),
518
0
        })
519
    }
520
0
}