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/collections.rs
Line
Count
Source
1
//! Collections parsing (lists, dataframes, comprehensions, blocks, object literals)
2
3
use super::{ParserState, *};
4
use crate::frontend::ast::{DataFrameColumn, Literal, ObjectField};
5
6
/// Parse a block expression or object literal
7
///
8
/// Blocks are sequences of expressions enclosed in braces `{}`. This function
9
/// intelligently detects whether the content represents a block of statements
10
/// or an object literal based on the syntax patterns.
11
///
12
/// # Examples
13
///
14
/// ```
15
/// use ruchy::Parser;
16
///
17
/// let input = "{ let x = 5; x + 1 }";
18
/// let mut parser = Parser::new(input);
19
/// let result = parser.parse();
20
/// assert!(result.is_ok());
21
/// ```
22
///
23
/// # Errors
24
///
25
/// Returns an error if:
26
/// - The opening brace is missing (should be handled by caller)
27
/// - Failed to parse any expression within the block
28
/// - Missing closing brace
29
/// - Invalid object literal syntax when detected as object
30
/// # Errors
31
///
32
/// Returns an error if the operation fails
33
/// Parse a block expression { ... } (complexity: 7)
34
///
35
/// Handles both regular blocks and let-statement conversion to let-expressions
36
0
pub fn parse_block(state: &mut ParserState) -> Result<Expr> {
37
0
    let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume {
38
39
    // Check if this might be an object literal
40
0
    if is_object_literal(state) {
41
0
        return parse_object_literal_body(state, start_span);
42
0
    }
43
44
0
    let exprs = parse_block_expressions(state, start_span)?;
45
0
    state.tokens.expect(&Token::RightBrace)?;
46
    
47
0
    Ok(create_block_result(exprs, start_span))
48
0
}
49
50
/// Parse all expressions within a block (complexity: 8)
51
0
fn parse_block_expressions(state: &mut ParserState, start_span: Span) -> Result<Vec<Expr>> {
52
0
    let mut exprs = Vec::new();
53
    
54
0
    while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
55
0
        let expr = parse_next_block_expression(state, start_span)?;
56
0
        exprs.push(expr);
57
        
58
0
        consume_optional_semicolon(state);
59
        
60
0
        if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
61
0
            break;
62
0
        }
63
    }
64
    
65
0
    Ok(exprs)
66
0
}
67
68
/// Parse the next expression in a block, handling let statements (complexity: 9)
69
0
fn parse_next_block_expression(state: &mut ParserState, start_span: Span) -> Result<Expr> {
70
0
    if matches!(state.tokens.peek(), Some((Token::Let, _))) {
71
0
        parse_potential_let_statement(state, start_span)
72
    } else {
73
0
        super::parse_expr_recursive(state)
74
    }
75
0
}
76
77
/// Handle potential let statement with lookahead (complexity: 10)
78
0
fn parse_potential_let_statement(state: &mut ParserState, start_span: Span) -> Result<Expr> {
79
0
    let saved_pos = state.tokens.position();
80
0
    state.tokens.advance(); // consume let
81
    
82
0
    if let Some(let_info) = try_parse_let_binding(state)? {
83
0
        if is_let_expression(state) {
84
            // Let expression - restore and parse normally
85
0
            state.tokens.set_position(saved_pos);
86
0
            super::parse_expr_recursive(state)
87
        } else {
88
            // Let statement - convert to let expression
89
0
            create_let_statement_expression(state, let_info, start_span)
90
        }
91
    } else {
92
        // Not a valid let - restore and parse as expression
93
0
        state.tokens.set_position(saved_pos);
94
0
        super::parse_expr_recursive(state)
95
    }
96
0
}
97
98
/// Try to parse let binding info (complexity: 6)
99
0
fn try_parse_let_binding(state: &mut ParserState) -> Result<Option<LetBindingInfo>> {
100
0
    if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
101
0
        let name = name.clone();
102
0
        state.tokens.advance();
103
        
104
0
        if matches!(state.tokens.peek(), Some((Token::Equal, _))) {
105
0
            state.tokens.advance(); // consume =
106
0
            let value = super::parse_expr_recursive(state)?;
107
0
            return Ok(Some(LetBindingInfo { name, value }));
108
0
        }
109
0
    }
110
0
    Ok(None)
