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