## 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/mod.rs

    Line
    Count
    Source
    1
    //! Modular parser implementation for Ruchy
    2
    #![allow(clippy::wildcard_imports)]
    3
    #![allow(clippy::expect_used)]
    4
    //!
    5
    //! The parser is split into logical modules to improve maintainability:
    6
    //! - `core` - Main parser entry points and precedence handling
    7
    //! - `expressions` - Basic expressions (literals, binary/unary ops)
    8
    //! - `control_flow` - Control flow constructs (if, match, loops)
    9
    //! - `functions` - Function definitions, lambdas, and calls
    10
    //! - `types` - Type-related parsing (struct, trait, impl)
    11
    //! - `collections` - Collections (lists, dataframes, comprehensions)
    12
    //! - `actors` - Actor system constructs
    13
    //! - `utils` - Parsing utilities and error recovery
    14
    15
    mod actors;
    16
    mod collections;
    17
    mod core;
    18
    mod expressions;
    19
    mod functions;
    20
    mod operator_precedence;
    21
    mod types;
    22
    mod utils;
    23
    24
    // Re-export the main parser
    25
    pub use core::Parser;
    26
    27
    use crate::frontend::arena::{Arena, StringInterner};
    28
    use crate::frontend::ast::{
    29
        ActorHandler, Attribute, BinaryOp, DataFrameColumn, EnumVariant, Expr, ExprKind, ImportItem, Literal, MatchArm, Param,
    30
        Pattern, PipelineStage, Span, StringPart, StructField, TraitMethod, Type, TypeKind, UnaryOp,
    31
    };
    32
    use crate::frontend::lexer::{Token, TokenStream};
    33
    use crate::parser::error_recovery::{ErrorNode, ErrorRecovery};
    34
    use anyhow::{bail, Result};
    35
    36
    /// Shared parser state and utilities
    37
    pub(crate) struct ParserState<'a> {
    38
        pub tokens: TokenStream<'a>,
    39
        #[allow(dead_code)]
    40
        pub error_recovery: ErrorRecovery,
    41
        pub errors: Vec<ErrorNode>,
    42
        /// Arena allocator for AST nodes
    43
        #[allow(dead_code)]
    44
        pub arena: Arena,
    45
        /// String interner for deduplicating identifiers
    46
        #[allow(dead_code)]
    47
        pub interner: StringInterner,
    48
    }
    49
    50
    impl<'a> ParserState<'a> {
    51
        #[must_use]
    52
    0
        pub fn new(input: &'a str) -> Self {
    53
    0
            Self {
    54
    0
                tokens: TokenStream::new(input),
    55
    0
                error_recovery: ErrorRecovery::new(),
    56
    0
                errors: Vec::new(),
    57
    0
                arena: Arena::new(),
    58
    0
                interner: StringInterner::new(),
    59
    0
            }
    60
    0
        }
    61
    62
        /// Get all errors encountered during parsing
    63
    0
        pub fn get_errors(&self) -> &[ErrorNode] {
    64
    0
            &self.errors
    65
    0
        }
    66
    67
        /// Get arena statistics for performance monitoring
    68
        #[allow(dead_code)]
    69
    0
        pub fn arena_stats(&self) -> (usize, usize) {
    70
    0
            (self.arena.total_allocated(), self.arena.num_items())
    71
    0
        }
    72
    73
        /// Get interner statistics
    74
        #[allow(dead_code)]
    75
    0
        pub fn interner_stats(&self) -> (usize, usize) {
    76
    0
            self.interner.stats()
    77
    0
        }
    78
    }
    79
    80
    /// Forward declarations for recursive parsing
    81
    0
    pub(crate) fn parse_expr_recursive(state: &mut ParserState) -> Result<Expr> {
    82
    0
        parse_expr_with_precedence_recursive(state, 0)
    83
    0
    }
    84
    85
    0
    pub(crate) fn parse_expr_with_precedence_recursive(
    86
    0
        state: &mut ParserState,
    87
    0
        min_prec: i32,
    88
    0
    ) -> Result<Expr> {
    89
    0
        let mut left = expressions::parse_prefix(state)?;
    90
    91
        loop {
    92
            // Handle postfix operators
    93
    0
            left = handle_postfix_operators(state, left)?;
    94
    95
            // Try to handle infix operators
    96
    0
            if let Some(new_left) = try_handle_infix_operators(state, left.clone(), min_prec)? {
    97
    0
                left = new_left;
    98
    0
            } else {
    99
    0
                break;
    100
            }
    101
        }
    102
    103
    0
        Ok(left)
    104
    0
    }
    105
    106
    /// Try to handle any infix operator (complexity: 7)
    107
    0
    fn try_handle_infix_operators(
    108
    0
        state: &mut ParserState,
    109
    0
        left: Expr,
    110
    0
        min_prec: i32,
    111
    0
    ) -> Result<Option<Expr>> {
    112
        // Get current token for infix processing
    113
    0
        let Some((token, _)) = state.tokens.peek() else {
    114
    0
            return Ok(None);
    115
        };
    116
    0
        let token_clone = token.clone();
    117
    118
        // Try operators in order of priority
    119
    0
        let handlers = [
    120
    0
            try_new_actor_operators,
    121
    0
            try_type_cast_operator,
    122
    0
            try_binary_operators,
    123
    0
            try_assignment_operators,
    124
    0
            try_pipeline_operators,
    125
    0
            try_range_operators,
    126
    0
        ];
    127
    128
    0
        for handler in &handlers {
    129
    0
            if let Some(new_left) = handler(state, left.clone(), &token_clone, min_prec)? {
    130
    0
                return Ok(Some(new_left));
    131
    0
            }
    132
        }
    133
    134
    0
        Ok(None)
    135
    0
    }
    136
    137
    /// Handle all postfix operators in a loop
    138
    0
    fn handle_postfix_operators(state: &mut ParserState, mut left: Expr) -> Result<Expr> {
    139
    0
        let mut handled_postfix = true;
    140
    0
        while handled_postfix {
    141
    0
            handled_postfix = false;
    142
    0
            match state.tokens.peek() {
    143
                Some((Token::Dot, _)) => {
    144
    0
                    state.tokens.advance();
    145
    0
                    left = functions::parse_method_call(state, left)?;
    146
    0
                    handled_postfix = true;
    147
                }
    148
                Some((Token::SafeNav, _)) => {
    149
    0
                    state.tokens.advance();
    150
    0
                    left = functions::parse_optional_method_call(state, left)?;
    151
    0
                    handled_postfix = true;
    152
                }
    153
                Some((Token::LeftParen, _)) => {
    154
    0
                    left = functions::parse_call(state, left)?;
    155
    0
                    handled_postfix = true;
    156
                }
    157
                Some((Token::LeftBracket, _)) => {
    158
    0
                    left = handle_array_indexing(state, left)?;
    159
    0
                    handled_postfix = true;
    160
                }
    161
                Some((Token::LeftBrace, _)) => {
    162
    0
                    if let Some(new_left) = try_parse_struct_literal(state, &left)? {
    163
    0
                        left = new_left;
    164
    0
                        handled_postfix = true;
    165
    0
                    }
    166
                }
    167
    0
                Some((Token::Increment, _)) => {
    168
    0
                    state.tokens.advance();
    169
    0
                    left = create_post_increment(left);
    170
    0
                    handled_postfix = true;
    171
    0
                }
    172
    0
                Some((Token::Decrement, _)) => {
    173
    0
                    state.tokens.advance();
    174
    0
                    left = create_post_decrement(left);
    175
    0
                    handled_postfix = true;
    176
    0
                }
    177
    0
                Some((Token::Question, _)) => {
    178
    0
                    state.tokens.advance();
    179
    0
                    left = Expr::new(
    180
    0
                        ExprKind::Try {
    181
    0
                            expr: Box::new(left),
    182
    0
                        },
    183
    0
                        Span { start: 0, end: 0 },
    184
    0
                    );
    185
    0
                    handled_postfix = true;
    186
    0
                }
    187
    0
                _ => {}
    188
            }
    189
        }
    190
    0
        Ok(left)
    191
    0
    }
    192
    193
    /// Handle array indexing and slicing syntax `[expr]` or `[start:end]`
    194
    0
    fn handle_array_indexing(state: &mut ParserState, left: Expr) -> Result<Expr> {
    195
    0
        state.tokens.advance(); // consume [
    196
    197
        // Check for empty slice [:end]
    198
    0
        if is_colon_next(state) {
    199
    0
            return parse_empty_start_slice(state, left);
    200
    0
        }
    201
    202
    0
        let first_expr = parse_expr_recursive(state)?;
    203
    204
        // Check if this is a slice [start:end] or just indexing [index]
    205
    0
        if is_colon_next(state) {
    206
    0
            parse_slice_with_start(state, left, first_expr)
    207
        } else {
    208
    0
            parse_index_access(state, left, first_expr)
    209
        }
    210
    0
    }
    211
    212
    /// Check if next token is colon (complexity: 1)
    213
    0
    fn is_colon_next(state: &mut ParserState) -> bool {
    214
    0
        matches!(state.tokens.peek(), Some((Token::Colon, _)))
    215
    0
    }
    216
    217
    /// Parse slice with empty start `[:end]` (complexity: 4)
    218
    0
    fn parse_empty_start_slice(state: &mut ParserState, left: Expr) -> Result<Expr> {
    219
    0
        state.tokens.advance(); // consume :
    220
    0
        let end = parse_optional_slice_end(state)?;
    221
    0
        state.tokens.expect(&Token::RightBracket)?;
    222
    223
    0
        Ok(create_slice_expr(left, None, end))
    224
    0
    }
    225
    226
    /// Parse slice with start `[start:end]` (complexity: 3)
    227
    0
    fn parse_slice_with_start(state: &mut ParserState, left: Expr, start: Expr) -> Result<Expr> {
    228
    0
        state.tokens.advance(); // consume :
    229
    0
        let end = parse_optional_slice_end(state)?;
    230
    0
        state.tokens.expect(&Token::RightBracket)?;
    231
    232
    0
        Ok(create_slice_expr(left, Some(Box::new(start)), end))
    233
    0
    }
    234
    235
    /// Parse optional slice end expression (complexity: 3)
    236
    0
    fn parse_optional_slice_end(state: &mut ParserState) -> Result<Option<Box<Expr>>> {
    237
    0
        if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
    238
    0
            Ok(None)
    239
        } else {
    240
    0
            Ok(Some(Box::new(parse_expr_recursive(state)?)))
    241
        }
    242
    0
    }
    243
    244
    /// Parse index access `[index]` (complexity: 2)
    245
    0
    fn parse_index_access(state: &mut ParserState, left: Expr, index: Expr) -> Result<Expr> {
    246
    0
        state.tokens.expect(&Token::RightBracket)?;
    247
    248
    0
        Ok(Expr {
    249
    0
            kind: ExprKind::IndexAccess {
    250
    0
                object: Box::new(left),
    251
    0
                index: Box::new(index),
    252
    0
            },
    253
    0
            span: Span { start: 0, end: 0 },
    254
    0
            attributes: Vec::new(),
    255
    0
        })
    256
    0
    }
    257
    258
    /// Create slice expression (complexity: 1)
    259
    0
    fn create_slice_expr(object: Expr, start: Option<Box<Expr>>, end: Option<Box<Expr>>) -> Expr {
    260
    0
        Expr {
    261
    0
            kind: ExprKind::Slice {
    262
    0
                object: Box::new(object),
    263
    0
                start,
    264
    0
                end,
    265
    0
            },
    266
    0
            span: Span { start: 0, end: 0 },
    267
    0
            attributes: Vec::new(),
    268
    0
        }
    269
    0
    }
    270
    271
    272
    /// Try to parse struct literal
    273
    0
    fn try_parse_struct_literal(state: &mut ParserState, left: &Expr) -> Result<Option<Expr>> {
    274
    0
        if let ExprKind::Identifier(name) = &left.kind {
    275
    0
            if name.chars().next().is_some_and(char::is_uppercase) {
    276
    0
                let name = name.clone();
    277
    0
                let span = left.span;
    278
    0
                return Ok(Some(types::parse_struct_literal(state, name, span)?));
    279
    0
            }
    280
    0
        }
    281
    0
        Ok(None)
    282
    0
    }
    283
    284
    /// Create post-increment expression
    285
    0
    fn create_post_increment(left: Expr) -> Expr {
    286
    0
        Expr {
    287
    0
            kind: ExprKind::PostIncrement {
    288
    0
                target: Box::new(left),
    289
    0
            },
    290
    0
            span: Span { start: 0, end: 0 },
    291
    0
            attributes: Vec::new(),
    292
    0
        }
    293
    0
    }
    294
    295
    /// Create post-decrement expression
    296
    0
    fn create_post_decrement(left: Expr) -> Expr {
    297
    0
        Expr {
    298
    0
            kind: ExprKind::PostDecrement {
    299
    0
                target: Box::new(left),
    300
    0
            },
    301
    0
            span: Span { start: 0, end: 0 },
    302
    0
            attributes: Vec::new(),
    303
    0
        }
    304
    0
    }
    305
    306
    307
    /// Try to parse binary operators
    308
    0
    fn try_binary_operators(
    309
    0
        state: &mut ParserState,
    310
    0
        left: Expr,
    311
    0
        token: &Token,
    312
    0
        min_prec: i32,
    313
    0
    ) -> Result<Option<Expr>> {
    314
    0
        if let Some(bin_op) = expressions::token_to_binary_op(token) {
    315
    0
            let prec = expressions::get_precedence(bin_op);
    316
    0
            if prec < min_prec {
    317
    0
                return Ok(None);
    318
    0
            }
    319
    320
    0
            state.tokens.advance();
    321
    0
            let right = parse_expr_with_precedence_recursive(state, prec + 1)?;
    322
    0
            Ok(Some(Expr {
    323
    0
                kind: ExprKind::Binary {
    324
    0
                    left: Box::new(left),
    325
    0
                    op: bin_op,
    326
    0
                    right: Box::new(right),
    327
    0
                },
    328
    0
                span: Span { start: 0, end: 0 },
    329
    0
                attributes: Vec::new(),
    330
    0
            }))
    331
        } else {
    332
    0
            Ok(None)
    333
        }
    334
    0
    }
    335
    336
    /// Try to parse type cast operator (as) - complexity: 5
    337
    0
    fn try_type_cast_operator(
    338
    0
        state: &mut ParserState,
    339
    0
        left: Expr,
    340
    0
        token: &Token,
    341
    0
        _min_prec: i32,
    342
    0
    ) -> Result<Option<Expr>> {
    343
    0
        if !matches!(token, Token::As) {
    344
    0
            return Ok(None);
    345
    0
        }
    346
    347
    0
        state.tokens.advance(); // consume 'as'
    348
    349
        // Get the target type
    350
    0
        let target_type = match state.tokens.peek() {
    351
    0
            Some((Token::Identifier(t), _)) => {
    352
    0
                let type_name = t.clone();
    353
    0
                state.tokens.advance();
    354
    0
                type_name
    355
            }
    356
    0
            _ => bail!("Expected type name after 'as'"),
    357
        };
    358
    359
    0
        Ok(Some(Expr {
    360
    0
            kind: ExprKind::TypeCast {
    361
    0
                expr: Box::new(left),
    362
    0
                target_type,
    363
    0
            },
    364
    0
            span: Span { start: 0, end: 0 },
    365
    0
            attributes: Vec::new(),
    366
    0
        }))
    367
    0
    }
    368
    369
    /// Try to parse new actor operations (<- and <?)
    370
    0
    fn try_new_actor_operators(
    371
    0
        state: &mut ParserState,
    372
    0
        left: Expr,
    373
    0
        token: &Token,
    374
    0
        min_prec: i32,
    375
    0
    ) -> Result<Option<Expr>> {
    376
    0
        let (expr_kind, _prec) = match token {
    377
            Token::LeftArrow => {
    378
                // Parse actor <- message
    379
    0
                let prec = 1; // Same as assignment
    380
    0
                if prec < min_prec {
    381
    0
                    return Ok(None);
    382
    0
                }
    383
    0
                state.tokens.advance();
    384
    0
                let message = parse_expr_with_precedence_recursive(state, prec)?;
    385
    0
                (ExprKind::ActorSend {
    386
    0
                    actor: Box::new(left),
    387
    0
                    message: Box::new(message),
    388
    0
                }, prec)
    389
            }
    390
            Token::ActorQuery => {
    391
                // Parse actor <? message
    392
    0
                let prec = 1; // Same as assignment
    393
    0
                if prec < min_prec {
    394
    0
                    return Ok(None);
    395
    0
                }
    396
    0
                state.tokens.advance();
    397
    0
                let message = parse_expr_with_precedence_recursive(state, prec)?;
    398
    0
                (ExprKind::ActorQuery {
    399
    0
                    actor: Box::new(left),
    400
    0
                    message: Box::new(message),
    401
    0
                }, prec)
    402
            }
    403
    0
            _ => return Ok(None),
    404
        };
    405
    406
    0
        Ok(Some(Expr {
    407
    0
            kind: expr_kind,
    408
    0
            span: Span { start: 0, end: 0 },
    409
    0
            attributes: Vec::new(),
    410
    0
        }))
    411
    0
    }
    412
    413
    /// Try to parse assignment operators
    414
    0
    fn try_assignment_operators(
    415
    0
        state: &mut ParserState,
    416
    0
        left: Expr,
    417
    0
        token: &Token,
    418
    0
        min_prec: i32,
    419
    0
    ) -> Result<Option<Expr>> {
    420
    0
        if !token.is_assignment_op() {
    421
    0
            return Ok(None);
    422
    0
        }
    423
    424
    0
        let prec = 1;
    425
    0
        if prec < min_prec {
    426
    0
            return Ok(None);
    427
    0
        }
    428
    429
    0
        state.tokens.advance();
    430
    0
        let value = parse_expr_with_precedence_recursive(state, prec)?;
    431
    432
    0
        let expr = if *token == Token::Equal {
    433
    0
            Expr {
    434
    0
                kind: ExprKind::Assign {
    435
    0
                    target: Box::new(left),
    436
    0
                    value: Box::new(value),
    437
    0
                },
    438
    0
                span: Span { start: 0, end: 0 },
    439
    0
                attributes: Vec::new(),
    440
    0
            }
    441
        } else {
    442
    0
            let bin_op = get_compound_assignment_op(token);
    443
    0
            Expr {
    444
    0
                kind: ExprKind::CompoundAssign {
    445
    0
                    target: Box::new(left),
    446
    0
                    op: bin_op,
    447
    0
                    value: Box::new(value),
    448
    0
                },
    449
    0
                span: Span { start: 0, end: 0 },
    450
    0
                attributes: Vec::new(),
    451
    0
            }
    452
        };
    453
    0
        Ok(Some(expr))
    454
    0
    }
    455
    456
    /// Get binary operator for compound assignment
    457
    0
    fn get_compound_assignment_op(token: &Token) -> BinaryOp {
    458
    0
        match token {
    459
    0
            Token::PlusEqual => BinaryOp::Add,
    460
    0
            Token::MinusEqual => BinaryOp::Subtract,
    461
    0
            Token::StarEqual => BinaryOp::Multiply,
    462
    0
            Token::SlashEqual => BinaryOp::Divide,
    463
    0
            Token::PercentEqual => BinaryOp::Modulo,
    464
    0
            Token::PowerEqual => BinaryOp::Power,
    465
    0
            Token::AmpersandEqual => BinaryOp::BitwiseAnd,
    466
    0
            Token::PipeEqual => BinaryOp::BitwiseOr,
    467
    0
            Token::CaretEqual => BinaryOp::BitwiseXor,
    468
    0
            Token::LeftShiftEqual => BinaryOp::LeftShift,
    469
    0
            _ => unreachable!("Already checked is_assignment_op"),
    470
        }
    471
    0
    }
    472
    473
    /// Try to parse pipeline operators (>>)
    474
    0
    fn try_pipeline_operators(
    475
    0
        state: &mut ParserState,
    476
    0
        left: Expr,
    477
    0
        token: &Token,
    478
    0
        min_prec: i32,
    479
    0
    ) -> Result<Option<Expr>> {
    480
    0
        if !matches!(token, Token::Pipeline) {
    481
    0
            return Ok(None);
    482
    0
        }
    483
    484
    0
        let prec = 3;
    485
    0
        if prec < min_prec {
    486
    0
            return Ok(None);
    487
    0
        }
    488
    489
    0
        state.tokens.advance();
    490
    0
        let stage_expr = parse_expr_with_precedence_recursive(state, prec + 1)?;
    491
    492
    0
        let expr = if let ExprKind::Pipeline { expr, mut stages } = left.kind {
    493
    0
            stages.push(PipelineStage {
    494
    0
                op: Box::new(stage_expr),
    495
    0
                span: Span { start: 0, end: 0 },
    496
    0
            });
    497
    0
            Expr {
    498
    0
                kind: ExprKind::Pipeline { expr, stages },
    499
    0
                span: Span { start: 0, end: 0 },
    500
    0
                attributes: Vec::new(),
    501
    0
            }
    502
        } else {
    503
    0
            Expr {
    504
    0
                kind: ExprKind::Pipeline {
    505
    0
                    expr: Box::new(left),
    506
    0
                    stages: vec![PipelineStage {
    507
    0
                        op: Box::new(stage_expr),
    508
    0
                        span: Span { start: 0, end: 0 },
    509
    0
                    }],
    510
    0
                },
    511
    0
                span: Span { start: 0, end: 0 },
    512
    0
                attributes: Vec::new(),
    513
    0
            }
    514
        };
    515
    0
        Ok(Some(expr))
    516
    0
    }
    517
    518
    /// Try to parse range operators (.., ..=)
    519
    0
    fn try_range_operators(
    520
    0
        state: &mut ParserState,
    521
    0
        left: Expr,
    522
    0
        token: &Token,
    523
    0
        min_prec: i32,
    524
    0
    ) -> Result<Option<Expr>> {
    525
    0
        if !matches!(token, Token::DotDot | Token::DotDotEqual) {
    526
    0
            return Ok(None);
    527
    0
        }
    528
    529
    0
        let prec = 5;
    530
    0
        if prec < min_prec {
    531
    0
            return Ok(None);
    532
    0
        }
    533
    534
    0
        let inclusive = matches!(token, Token::DotDotEqual);
    535
    0
        state.tokens.advance();
    536
    0
        let end = parse_expr_with_precedence_recursive(state, prec + 1)?;
    537
    0
        Ok(Some(Expr {
    538
    0
            kind: ExprKind::Range {
    539
    0
                start: Box::new(left),
    540
    0
                end: Box::new(end),
    541
    0
                inclusive,
    542
    0
            },
    543
    0
            span: Span { start: 0, end: 0 },
    544
    0
            attributes: Vec::new(),
    545
    0
        }))
    546
    0
    }