111
0
}
112
113
/// Check if this is a let expression (has 'in' keyword) (complexity: 2)
114
0
fn is_let_expression(state: &mut ParserState) -> bool {
115
0
    matches!(state.tokens.peek(), Some((Token::In, _)))
116
0
}
117
118
/// Create let statement expression from binding info (complexity: 8)
119
0
fn create_let_statement_expression(state: &mut ParserState, let_info: LetBindingInfo, start_span: Span) -> Result<Expr> {
120
0
    consume_optional_semicolon(state);
121
    
122
0
    let body = parse_remaining_block_body(state, start_span)?;
123
    
124
0
    Ok(Expr::new(
125
0
        ExprKind::Let {
126
0
            name: let_info.name,
127
0
            type_annotation: None,
128
0
            value: Box::new(let_info.value),
129
0
            body: Box::new(body),
130
0
            is_mutable: false,
131
0
        },
132
0
        start_span,
133
0
    ))
134
0
}
135
136
/// Parse remaining expressions as block body (complexity: 8)
137
0
fn parse_remaining_block_body(state: &mut ParserState, start_span: Span) -> Result<Expr> {
138
0
    let mut body_exprs = Vec::new();
139
    
140
0
    while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
141
0
        body_exprs.push(super::parse_expr_recursive(state)?);
142
        
143
0
        consume_optional_semicolon(state);
144
        
145
0
        if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
146
0
            break;
147
0
        }
148
    }
149
    
150
0
    Ok(create_body_expression(body_exprs, start_span))
151
0
}
152
153
/// Create body expression from parsed expressions (complexity: 4)
154
0
fn create_body_expression(body_exprs: Vec<Expr>, start_span: Span) -> Expr {
155
0
    if body_exprs.is_empty() {
156
0
        Expr::new(ExprKind::Literal(Literal::Unit), start_span)
157
0
    } else if body_exprs.len() == 1 {
158
0
        body_exprs.into_iter().next().expect("checked: len == 1")
159
    } else {
160
0
        Expr::new(ExprKind::Block(body_exprs), start_span)
161
    }
162
0
}
163
164
/// Create final block result (complexity: 3)
165
0
fn create_block_result(exprs: Vec<Expr>, start_span: Span) -> Expr {
166
0
    if exprs.is_empty() {
167
0
        Expr::new(ExprKind::Literal(Literal::Unit), start_span)
168
    } else {
169
0
        Expr::new(ExprKind::Block(exprs), start_span)
170
    }
171
0
}
172
173
/// Consume optional semicolon (complexity: 2)
174
0
fn consume_optional_semicolon(state: &mut ParserState) {
175
0
    if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) {
176
0
        state.tokens.advance();
177
0
    }
178
0
}
179
180
/// Information about a let binding (complexity: 1)
181
#[derive(Debug, Clone)]
182
struct LetBindingInfo {
183
    name: String,
184
    value: Expr,
185
}
186
187
/// Check if the current position looks like an object literal
188
///
189
/// Analyzes the upcoming tokens to determine if the content should be parsed
190
/// as an object literal rather than a regular block. Object literals have
191
/// specific patterns like `key: value` pairs or spread operators `...expr`.
192
///
193
/// # Examples
194
///
195
/// Returns `true` for patterns like:
196
/// - `{ name: "John" }`
197
/// - `{ ...other }`
198
/// - `{ "key": value }`
199
///
200
/// Returns `false` for:
201
/// - `{ x + 1 }`
202
/// - `{ let x = 5 }`
203
/// - `{ }`
204
///
205
/// # Errors
206
///
207
/// Returns an error if token stream operations fail during lookahead.
208
0
fn is_object_literal(state: &mut ParserState) -> bool {
209
    // Peek at the next few tokens to determine if this is an object literal
210
    // Object literal patterns:
211
    // 1. { key: value, ... }
212
    // 2. { ...spread }
213
    // 3. { } (empty)
214
215
    // Empty braces could be either - default to block
216
0
    if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
217
0
        return false;
218
0
    }
219
220
    // Check for spread operator
221
0
    if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) {
222
0
        return true;
223
0
    }
224
225
    // Check for identifier/string followed by colon or fat arrow (book compatibility)
226
0
    match state.tokens.peek() {
227
        Some((Token::Identifier(_) | Token::String(_) | Token::RawString(_), _)) => {
228
            // Look ahead for colon or fat arrow
229
0
            let saved_pos = state.tokens.position();
230
0
            state.tokens.advance(); // skip identifier/string
231
0
            let has_separator = matches!(
232
0
                state.tokens.peek(),
233
                Some((Token::Colon | Token::FatArrow, _))
234
            );
235
0
            state.tokens.set_position(saved_pos); // restore position
236
0
            has_separator
237
        }
238
0
        _ => false,
239
    }
240
0
}
241
242
/// Parse an object key, handling identifiers, strings, and reserved words
243
/// Complexity: 8 (extracted from `parse_object_literal_body`)
244
0
fn parse_object_key(state: &mut ParserState) -> Result<String> {
245
0
    if let Some((token, _)) = state.tokens.peek() {
246
0
        let key = token_to_object_key(token)?;
247
0
        state.tokens.advance();
248
0
        Ok(key)
249
    } else {
250
0
        bail!("Expected key in object literal")
251
    }
252
0
}
253
254
/// Convert a token to an object key string
255
/// Complexity: 7 (simple match statement)
256
0
fn token_to_object_key(token: &Token) -> Result<String> {
257
0
    match token {
258
0
        Token::Identifier(name) => Ok(name.clone()),
259
0
        Token::String(s) | Token::RawString(s) => Ok(s.clone()),
260
        // Allow reserved words as object keys
261
0
        Token::Command => Ok("command".to_string()),
262
0
        Token::Type => Ok("type".to_string()),
263
0
        Token::Module => Ok("module".to_string()),
264
0
        Token::Import => Ok("import".to_string()),
265
0
        Token::Export => Ok("export".to_string()),
266
0
        Token::Fun => Ok("fun".to_string()),
267
0
        Token::Fn => Ok("fn".to_string()),
268
0
        Token::Return => Ok("return".to_string()),
269
0
        Token::If => Ok("if".to_string()),
270
0
        Token::Else => Ok("else".to_string()),
271
0
        Token::For => Ok("for".to_string()),
272
0
        Token::While => Ok("while".to_string()),
273
0
        Token::Loop => Ok("loop".to_string()),
274
0
        Token::Match => Ok("match".to_string()),
275
0
        Token::Let => Ok("let".to_string()),
276
0
        Token::Var => Ok("var".to_string()),
277
0
        Token::Const => Ok("const".to_string()),
278
0
        Token::Static => Ok("static".to_string()),
279
0
        Token::Pub => Ok("pub".to_string()),
280
0
        Token::Mut => Ok("mut".to_string()),
281
0
        Token::Struct => Ok("struct".to_string()),
282
0
        Token::Enum => Ok("enum".to_string()),
283
0
        Token::Impl => Ok("impl".to_string()),
284
0
        Token::Trait => Ok("trait".to_string()),
285
0
        Token::Use => Ok("use".to_string()),
286
0
        Token::As => Ok("as".to_string()),
287
0
        Token::In => Ok("in".to_string()),
288
0
        Token::Where => Ok("where".to_string()),
289
0
        Token::Async => Ok("async".to_string()),
290
0
        Token::Await => Ok("await".to_string()),
291
0
        Token::Try => Ok("try".to_string()),
292
0
        Token::Catch => Ok("catch".to_string()),
293
0
        Token::Throw => Ok("throw".to_string()),
294
0
        Token::Break => Ok("break".to_string()),
295
0
        Token::Continue => Ok("continue".to_string()),
296
0
        Token::State => Ok("state".to_string()),
297
0
        _ => bail!("Expected identifier or string key in object literal"),
298
    }
299
0
}
300
301
/// Parse the body of an object literal after the opening brace
302
///
303
/// Parses the contents of an object literal including key-value pairs and
304
/// spread expressions. Handles both string and identifier keys.
305
///
306
/// # Examples
307
///
308
/// ```
309
/// use ruchy::Parser;
310
///
311
/// let input = r#"{ name: "John", age: 30 }"#;
312
/// let mut parser = Parser::new(input);
313
/// let result = parser.parse();
314
/// assert!(result.is_ok());
315
/// ```
316
///
317
/// # Errors
318
///
319
/// Returns an error if:
320
/// - Invalid key type (neither identifier nor string)
321
/// - Missing colon after key
322
/// - Failed to parse value expression
323
/// - Missing comma between fields
324
/// - Missing closing brace
325
0
fn parse_object_literal_body(state: &mut ParserState, start_span: Span) -> Result<Expr> {
326
0
    let mut fields = Vec::new();
327
328
0
    while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
329
0
        parse_single_object_field(state, &mut fields)?;
330
0
        handle_object_field_separator(state)?;
331
    }
332
333
0
    state.tokens.expect(&Token::RightBrace)?;
334
0
    Ok(Expr::new(ExprKind::ObjectLiteral { fields }, start_span))
335
0
}
336
337
/// Parse a single object field (either spread or key-value) - complexity: 6
338
0
fn parse_single_object_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> {
339
0
    if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) {
340
0
        parse_object_spread_field(state, fields)
341
    } else {
342
0
        parse_object_key_value_field(state, fields)
343
    }
344
0
}
345
346
/// Parse object spread field (...expr) - complexity: 3
347
0
fn parse_object_spread_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> {
348
0
    state.tokens.advance(); // consume ...
349
0
    let expr = super::parse_expr_recursive(state)?;
350
0
    fields.push(ObjectField::Spread { expr });
351
0
    Ok(())
352
0
}
353
354
/// Parse object key-value field (key: value or key => value) - complexity: 5
355
0
fn parse_object_key_value_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> {
356
0
    let key = parse_object_key(state)?;
357
    
358
    // Accept either : or => for object key-value pairs (book compatibility)
359
0
    if matches!(state.tokens.peek(), Some((Token::FatArrow, _))) {
360
0
        state.tokens.advance(); // consume =>
361
0
    } else {
362
0
        state.tokens.expect(&Token::Colon)?;
363
    }
364
    
365
0
    let value = super::parse_expr_recursive(state)?;
366
0
    fields.push(ObjectField::KeyValue { key, value });
367
0
    Ok(())
368
0
}
369
370
/// Handle comma separator between object fields - complexity: 4
371
0
fn handle_object_field_separator(state: &mut ParserState) -> Result<()> {
372
0
    if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
373
0
        state.tokens.advance();
374
0
        Ok(())
375
0
    } else if !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
376
0
        bail!("Expected comma or closing brace in object literal")
377
    } else {
378
0
        Ok(())
379
    }
380
0
}
381
382
/// Parse a list expression or list comprehension
383
///
384
/// Parses list literals enclosed in brackets `[]`. Automatically detects
385
/// list comprehensions when the `for` keyword is encountered after the
386
/// first element.
387
///
388
/// # Examples
389
///
390
/// ```
391
/// use ruchy::Parser;
392
///
393
/// let input = "[1, 2, 3]";
394
/// let mut parser = Parser::new(input);
395
/// let result = parser.parse();
396
/// assert!(result.is_ok());
397
/// ```
398
///
399
/// # Errors
400
///
401
/// Returns an error if:
402
/// - Failed to parse any element expression
403
/// - Missing closing bracket
404
/// - Invalid list comprehension syntax
405
/// - Malformed comma-separated elements
406
/// # Errors
407
///
408
/// Returns an error if the operation fails
409
0
pub fn parse_list(state: &mut ParserState) -> Result<Expr> {
410
0
    let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume [
411
412
    // Check for empty list
413
0
    if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
414
0
        state.tokens.advance(); // consume ]
415
0
        return Ok(Expr::new(ExprKind::List(Vec::new()), start_span));
416
0
    }
417
418
    // Parse the first element (checking for spread syntax)
419
0
    let first_element = parse_list_element(state)?;
420
421
    // Check if this is a list comprehension by looking for 'for'
422
0
    if matches!(state.tokens.peek(), Some((Token::For, _))) {
423
0
        return parse_list_comprehension(state, start_span, first_element);
424
0
    }
425
426
    // Regular list - continue parsing elements
427
0
    let mut elements = vec![first_element];
428
429
0
    while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
430
0
        state.tokens.advance(); // consume comma
431
432
0
        if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
433
0
            break; // trailing comma
434
0
        }
435
436
0
        elements.push(parse_list_element(state)?);
437
    }
438
439
0
    state.tokens.expect(&Token::RightBracket)?;
440
441
0
    Ok(Expr::new(ExprKind::List(elements), start_span))
442
0
}
443
444
/// Parse a single list element, handling both regular expressions and spread syntax
445
0
fn parse_list_element(state: &mut ParserState) -> Result<Expr> {
446
    // Check for spread syntax (...)
447
0
    if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) {
448
0
        let start_pos = state.tokens.advance().expect("checked above").1.start; // consume ...
449
0
        let expr = super::parse_expr_recursive(state)?;
450
0
        let span = Span { 
451
0
            start: start_pos, 
452
0
            end: expr.span.end 
453
0
        };
454
0
        Ok(Expr::new(ExprKind::Spread { expr: Box::new(expr) }, span))
455
    } else {
456
        // Regular element
457
0
        super::parse_expr_recursive(state)
458
    }
459
0
}
460
461
/// Parse a list comprehension after the element expression
462
///
463
/// Parses the remaining parts of a list comprehension: the `for` clause,
464
/// variable binding, iterable expression, and optional `if` condition.
465
///
466
/// # Examples
467
///
468
/// ```
469
/// use ruchy::Parser;
470
///
471
/// let input = "[x * 2 for x in range(10)]";
472
/// let mut parser = Parser::new(input);
473
/// let result = parser.parse();
474
/// assert!(result.is_ok());
475
/// ```
476
///
477
/// # Errors
478
///
479
/// Returns an error if:
480
/// - Missing `for` keyword
481
/// - Invalid variable name
482
/// - Missing `in` keyword
483
/// - Failed to parse iterable expression
484
/// - Failed to parse condition expression (when present)
485
/// - Missing closing bracket
486
///
487
/// Parse a condition expression for list comprehension that stops at ]
488
0
fn parse_condition_expr(state: &mut ParserState) -> Result<Expr> {
489
    // Save the current position in case we need to backtrack
490
0
    let _start_pos = state.tokens.position();
491
492
    // Try to parse an expression, but we need to be careful about ]
493
    // We'll parse terms and operators manually to avoid consuming ]
494
0
    let mut left = parse_condition_term(state)?;
495
496
    // Check for comparison operators
497
0
    while let Some((token, _)) = state.tokens.peek() {
498
0
        match token {
499
            Token::Greater
500
            | Token::Less
501
            | Token::GreaterEqual
502
            | Token::LessEqual
503
            | Token::EqualEqual
504
            | Token::NotEqual
505
            | Token::AndAnd
506
            | Token::OrOr => {
507
0
                let op = expressions::token_to_binary_op(token).expect("checked: valid op");
508
0
                state.tokens.advance(); // consume operator
509
0
                let right = parse_condition_term(state)?;
510
0
                left = Expr::new(
511
0
                    ExprKind::Binary {
512
0
                        left: Box::new(left),
513
0
                        op,
514
0
                        right: Box::new(right),
515
0
                    },
516
0
                    Span { start: 0, end: 0 },
517
                );
518
            }
519
0
            _ => break, // Stop at closing bracket or any other token
520
        }
521
    }
522
523
0
    Ok(left)
524
0
}
525
526
/// Parse a single term in a condition expression
527
0
fn parse_condition_term(state: &mut ParserState) -> Result<Expr> {
528
    // Parse a primary expression (identifier, literal, call, etc.)
529
0
    let mut expr = expressions::parse_prefix(state)?;
530
531
    // Handle postfix operations like method calls and field access
532
0
    while let Some((token, _)) = state.tokens.peek() {
533
0
        expr = match token {
534
0
            Token::Dot => parse_dot_operation(state, expr)?,
535
0
            Token::LeftParen => functions::parse_call(state, expr)?,
536
0
            _ => break, // Stop at other tokens
537
        };
538
    }
539
540
0
    Ok(expr)
541
0
}
542
543
/// Parse dot operation (field access or method call) - complexity: 8
544
0
fn parse_dot_operation(state: &mut ParserState, expr: Expr) -> Result<Expr> {
545
0
    state.tokens.advance(); // consume .
546
    
547
0
    let Some((Token::Identifier(name), _)) = state.tokens.peek() else {
548
0
        return Ok(expr); // No identifier after dot
549
    };
550
    
551
0
    let name = name.clone();
552
0
    state.tokens.advance();
553
    
554
    // Check if it's a method call or field access
555
0
    if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
556
0
        parse_method_call(state, expr, name)
557
    } else {
558
0
        Ok(create_field_access(expr, name))
559
    }
560
0
}
561
562
/// Parse method call arguments (complexity: 5)
563
0
fn parse_method_call(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> {
564
0
    state.tokens.advance(); // consume (
565
    
566
0
    let args = parse_method_arguments(state)?;
567
    
568
0
    state.tokens.expect(&Token::RightParen)?;
569
    
570
0
    Ok(Expr::new(
571
0
        ExprKind::MethodCall {
572
0
            receiver: Box::new(receiver),
573
0
            method,
574
0
            args,
575
0
        },
576
0
        Span { start: 0, end: 0 },
577
0
    ))
578
0
}
579
580
/// Parse method call arguments (complexity: 4)
581
0
fn parse_method_arguments(state: &mut ParserState) -> Result<Vec<Expr>> {
582
0
    let mut args = Vec::new();
583
    
584
0
    while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
585
0
        args.push(super::parse_expr_recursive(state)?);
586
        
587
0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
588
0
            state.tokens.advance();
589
0
        } else {
590
0
            break;
591
        }
592
    }
593
    
594
0
    Ok(args)
595
0
}
596
597
/// Create field access expression (complexity: 1)
598
0
fn create_field_access(object: Expr, field: String) -> Expr {
599
0
    Expr::new(
600
0
        ExprKind::FieldAccess {
601
0
            object: Box::new(object),
602
0
            field,
603
0
        },
604
0
        Span { start: 0, end: 0 },
605
    )
606
0
}
607
608
0
pub fn parse_list_comprehension(
609
0
    state: &mut ParserState,
610
0
    start_span: Span,
611
0
    element: Expr,
612
0
) -> Result<Expr> {
613
    // We've already parsed the element expression
614
    // Now expect: for variable in iterable [if condition]
615
616
0
    state.tokens.expect(&Token::For)?;
617
618
    // Parse variable name
619
0
    let variable = if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
620
0
        let name = name.clone();
621
0
        state.tokens.advance();
622
0
        name
623
    } else {
624
0
        bail!("Expected variable name in list comprehension");
625
    };
626
627
0
    state.tokens.expect(&Token::In)?;
628
629
    // Parse iterable expression
630
0
    let iterable = super::parse_expr_recursive(state)?;
631
632
    // Check for optional if condition
633
0
    let condition = if matches!(state.tokens.peek(), Some((Token::If, _))) {
634
0
        state.tokens.advance(); // consume 'if'
635
                                // Parse condition expression - this needs to stop at the closing bracket
636
                                // We'll parse a simple expression that stops at ]
637
0
        let cond = parse_condition_expr(state)?;
638
0
        Some(Box::new(cond))
639
    } else {
640
0
        None
641
    };
642
643
0
    state.tokens.expect(&Token::RightBracket)?;
644
645
0
    Ok(Expr::new(
646
0
        ExprKind::ListComprehension {
647
0
            element: Box::new(element),
648
0
            variable,
649
0
            iterable: Box::new(iterable),
650
0
            condition,
651
0
        },
652
0
        start_span,
653
0
    ))
654
0
}
655
656
/// Parse a `DataFrame` literal expression
657
///
658
/// Parses `DataFrame` literals with column headers and data rows. The first
659
/// row defines column names, subsequent rows contain data values.
660
///
661
/// # Examples
662
///
663
/// ```
664
/// use ruchy::Parser;
665
///
666
/// let input = r#"df![name => ["Alice", "Bob"], age => [30, 25]]"#;
667
/// let mut parser = Parser::new(input);
668
/// let result = parser.parse();
669
/// assert!(result.is_ok());
670
/// ```
671
///
672
/// # Errors
673
///
674
/// Returns an error if:
675
/// - Missing opening brace after `DataFrame`
676
/// - Invalid column name (must be identifier)
677
/// - Missing semicolon between rows
678
/// - Failed to parse data value expressions
679
/// - Missing closing brace
680
/// - Inconsistent number of values per row
681
/// # Errors
682
///
683
/// Returns an error if the operation fails
684
/// Parse `DataFrame` header: df![ (complexity: 3)
685
0
fn parse_dataframe_header(state: &mut ParserState) -> Result<Span> {
686
0
    let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume df
687
0
    state.tokens.expect(&Token::Bang)?;
688
0
    state.tokens.expect(&Token::LeftBracket)?;
689
0
    Ok(start_span)
690
0
}
691
692
/// Parse column name identifier (complexity: 3)
693
0
fn parse_dataframe_column_name(state: &mut ParserState) -> Result<String> {
694
0
    if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
695
0
        let name = name.clone();
696
0
        state.tokens.advance();
697
0
        Ok(name)
698
    } else {
699
0
        bail!("Expected column name in DataFrame literal");
700
    }
701
0
}
702
703
/// Parse column values after => (complexity: 4)
704
0
fn parse_dataframe_column_values(state: &mut ParserState) -> Result<Vec<Expr>> {
705
0
    state.tokens.expect(&Token::FatArrow)?; // consume =>
706
    
707
0
    let values = if matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
708
        // Values are in a list
709
0
        parse_list(state)?
710
    } else {
711
        // Parse individual expression
712
0
        super::parse_expr_recursive(state)?
713
    };
714
    
715
    // Convert to vector of expressions
716
0
    let value_vec = match values.kind {
717
0
        ExprKind::List(exprs) => exprs,
718
0
        _ => vec![values],
719
    };
720
    
721
0
    Ok(value_vec)
722
0
}
723
724
/// Handle legacy syntax column (complexity: 3)
725
0
fn handle_dataframe_legacy_syntax_column(col_name: String) -> DataFrameColumn {
726
    // Legacy syntax: just column names, then semicolon and rows
727
    // For backward compatibility, create empty column for now
728
0
    DataFrameColumn {
729
0
        name: col_name,
730
0
        values: Vec::new(),
731
0
    }
732
0
}
733
734
/// Parse column definitions loop (complexity: 6)
735
0
fn parse_dataframe_column_definitions(state: &mut ParserState) -> Result<Vec<DataFrameColumn>> {
736
0
    let mut columns = Vec::new();
737
    
738
    loop {
739
0
        let col_name = parse_dataframe_column_name(state)?;
740
0
        parse_single_dataframe_column(state, col_name, &mut columns)?;
741
        
742
0
        if !handle_dataframe_column_continuation(state, &mut columns)? {
743
0
            break;
744
0
        }
745
    }
746
    
747
0
    Ok(columns)
748
0
}
749
750
/// Parse a single `DataFrame` column (either new or legacy syntax) - complexity: 5
751
0
fn parse_single_dataframe_column(
752
0
    state: &mut ParserState,
753
0
    col_name: String,
754
0
    columns: &mut Vec<DataFrameColumn>
755
0
) -> Result<()> {
756
0
    if matches!(state.tokens.peek(), Some((Token::FatArrow, _))) {
757
        // New syntax: col => [values]
758
0
        let values = parse_dataframe_column_values(state)?;
759
0
        columns.push(DataFrameColumn {
760
0
            name: col_name,
761
0
            values,
762
0
        });
763
0
    } else if is_dataframe_legacy_syntax_token(state) {
764
0
        columns.push(handle_dataframe_legacy_syntax_column(col_name));
765
0
    } else {
766
0
        bail!("Expected '=>' or ',' after column name in DataFrame literal");
767
    }
768
0
    Ok(())
769
0
}
770
771
/// Check if current token indicates legacy `DataFrame` syntax - complexity: 4
772
0
fn is_dataframe_legacy_syntax_token(state: &mut ParserState) -> bool {
773
0
    matches!(state.tokens.peek(), Some((Token::Comma, _)))
774
0
        || matches!(state.tokens.peek(), Some((Token::Semicolon, _)))
775
0
        || matches!(state.tokens.peek(), Some((Token::RightBracket, _)))
776
0
}
777
778
/// Handle `DataFrame` column continuation tokens - complexity: 5
779
0
fn handle_dataframe_column_continuation(
780
0
    state: &mut ParserState,
781
0
    columns: &mut Vec<DataFrameColumn>
782
0
) -> Result<bool> {
783
0
    if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
784
0
        state.tokens.advance();
785
0
        Ok(true)
786
0
    } else if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) {
787
        // Legacy row-based syntax
788
0
        state.tokens.advance();
789
0
        parse_legacy_dataframe_rows(state, columns)?;
790
0
        Ok(false)
791
    } else {
792
0
        Ok(false)
793
    }
794
0
}
795
796
/// Create final `DataFrame` expression (complexity: 3)
797
0
fn create_dataframe_result(columns: Vec<DataFrameColumn>, start_span: Span) -> Result<Expr> {
798
0
    Ok(Expr::new(ExprKind::DataFrame { columns }, start_span))
799
0
}
800
801
/// Parse `DataFrame` literal: df![...] (complexity: 6)
802
0
pub fn parse_dataframe(state: &mut ParserState) -> Result<Expr> {
803
0
    let start_span = parse_dataframe_header(state)?;
804
    
805
    // Check for empty DataFrame df![]
806
0
    if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
807
0
        state.tokens.advance();
808
0
        return create_dataframe_result(Vec::new(), start_span);
809
0
    }
810
    
811
    // Parse column definitions
812
0
    let columns = parse_dataframe_column_definitions(state)?;
813
    
814
0
    state.tokens.expect(&Token::RightBracket)?;
815
0
    create_dataframe_result(columns, start_span)
816
0
}
817
818
/// Parse legacy row-based `DataFrame` syntax for backward compatibility
819
#[allow(clippy::ptr_arg)] // We need to mutate the Vec, not just read it
820
0
fn parse_legacy_dataframe_rows(
821
0
    state: &mut ParserState,
822
0
    columns: &mut Vec<DataFrameColumn>,
823
0
) -> Result<()> {
824
0
    let rows = parse_all_dataframe_rows(state)?;
825
0
    populate_dataframe_columns(columns, &rows);
826
0
    Ok(())
827
0
}
828
829
/// Parse all dataframe rows (complexity: 2)
830
0
fn parse_all_dataframe_rows(state: &mut ParserState) -> Result<Vec<Vec<Expr>>> {
831
0
    let mut rows = Vec::new();
832
    
833
    loop {
834
0
        if is_end_bracket(state) {
835
0
            break;
836
0
        }
837
        
838
0
        let row = parse_single_dataframe_row(state)?;
839
0
        add_non_empty_row(&mut rows, row);
840
        
841
0
        if !consume_row_separator(state) {
842
0
            break;
843
0
        }
844
    }
845
    
846
0
    Ok(rows)
847
0
}
848
849
/// Check if current token is end bracket (complexity: 1)
850
0
fn is_end_bracket(state: &mut ParserState) -> bool {
851
0
    matches!(state.tokens.peek(), Some((Token::RightBracket, _)))
852
0
}
853
854
/// Parse a single dataframe row (complexity: 2)
855
0
fn parse_single_dataframe_row(state: &mut ParserState) -> Result<Vec<Expr>> {
856
0
    let mut row = Vec::new();
857
    
858
    loop {
859
0
        if is_row_boundary(state) {
860
0
            break;
861
0
        }
862
        
863
0
        row.push(super::parse_expr_recursive(state)?);
864
        
865
0
        if !consume_value_separator(state) {
866
0
            break;
867
0
        }
868
    }
869
    
870
0
    Ok(row)
871
0
}
872
873
/// Check if current token is a row boundary (complexity: 2)
874
0
fn is_row_boundary(state: &mut ParserState) -> bool {
875
0
    matches!(state.tokens.peek(), Some((Token::Semicolon, _)))
876
0
        || matches!(state.tokens.peek(), Some((Token::RightBracket, _)))
877
0
}
878
879
/// Consume comma separator if present (complexity: 2)
880
0
fn consume_value_separator(state: &mut ParserState) -> bool {
881
0
    if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
882
0
        state.tokens.advance();
883
0
        true
884
    } else {
885
0
        false
886
    }
887
0
}
888
889
/// Consume semicolon row separator if present (complexity: 2)
890
0
fn consume_row_separator(state: &mut ParserState) -> bool {
891
0
    if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) {
892
0
        state.tokens.advance();
893
0
        true
894
    } else {
895
0
        false
896
    }
897
0
}
898
899
/// Add non-empty row to collection (complexity: 2)
900
0
fn add_non_empty_row(rows: &mut Vec<Vec<Expr>>, row: Vec<Expr>) {
901
0
    if !row.is_empty() {
902
0
        rows.push(row);
903
0
    }
904
0
}
905
906
/// Populate columns from row data (complexity: 3)
907
0
fn populate_dataframe_columns(columns: &mut [DataFrameColumn], rows: &[Vec<Expr>]) {
908
0
    for (col_idx, column) in columns.iter_mut().enumerate() {
909
0
        for row in rows {
910
0
            if col_idx < row.len() {
911
0
                column.values.push(row[col_idx].clone());
912
0
            }
913
        }
914
    }
915
0
}