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

    Line
    Count
    Source
    1
    //! Parsing utilities and helper functions
    2
    3
    use super::{ParserState, *};
    4
    use crate::frontend::ast::ImportItem;
    5
    6
    /// Validate URL imports for safe operation
    7
    0
    fn validate_url_import(url: &str) -> Result<()> {
    8
        // Safety checks for URL imports
    9
    10
        // 1. Must be HTTPS in production (allow HTTP for local dev)
    11
    0
        if !url.starts_with("https://") && !url.starts_with("http://localhost")
    12
    0
            && !url.starts_with("http://127.0.0.1") {
    13
    0
            bail!("URL imports must use HTTPS (except for localhost)");
    14
    0
        }
    15
    16
        // 2. Must end with .ruchy or .rchy extension
    17
    0
        if !url.ends_with(".ruchy") && !url.ends_with(".rchy") {
    18
    0
            bail!("URL imports must reference .ruchy or .rchy files");
    19
    0
        }
    20
    21
        // 3. Basic URL validation - no path traversal
    22
    0
        if url.contains("..") || url.contains("/.") {
    23
    0
            bail!("URL imports cannot contain path traversal sequences");
    24
    0
        }
    25
    26
        // 4. Disallow certain suspicious patterns
    27
    0
        if url.contains("javascript:") || url.contains("data:") || url.contains("file:") {
    28
    0
            bail!("Invalid URL scheme for import");
    29
    0
        }
    30
    31
    0
        Ok(())
    32
    0
    }
    33
    34
    /// Parse a pattern for destructuring
    35
    ///
    36
    /// Supports:
    37
    /// - Simple identifiers: `name`
    38
    /// - Tuple patterns: `(a, b, c)`
    39
    /// - List patterns: `[head, tail]`
    40
    /// - Struct patterns: `User { name, age }`
    41
    /// - Wildcard patterns: `_`
    42
    ///
    43
    /// # Errors
    44
    ///
    45
    /// Returns an error if the operation fails
    46
    0
    pub fn parse_params(state: &mut ParserState) -> Result<Vec<Param>> {
    47
    0
        state.tokens.expect(&Token::LeftParen)?;
    48
    49
    0
        let mut params = Vec::new();
    50
    0
        while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    51
    0
            let param = parse_single_param(state)?;
    52
    0
            params.push(param);
    53
    54
    0
            if !should_continue_param_list(state)? {
    55
    0
                break;
    56
    0
            }
    57
        }
    58
    59
    0
        state.tokens.expect(&Token::RightParen)?;
    60
    0
        Ok(params)
    61
    0
    }
    62
    63
    /// Parse a single parameter (complexity: 7)
    64
    0
    fn parse_single_param(state: &mut ParserState) -> Result<Param> {
    65
    0
        let is_mutable = check_and_consume_mut(state);
    66
    0
        let pattern = parse_param_pattern(state)?;
    67
    0
        let ty = parse_optional_type_annotation(state)?;
    68
    0
        let default_value = parse_optional_default_value(state)?;
    69
    70
    0
        Ok(Param {
    71
    0
            pattern,
    72
    0
            ty,
    73
    0
            span: Span { start: 0, end: 0 },
    74
    0
            is_mutable,
    75
    0
            default_value,
    76
    0
        })
    77
    0
    }
    78
    79
    /// Check for and consume mut keyword (complexity: 2)
    80
    0
    fn check_and_consume_mut(state: &mut ParserState) -> bool {
    81
    0
        if matches!(state.tokens.peek(), Some((Token::Mut, _))) {
    82
    0
            state.tokens.advance();
    83
    0
            true
    84
        } else {
    85
    0
            false
    86
        }
    87
    0
    }
    88
    89
    /// Parse parameter pattern (complexity: 8 - increased to support destructuring)
    90
    0
    fn parse_param_pattern(state: &mut ParserState) -> Result<Pattern> {
    91
    0
        match state.tokens.peek() {
    92
    0
            Some((Token::Ampersand, _)) => parse_reference_pattern(state),
    93
    0
            Some((Token::Identifier(name), _)) => {
    94
    0
                let name = name.clone();
    95
    0
                state.tokens.advance();
    96
    0
                Ok(Pattern::Identifier(name))
    97
            }
    98
            Some((Token::DataFrame, _)) => {
    99
                // Handle "df" parameter name (tokenized as DataFrame)
    100
    0
                state.tokens.advance();
    101
    0
                Ok(Pattern::Identifier("df".to_string()))
    102
            }
    103
            Some((Token::LeftParen, _)) => {
    104
                // Parse tuple destructuring: fun f((x, y)) {}
    105
    0
                super::expressions::parse_tuple_pattern(state)
    106
            }
    107
            Some((Token::LeftBracket, _)) => {
    108
                // Parse list destructuring: fun f([x, y]) {}
    109
    0
                super::expressions::parse_list_pattern(state)
    110
            }
    111
            Some((Token::LeftBrace, _)) => {
    112
                // Parse struct destructuring: fun f({x, y}) {}
    113
    0
                super::expressions::parse_struct_pattern(state)
    114
            }
    115
    0
            _ => bail!("Function parameters must be simple identifiers or destructuring patterns"),
    116
        }
    117
    0
    }
    118
    119
    /// Parse reference patterns (&self, &mut self) (complexity: 8)
    120
    0
    fn parse_reference_pattern(state: &mut ParserState) -> Result<Pattern> {
    121
    0
        state.tokens.advance(); // consume &
    122
    123
    0
        let is_mut_ref = matches!(state.tokens.peek(), Some((Token::Mut, _)));
    124
    0
        if is_mut_ref {
    125
    0
            state.tokens.advance(); // consume mut
    126
    0
        }
    127
    128
    0
        match state.tokens.peek() {
    129
    0
            Some((Token::Identifier(n), _)) if n == "self" => {
    130
    0
                state.tokens.advance();
    131
    0
                if is_mut_ref {
    132
    0
                    Ok(Pattern::Identifier("&mut self".to_string()))
    133
                } else {
    134
    0
                    Ok(Pattern::Identifier("&self".to_string()))
    135
                }
    136
            }
    137
            _ => {
    138
    0
                let expected = if is_mut_ref { "'self' after '&mut'" } else { "'self' after '&'" };
    139
    0
                bail!("Expected {}", expected)
    140
            }
    141
        }
    142
    0
    }
    143
    144
    /// Parse optional type annotation (complexity: 4)
    145
    0
    fn parse_optional_type_annotation(state: &mut ParserState) -> Result<Type> {
    146
    0
        if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
    147
    0
            state.tokens.advance(); // consume :
    148
    0
            parse_type(state)
    149
        } else {
    150
            // Default to 'Any' type for untyped parameters
    151
    0
            Ok(Type {
    152
    0
                kind: TypeKind::Named("Any".to_string()),
    153
    0
                span: Span { start: 0, end: 0 },
    154
    0
            })
    155
        }
    156
    0
    }
    157
    158
    /// Parse optional default value (complexity: 3)
    159
    0
    fn parse_optional_default_value(state: &mut ParserState) -> Result<Option<Box<Expr>>> {
    160
    0
        if matches!(state.tokens.peek(), Some((Token::Equal, _))) {
    161
    0
            state.tokens.advance(); // consume =
    162
    0
            Ok(Some(Box::new(super::parse_expr_recursive(state)?)))
    163
        } else {
    164
    0
            Ok(None)
    165
        }
    166
    0
    }
    167
    168
    /// Check if we should continue parsing parameters (complexity: 3)
    169
    0
    fn should_continue_param_list(state: &mut ParserState) -> Result<bool> {
    170
    0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    171
    0
            state.tokens.advance(); // consume comma
    172
    0
            Ok(true)
    173
        } else {
    174
    0
            Ok(false)
    175
        }
    176
    0
    }
    177
    178
    /// # Errors
    179
    ///
    180
    /// Returns an error if the operation fails
    181
    /// # Errors
    182
    ///
    183
    /// Returns an error if the operation fails
    184
    0
    pub fn parse_type_parameters(state: &mut ParserState) -> Result<Vec<String>> {
    185
    0
        state.tokens.expect(&Token::Less)?;
    186
    187
    0
        let mut type_params = Vec::new();
    188
    189
        // Parse first type parameter
    190
    0
        if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    191
    0
            type_params.push(name.clone());
    192
    0
            state.tokens.advance();
    193
    0
        }
    194
    195
        // Parse additional type parameters
    196
    0
        while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    197
    0
            state.tokens.advance(); // consume comma
    198
    0
            if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    199
    0
                type_params.push(name.clone());
    200
    0
                state.tokens.advance();
    201
    0
            }
    202
        }
    203
    204
    0
        state.tokens.expect(&Token::Greater)?;
    205
    0
        Ok(type_params)
    206
    0
    }
    207
    208
    /// Parse type expressions with complexity ≤10
    209
    /// # Errors
    210
    /// Returns an error if the operation fails
    211
    0
    pub fn parse_type(state: &mut ParserState) -> Result<Type> {
    212
    0
        let span = Span { start: 0, end: 0 }; // Simplified for now
    213
    214
    0
        match state.tokens.peek() {
    215
    0
            Some((Token::Ampersand, _)) => parse_reference_type(state, span),
    216
    0
            Some((Token::Fn, _)) => parse_fn_type(state, span),
    217
    0
            Some((Token::LeftBracket, _)) => parse_list_type(state, span),
    218
    0
            Some((Token::LeftParen, _)) => parse_paren_type(state, span),
    219
            Some((Token::Identifier(_) | Token::Result | Token::Option | Token::Ok |
    220
    0
    Token::Err | Token::Some | Token::DataFrame | Token::None | Token::Null, _)) => parse_named_type(state, span),
    221
    0
            _ => bail!("Expected type"),
    222
        }
    223
    0
    }
    224
    225
    // Helper: Parse reference type &T or &mut T (complexity: 4)
    226
    0
    fn parse_reference_type(state: &mut ParserState, span: Span) -> Result<Type> {
    227
    0
        state.tokens.advance(); // consume &
    228
    229
    0
        let is_mut = if matches!(state.tokens.peek(), Some((Token::Mut, _))) {
    230
    0
            state.tokens.advance(); // consume mut
    231
    0
            true
    232
        } else {
    233
    0
            false
    234
        };
    235
    236
    0
        let inner_type = parse_type(state)?;
    237
    238
    0
        Ok(Type {
    239
    0
            kind: TypeKind::Reference {
    240
    0
                is_mut,
    241
    0
                inner: Box::new(inner_type),
    242
    0
            },
    243
    0
            span,
    244
    0
        })
    245
    0
    }
    246
    247
    // Helper: Parse function type fn(T1, T2) -> T3 (complexity: 5)
    248
    0
    fn parse_fn_type(state: &mut ParserState, span: Span) -> Result<Type> {
    249
    0
        state.tokens.advance(); // consume fn
    250
    0
        state.tokens.expect(&Token::LeftParen)?;
    251
    252
    0
        let param_types = parse_type_list(state)?;
    253
    0
        state.tokens.expect(&Token::RightParen)?;
    254
    0
        state.tokens.expect(&Token::Arrow)?;
    255
    0
        let ret_type = parse_type(state)?;
    256
    257
    0
        Ok(Type {
    258
    0
            kind: TypeKind::Function {
    259
    0
                params: param_types,
    260
    0
                ret: Box::new(ret_type),
    261
    0
            },
    262
    0
            span,
    263
    0
        })
    264
    0
    }
    265
    266
    // Helper: Parse list type `[T]` (complexity: 3)
    267
    0
    fn parse_list_type(state: &mut ParserState, span: Span) -> Result<Type> {
    268
    0
        state.tokens.advance(); // consume [
    269
    0
        let inner = parse_type(state)?;
    270
    0
        state.tokens.expect(&Token::RightBracket)?;
    271
    272
    0
        Ok(Type {
    273
    0
            kind: TypeKind::List(Box::new(inner)),
    274
    0
            span,
    275
    0
        })
    276
    0
    }
    277
    278
    // Helper: Parse parenthesized type (T1, T2) or (T1, T2) -> T3 (complexity: 6)
    279
    0
    fn parse_paren_type(state: &mut ParserState, span: Span) -> Result<Type> {
    280
    0
        state.tokens.advance(); // consume (
    281
    282
    0
        if matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    283
            // Unit type: ()
    284
    0
            state.tokens.advance();
    285
    0
            Ok(Type {
    286
    0
                kind: TypeKind::Named("()".to_string()),
    287
    0
                span,
    288
    0
            })
    289
        } else {
    290
    0
            let param_types = parse_type_list(state)?;
    291
    0
            state.tokens.expect(&Token::RightParen)?;
    292
    293
    0
            if matches!(state.tokens.peek(), Some((Token::Arrow, _))) {
    294
                // Function type: (T1, T2) -> T3
    295
    0
                state.tokens.advance(); // consume ->
    296
    0
                let ret_type = parse_type(state)?;
    297
    298
    0
                Ok(Type {
    299
    0
                    kind: TypeKind::Function {
    300
    0
                        params: param_types,
    301
    0
                        ret: Box::new(ret_type),
    302
    0
                    },
    303
    0
                    span,
    304
    0
                })
    305
            } else {
    306
                // Tuple type: (T1, T2)
    307
    0
                Ok(Type {
    308
    0
                    kind: TypeKind::Tuple(param_types),
    309
    0
                    span,
    310
    0
                })
    311
            }
    312
        }
    313
    0
    }
    314
    315
    // Helper: Parse named type with optional generics (complexity: 4)
    316
    0
    fn parse_named_type(state: &mut ParserState, span: Span) -> Result<Type> {
    317
    0
        let name = parse_qualified_name(state)?;
    318
    319
    0
        if matches!(state.tokens.peek(), Some((Token::Less, _))) {
    320
    0
            parse_generic_type(state, name, span)
    321
        } else {
    322
    0
            Ok(Type {
    323
    0
                kind: TypeKind::Named(name),
    324
    0
                span,
    325
    0
            })
    326
        }
    327
    0
    }
    328
    329
    // Helper: Parse qualified name like std::collections::HashMap (complexity: 6)
    330
    0
    fn parse_qualified_name(state: &mut ParserState) -> Result<String> {
    331
    0
        let mut name = match state.tokens.peek() {
    332
    0
            Some((Token::Identifier(n), _)) => {
    333
    0
                let name = n.clone();
    334
    0
                state.tokens.advance();
    335
    0
                name
    336
            }
    337
            // Handle special tokens that can be type names
    338
            Some((Token::Result, _)) => {
    339
    0
                state.tokens.advance();
    340
    0
                "Result".to_string()
    341
            }
    342
            Some((Token::Option, _)) => {
    343
    0
                state.tokens.advance();
    344
    0
                "Option".to_string()
    345
            }
    346
            Some((Token::Ok, _)) => {
    347
    0
                state.tokens.advance();
    348
    0
                "Ok".to_string()
    349
            }
    350
            Some((Token::Err, _)) => {
    351
    0
                state.tokens.advance();
    352
    0
                "Err".to_string()
    353
            }
    354
            Some((Token::Some, _)) => {
    355
    0
                state.tokens.advance();
    356
    0
                "Some".to_string()
    357
            }
    358
            Some((Token::DataFrame, _)) => {
    359
    0
                state.tokens.advance();
    360
    0
                "DataFrame".to_string()
    361
            }
    362
            Some((Token::None | Token::Null, _)) => {
    363
    0
                state.tokens.advance();
    364
    0
                "None".to_string()
    365
            }
    366
    0
            _ => bail!("Expected identifier"),
    367
        };
    368
    369
    0
        while matches!(state.tokens.peek(), Some((Token::ColonColon, _))) {
    370
    0
            state.tokens.advance(); // consume ::
    371
    372
    0
            let next_name = match state.tokens.peek() {
    373
    0
                Some((Token::Identifier(next), _)) => next.clone(),
    374
                // Handle special tokens that can be type names
    375
    0
                Some((Token::Result, _)) => "Result".to_string(),
    376
    0
                Some((Token::Option, _)) => "Option".to_string(),
    377
    0
                Some((Token::Ok, _)) => "Ok".to_string(),
    378
    0
                Some((Token::Err, _)) => "Err".to_string(),
    379
    0
                Some((Token::Some, _)) => "Some".to_string(),
    380
    0
                Some((Token::None | Token::Null, _)) => "None".to_string(),
    381
    0
                _ => bail!("Expected identifier after :: in type name"),
    382
            };
    383
    384
    0
            name.push_str("::");
    385
    0
            name.push_str(&next_name);
    386
    0
            state.tokens.advance();
    387
        }
    388
    389
    0
        Ok(name)
    390
    0
    }
    391
    392
    // Helper: Parse generic type Vec<T, U> (complexity: 4)
    393
    0
    fn parse_generic_type(state: &mut ParserState, base: String, span: Span) -> Result<Type> {
    394
    0
        state.tokens.advance(); // consume <
    395
    0
        let type_params = parse_type_list(state)?;
    396
    0
        state.tokens.expect(&Token::Greater)?;
    397
    398
    0
        Ok(Type {
    399
    0
            kind: TypeKind::Generic {
    400
    0
                base,
    401
    0
                params: type_params,
    402
    0
            },
    403
    0
            span,
    404
    0
        })
    405
    0
    }
    406
    407
    // Helper: Parse comma-separated type list (complexity: 3)
    408
    0
    fn parse_type_list(state: &mut ParserState) -> Result<Vec<Type>> {
    409
    0
        let mut types = Vec::new();
    410
    411
    0
        if !matches!(state.tokens.peek(), Some((Token::RightParen | Token::Greater, _))) {
    412
    0
            types.push(parse_type(state)?);
    413
    414
    0
            while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    415
    0
                state.tokens.advance(); // consume comma
    416
    0
                types.push(parse_type(state)?);
    417
            }
    418
    0
        }
    419
    420
    0
        Ok(types)
    421
    0
    }
    422
    423
    /// Parse import statements in various forms
    424
    ///
    425
    /// Supports:
    426
    /// - Simple imports: `import std::collections::HashMap`
    427
    /// - Multiple imports: `import std::io::{Read, Write}`
    428
    /// - Aliased imports: `import std::collections::{HashMap as Map}`
    429
    /// - Wildcard imports: `import std::collections::*`
    430
    ///
    431
    /// # Examples
    432
    ///
    433
    /// ```
    434
    /// use ruchy::frontend::parser::Parser;
    435
    /// use ruchy::frontend::ast::{ExprKind, ImportItem};
    436
    ///
    437
    /// let mut parser = Parser::new("import std::collections::HashMap");
    438
    /// let expr = parser.parse().unwrap();
    439
    ///
    440
    /// match &expr.kind {
    441
    ///     ExprKind::Import { path, items } => {
    442
    ///         assert_eq!(path, "std::collections::HashMap");
    443
    ///         assert_eq!(items.len(), 1);
    444
    ///         assert!(matches!(items[0], ImportItem::Named(ref name) if name == "HashMap"));
    445
    ///     }
    446
    ///     _ => panic!("Expected Import expression"),
    447
    /// }
    448
    /// ```
    449
    ///
    450
    /// ```
    451
    /// use ruchy::frontend::parser::Parser;
    452
    /// use ruchy::frontend::ast::{ExprKind, ImportItem};
    453
    ///
    454
    /// // Multiple imports with alias
    455
    /// let mut parser = Parser::new("import std::collections::{HashMap as Map, Vec}");
    456
    /// let expr = parser.parse().unwrap();
    457
    ///
    458
    /// match &expr.kind {
    459
    ///     ExprKind::Import { path, items } => {
    460
    ///         assert_eq!(path, "std::collections");
    461
    ///         assert_eq!(items.len(), 2);
    462
    ///         assert!(matches!(&items[0], ImportItem::Aliased { name, alias }
    463
    ///                          if name == "HashMap" && alias == "Map"));
    464
    ///         assert!(matches!(&items[1], ImportItem::Named(name) if name == "Vec"));
    465
    ///     }
    466
    ///     _ => panic!("Expected Import expression"),
    467
    /// }
    468
    /// ```
    469
    ///
    470
    /// # Errors
    471
    ///
    472
    /// Returns an error if:
    473
    /// - No identifier follows the import keyword
    474
    /// - Invalid syntax in import specification
    475
    /// - Unexpected tokens in import list
    476
    ///
    477
    /// Parse import statement (complexity: 7)
    478
    /// Orchestrates URL and regular import parsing
    479
    0
    pub fn parse_import(state: &mut ParserState) -> Result<Expr> {
    480
    0
        let start_span = state.tokens.advance().expect("checked by parser logic").1;
    481
    482
        // Check for URL import first
    483
    0
        if let Some((Token::String(url), _)) = state.tokens.peek() {
    484
    0
            let url = url.clone();
    485
    0
            return parse_url_import(state, &url, start_span);
    486
    0
        }
    487
    488
        // Parse regular module import
    489
    0
        let path_parts = parse_module_path(state)?;
    490
    0
        let items = parse_import_items(state, &path_parts)?;
    491
    492
    0
        create_import_expression(path_parts, items, start_span)
    493
    0
    }
    494
    495
    /// Parse URL import statement (complexity: 6)
    496
    0
    fn parse_url_import(state: &mut ParserState, url: &str, start_span: Span) -> Result<Expr> {
    497
        // Validate URL format
    498
    0
        if !url.starts_with("https://") && !url.starts_with("http://") {
    499
    0
            bail!("URL imports must start with 'https://' or 'http://'");
    500
    0
        }
    501
    502
        // Safety validation for URL imports
    503
    0
        validate_url_import(url)?;
    504
    505
    0
        state.tokens.advance();
    506
    507
    0
        Ok(Expr::new(
    508
    0
            ExprKind::Import {
    509
    0
                path: url.to_string(),
    510
    0
                items: vec![ImportItem::Named("*".to_string())],
    511
    0
            },
    512
    0
            start_span,
    513
    0
        ))
    514
    0
    }
    515
    516
    /// Parse module path components (complexity: 8)
    517
    0
    fn parse_module_path(state: &mut ParserState) -> Result<Vec<String>> {
    518
    0
        let mut path_parts = Vec::new();
    519
    520
        // Get first identifier
    521
    0
        if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    522
    0
            path_parts.push(name.clone());
    523
    0
            state.tokens.advance();
    524
    525
            // Parse additional path segments
    526
    0
            while matches!(state.tokens.peek(), Some((Token::ColonColon, _))) {
    527
    0
                state.tokens.advance(); // consume ::
    528
    529
                // Check if this is the start of import items
    530
    0
                if is_import_items_start(state) {
    531
    0
                    break;
    532
    0
                }
    533
    534
    0
                path_parts.push(parse_path_segment(state)?);
    535
            }
    536
    0
        }
    537
    538
    0
        Ok(path_parts)
    539
    0
    }
    540
    541
    /// Check if current position is start of import items (complexity: 2)
    542
    0
    fn is_import_items_start(state: &mut ParserState) -> bool {
    543
    0
        matches!(
    544
    0
            state.tokens.peek(),
    545
            Some((Token::Star | Token::LeftBrace, _))
    546
        )
    547
    0
    }
    548
    549
    /// Parse single path segment after :: (complexity: 3)
    550
    0
    fn parse_path_segment(state: &mut ParserState) -> Result<String> {
    551
    0
        if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    552
    0
            let name = name.clone();
    553
    0
            state.tokens.advance();
    554
    0
            Ok(name)
    555
        } else {
    556
    0
            bail!("Expected identifier, '*', or '{{' after '::'");
    557
        }
    558
    0
    }
    559
    560
    /// Parse import items (wildcard, braced list, or simple) (complexity: 9)
    561
    0
    fn parse_import_items(state: &mut ParserState, path_parts: &[String]) -> Result<Vec<ImportItem>> {
    562
    0
        if matches!(state.tokens.peek(), Some((Token::Star, _))) {
    563
    0
            parse_wildcard_import(state)
    564
    0
        } else if matches!(state.tokens.peek(), Some((Token::LeftBrace, _))) {
    565
    0
            parse_braced_import_list(state)
    566
        } else {
    567
    0
            parse_simple_import(state, path_parts)
    568
        }
    569
    0
    }
    570
    571
    /// Parse wildcard import (* syntax) (complexity: 2)
    572
    0
    fn parse_wildcard_import(state: &mut ParserState) -> Result<Vec<ImportItem>> {
    573
    0
        state.tokens.advance(); // consume *
    574
    0
        Ok(vec![ImportItem::Wildcard])
    575
    0
    }
    576
    577
    /// Parse braced import list ({item1, item2, ...}) (complexity: 10)
    578
    0
    fn parse_braced_import_list(state: &mut ParserState) -> Result<Vec<ImportItem>> {
    579
    0
        state.tokens.expect(&Token::LeftBrace)?;
    580
    581
    0
        let mut items = Vec::new();
    582
    0
        while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    583
    0
            if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    584
    0
                let name = name.clone();
    585
    0
                state.tokens.advance();
    586
    587
    0
                let item = parse_import_item_with_alias(state, name)?;
    588
    0
                items.push(item);
    589
    590
    0
                if !handle_item_separator(state)? {
    591
    0
                    break;
    592
    0
                }
    593
            } else {
    594
    0
                validate_braced_list_token(state)?;
    595
            }
    596
        }
    597
    598
    0
        state.tokens.expect(&Token::RightBrace)?;
    599
    0
        Ok(items)
    600
    0
    }
    601
    602
    /// Parse import item with optional alias (complexity: 6)
    603
    0
    fn parse_import_item_with_alias(state: &mut ParserState, name: String) -> Result<ImportItem> {
    604
    0
        if matches!(state.tokens.peek(), Some((Token::As, _))) {
    605
    0
            state.tokens.advance(); // consume as
    606
    0
            if let Some((Token::Identifier(alias), _)) = state.tokens.peek() {
    607
    0
                let alias = alias.clone();
    608
    0
                state.tokens.advance();
    609
    0
                Ok(ImportItem::Aliased { name, alias })
    610
            } else {
    611
    0
                bail!("Expected alias name after 'as'");
    612
            }
    613
        } else {
    614
    0
            Ok(ImportItem::Named(name))
    615
        }
    616
    0
    }
    617
    618
    /// Handle item separator in braced list (complexity: 4)
    619
    0
    fn handle_item_separator(state: &mut ParserState) -> Result<bool> {
    620
    0
        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    621
    0
            state.tokens.advance();
    622
    0
            Ok(true) // Continue parsing
    623
    0
        } else if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    624
    0
            Ok(false) // End of list
    625
        } else {
    626
    0
            bail!("Expected ',' or '}}' in import list");
    627
        }
    628
    0
    }
    629
    630
    /// Validate token in braced import list (complexity: 3)
    631
    0
    fn validate_braced_list_token(state: &mut ParserState) -> Result<()> {
    632
    0
        if !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    633
    0
            bail!("Expected identifier or '}}' in import list");
    634
    0
        }
    635
    0
        Ok(())
    636
    0
    }
    637
    638
    /// Parse simple import (path or path as alias) (complexity: 8)
    639
    0
    fn parse_simple_import(state: &mut ParserState, path_parts: &[String]) -> Result<Vec<ImportItem>> {
    640
    0
        if matches!(state.tokens.peek(), Some((Token::As, _))) {
    641
    0
            parse_simple_import_with_alias(state, path_parts)
    642
        } else {
    643
    0
            parse_simple_import_without_alias(path_parts)
    644
        }
    645
    0
    }
    646
    647
    /// Parse simple import with alias (complexity: 5)
    648
    0
    fn parse_simple_import_with_alias(state: &mut ParserState, path_parts: &[String]) -> Result<Vec<ImportItem>> {
    649
    0
        state.tokens.advance(); // consume as
    650
    0
        if let Some((Token::Identifier(alias), _)) = state.tokens.peek() {
    651
    0
            let alias = alias.clone();
    652
    0
            state.tokens.advance();
    653
    0
            Ok(vec![ImportItem::Aliased {
    654
    0
                name: path_parts.last().unwrap_or(&String::new()).clone(),
    655
    0
                alias,
    656
    0
            }])
    657
        } else {
    658
    0
            bail!("Expected alias name after 'as'");
    659
        }
    660
    0
    }
    661
    662
    /// Parse simple import without alias (complexity: 5)
    663
    0
    fn parse_simple_import_without_alias(path_parts: &[String]) -> Result<Vec<ImportItem>> {
    664
    0
        if path_parts.is_empty() {
    665
    0
            Ok(Vec::new())
    666
    0
        } else if path_parts.len() == 1 {
    667
            // Single segment - treat as wildcard
    668
    0
            Ok(Vec::new())
    669
        } else {
    670
            // Multi-segment - import the last part
    671
    0
            Ok(vec![ImportItem::Named(
    672
    0
                path_parts
    673
    0
                    .last()
    674
    0
                    .expect("checked: !path_parts.is_empty()")
    675
    0
                    .clone(),
    676
    0
            )])
    677
        }
    678
    0
    }
    679
    680
    /// Create final import expression (complexity: 4)
    681
    0
    fn create_import_expression(path_parts: Vec<String>, items: Vec<ImportItem>, start_span: Span) -> Result<Expr> {
    682
    0
        let path = path_parts.join("::");
    683
    684
        // Validate that we have either a path or items
    685
    0
        if path.is_empty() && items.is_empty() {
    686
    0
            bail!("Expected import path or items after 'import'");
    687
    0
        }
    688
    689
    0
        Ok(Expr::new(ExprKind::Import { path, items }, start_span))
    690
    0
    }
    691
    692
    /// # Errors
    693
    ///
    694
    /// Returns an error if the operation fails
    695
    /// # Errors
    696
    ///
    697
    /// Returns an error if the operation fails
    698
    0
    pub fn parse_attributes(state: &mut ParserState) -> Result<Vec<Attribute>> {
    699
    0
        let mut attributes = Vec::new();
    700
    701
    0
        while matches!(state.tokens.peek(), Some((Token::Hash, _))) {
    702
    0
            state.tokens.advance(); // consume #
    703
    704
    0
            if !matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
    705
    0
                bail!("Expected '[' after '#'");
    706
    0
            }
    707
    0
            state.tokens.advance(); // consume [
    708
    709
    0
            let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
    710
    0
                let name = n.clone();
    711
    0
                state.tokens.advance();
    712
    0
                name
    713
            } else {
    714
    0
                bail!("Expected attribute name");
    715
            };
    716
    717
    0
            let mut args = Vec::new();
    718
    0
            if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
    719
    0
                state.tokens.advance(); // consume (
    720
    721
    0
                while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
    722
    0
                    if let Some((Token::Identifier(arg), _)) = state.tokens.peek() {
    723
    0
                        args.push(arg.clone());
    724
    0
                        state.tokens.advance();
    725
    726
    0
                        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    727
    0
                            state.tokens.advance();
    728
    0
                        } else {
    729
    0
                            break;
    730
                        }
    731
                    } else {
    732
    0
                        break;
    733
                    }
    734
                }
    735
    736
    0
                state.tokens.advance(); // consume )
    737
    0
            }
    738
    739
    0
            let end_span = state.tokens.advance().expect("Expected ']' token").1; // consume ]
    740
    741
    0
            attributes.push(Attribute {
    742
    0
                name,
    743
    0
                args,
    744
    0
                span: end_span,
    745
    0
            });
    746
        }
    747
    748
    0
        Ok(attributes)
    749
    0
    }
    750
    751
    /// Parse string interpolation from a string containing {expr} patterns
    752
    0
    pub fn parse_string_interpolation(_state: &mut ParserState, s: &str) -> Vec<StringPart> {
    753
    0
        let mut parts = Vec::new();
    754
    0
        let mut chars = s.chars().peekable();
    755
    0
        let mut current_text = String::new();
    756
    757
    0
        while let Some(ch) = chars.next() {
    758
    0
            match ch {
    759
    0
                '{' if chars.peek() == Some(&'{') => {
    760
    0
                    handle_escaped_brace(&mut chars, &mut current_text, '{');
    761
    0
                }
    762
    0
                '}' if chars.peek() == Some(&'}') => {
    763
    0
                    handle_escaped_brace(&mut chars, &mut current_text, '}');
    764
    0
                }
    765
    0
                '{' => {
    766
    0
                    handle_interpolation(&mut chars, &mut parts, &mut current_text);
    767
    0
                }
    768
    0
                _ => current_text.push(ch),
    769
            }
    770
        }
    771
    772
    0
        finalize_text_part(&mut parts, current_text);
    773
    0
        parts
    774
    0
    }
    775
    776
    // Helper: Handle escaped braces (complexity: 2)
    777
    0
    fn handle_escaped_brace<T: Iterator<Item = char>>(
    778
    0
        chars: &mut T,
    779
    0
        current_text: &mut String,
    780
    0
        brace_char: char,
    781
    0
    ) {
    782
    0
        chars.next(); // consume second brace
    783
    0
        current_text.push(brace_char);
    784
    0
    }
    785
    786
    // Helper: Handle interpolation expressions (complexity: 4)
    787
    0
    fn handle_interpolation<T: Iterator<Item = char>>(
    788
    0
        chars: &mut T,
    789
    0
        parts: &mut Vec<StringPart>,
    790
    0
        current_text: &mut String,
    791
    0
    ) {
    792
    0
        if !current_text.is_empty() {
    793
    0
            parts.push(StringPart::Text(current_text.clone()));
    794
    0
            current_text.clear();
    795
    0
        }
    796
    797
    0
        let expr_text = extract_expression_text(chars);
    798
    0
        let string_part = parse_interpolated_expr(&expr_text);
    799
    0
        parts.push(string_part);
    800
    0
    }
    801
    802
    // Helper: Extract expression text from braces (complexity: 8)
    803
    0
    fn extract_expression_text<T: Iterator<Item = char>>(chars: &mut T) -> String {
    804
    0
        let mut expr_text = String::new();
    805
    0
        let mut context = ExprContext::default();
    806
    807
    0
        for expr_ch in chars {
    808
    0
            if process_character(expr_ch, &mut context, &mut expr_text) {
    809
    0
                break;
    810
    0
            }
    811
        }
    812
    813
    0
        expr_text
    814
    0
    }
    815
    816
    /// Process a single character in expression extraction (complexity: 8)
    817
    0
    fn process_character(ch: char, context: &mut ExprContext, expr_text: &mut String) -> bool {
    818
    0
        match ch {
    819
    0
            '"' if should_process_string_quote(context) => {
    820
    0
                handle_string_delimiter(context);
    821
    0
                expr_text.push(ch);
    822
    0
            }
    823
    0
            '\'' if should_process_char_quote(context) => {
    824
    0
                handle_char_delimiter(context);
    825
    0
                expr_text.push(ch);
    826
    0
            }
    827
    0
            '{' if should_process_brace(context) => {
    828
    0
                handle_open_brace(context);
    829
    0
                expr_text.push(ch);
    830
    0
            }
    831
    0
            '}' if should_process_brace(context) => {
    832
    0
                handle_close_brace(context);
    833
    0
                if should_terminate(context) {
    834
    0
                    return true; // Signal to break the loop
    835
    0
                }
    836
    0
                expr_text.push(ch);
    837
            }
    838
    0
            '\\' if should_escape(context) => {
    839
    0
                handle_escape(context);
    840
    0
                expr_text.push(ch);
    841
    0
            }
    842
    0
            _ => {
    843
    0
                handle_regular_char(context, ch);
    844
    0
                expr_text.push(ch);
    845
    0
            }
    846
        }
    847
    848
        // Reset escape flag for non-backslash characters
    849
    0
        reset_escape_flag(context, ch);
    850
    0
        false // Continue processing
    851
    0
    }
    852
    853
    /// Check if string quote should be processed (complexity: 1)
    854
    0
    fn should_process_string_quote(context: &ExprContext) -> bool {
    855
    0
        !context.in_char && !context.escaped
    856
    0
    }
    857
    858
    /// Check if char quote should be processed (complexity: 1)
    859
    0
    fn should_process_char_quote(context: &ExprContext) -> bool {
    860
    0
        !context.in_string && !context.escaped
    861
    0
    }
    862
    863
    /// Check if brace should be processed (complexity: 1)
    864
    0
    fn should_process_brace(context: &ExprContext) -> bool {
    865
    0
        !context.in_string && !context.in_char
    866
    0
    }
    867
    868
    /// Check if escape should be handled (complexity: 1)
    869
    0
    fn should_escape(context: &ExprContext) -> bool {
    870
    0
        (context.in_string || context.in_char) && !context.escaped
    871
    0
    }
    872
    873
    /// Toggle string delimiter state (complexity: 1)
    874
    0
    fn handle_string_delimiter(context: &mut ExprContext) {
    875
    0
        context.in_string = !context.in_string;
    876
    0
    }
    877
    878
    /// Toggle char delimiter state (complexity: 1)
    879
    0
    fn handle_char_delimiter(context: &mut ExprContext) {
    880
    0
        context.in_char = !context.in_char;
    881
    0
    }
    882
    883
    /// Increment brace count (complexity: 1)
    884
    0
    fn handle_open_brace(context: &mut ExprContext) {
    885
    0
        context.brace_count += 1;
    886
    0
    }
    887
    888
    /// Decrement brace count (complexity: 1)
    889
    0
    fn handle_close_brace(context: &mut ExprContext) {
    890
    0
        context.brace_count -= 1;
    891
    0
    }
    892
    893
    /// Set escape flag (complexity: 1)
    894
    0
    fn handle_escape(context: &mut ExprContext) {
    895
    0
        context.escaped = true;
    896
    0
    }
    897
    898
    /// Handle regular character (complexity: 1)
    899
    0
    fn handle_regular_char(context: &mut ExprContext, _ch: char) {
    900
    0
        context.escaped = false;
    901
    0
    }
    902
    903
    /// Reset escape flag if needed (complexity: 2)
    904
    0
    fn reset_escape_flag(context: &mut ExprContext, ch: char) {
    905
    0
        if ch != '\\' {
    906
    0
            context.escaped = false;
    907
    0
        }
    908
    0
    }
    909
    910
    /// Check if we should terminate extraction (complexity: 1)
    911
    0
    fn should_terminate(context: &ExprContext) -> bool {
    912
    0
        context.brace_count == 0
    913
    0
    }
    914
    915
    // Helper: Parse interpolated expression with format specifier (complexity: 4)
    916
    0
    fn parse_interpolated_expr(expr_text: &str) -> StringPart {
    917
    0
        let (expr_part, format_spec) = split_format_specifier(expr_text);
    918
    919
    0
        let mut expr_parser = super::core::Parser::new(expr_part);
    920
    0
        match expr_parser.parse() {
    921
    0
            Ok(expr) => {
    922
    0
                if let Some(spec) = format_spec {
    923
    0
                    StringPart::ExprWithFormat {
    924
    0
                        expr: Box::new(expr),
    925
    0
                        format_spec: spec.to_string(),
    926
    0
                    }
    927
                } else {
    928
    0
                    StringPart::Expr(Box::new(expr))
    929
                }
    930
            }
    931
            Err(_) => {
    932
                // Fallback to text if parsing fails
    933
    0
                StringPart::Text(format!("{{{expr_text}}}"))
    934
            }
    935
        }
    936
    0
    }
    937
    938
    // Helper: Split format specifier from expression (complexity: 3)
    939
    0
    fn split_format_specifier(expr_text: &str) -> (&str, Option<&str>) {
    940
    0
        if let Some(colon_pos) = expr_text.find(':') {
    941
    0
            let before_colon = &expr_text[..colon_pos];
    942
    0
            if !before_colon.contains('"') && !before_colon.contains('\'') {
    943
    0
                (before_colon, Some(&expr_text[colon_pos..]))
    944
            } else {
    945
    0
                (expr_text, None)
    946
            }
    947
        } else {
    948
    0
            (expr_text, None)
    949
        }
    950
    0
    }
    951
    952
    // Helper: Finalize remaining text (complexity: 2)
    953
    0
    fn finalize_text_part(parts: &mut Vec<StringPart>, current_text: String) {
    954
    0
        if !current_text.is_empty() {
    955
    0
            parts.push(StringPart::Text(current_text));
    956
    0
        }
    957
    0
    }
    958
    959
    // Helper struct to track expression parsing context (complexity: 0)
    960
    #[derive(Default)]
    961
    struct ExprContext {
    962
        brace_count: i32,
    963
        in_string: bool,
    964
        in_char: bool,
    965
        escaped: bool,
    966
    }
    967
    968
    impl ExprContext {
    969
    0
        fn default() -> Self {
    970
    0
            Self {
    971
    0
                brace_count: 1,
    972
    0
                in_string: false,
    973
    0
                in_char: false,
    974
    0
                escaped: false,
    975
    0
            }
    976
    0
        }
    977
    }
    978
    979
    /// Parse module declarations
    980
    ///
    981
    /// Supports:
    982
    /// - Empty modules: `module MyModule {}`
    983
    /// - Single expression modules: `module Math { sqrt(x) }`
    984
    /// - Multi-expression modules: `module Utils { fn helper() {...}; const PI = 3.14 }`
    985
    ///
    986
    /// # Examples
    987
    ///
    988
    /// ```
    989
    /// use ruchy::frontend::parser::Parser;
    990
    /// use ruchy::frontend::ast::ExprKind;
    991
    ///
    992
    /// // Empty module
    993
    /// let mut parser = Parser::new("module Empty {}");
    994
    /// let expr = parser.parse().unwrap();
    995
    ///
    996
    /// match &expr.kind {
    997
    ///     ExprKind::Module { name, .. } => {
    998
    ///         assert_eq!(name, "Empty");
    999
    ///     }
    1000
    ///     _ => panic!("Expected Module expression"),
    1001
    /// }
    1002
    /// ```
    1003
    ///
    1004
    /// ```
    1005
    /// use ruchy::frontend::parser::Parser;
    1006
    /// use ruchy::frontend::ast::{ExprKind, Literal};
    1007
    ///
    1008
    /// // Module with content
    1009
    /// let mut parser = Parser::new("module Math { 42 }");
    1010
    /// let expr = parser.parse().unwrap();
    1011
    ///
    1012
    /// match &expr.kind {
    1013
    ///     ExprKind::Module { name, body } => {
    1014
    ///         assert_eq!(name, "Math");
    1015
    ///         // Verify body contains literal 42
    1016
    ///         match &body.kind {
    1017
    ///             ExprKind::Literal(Literal::Integer(n)) => assert_eq!(*n, 42),
    1018
    ///             _ => panic!("Expected integer literal in module body"),
    1019
    ///         }
    1020
    ///     }
    1021
    ///     _ => panic!("Expected Module expression"),
    1022
    /// }
    1023
    /// ```
    1024
    ///
    1025
    /// # Errors
    1026
    ///
    1027
    /// Returns an error if:
    1028
    /// - No identifier follows the module keyword
    1029
    /// - Missing opening or closing braces
    1030
    /// - Invalid syntax in module body
    1031
    0
    pub fn parse_module(state: &mut ParserState) -> Result<Expr> {
    1032
    0
        let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume module
    1033
    1034
        // Parse module name
    1035
    0
        let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() {
    1036
    0
            let name = n.clone();
    1037
    0
            state.tokens.advance();
    1038
    0
            name
    1039
        } else {
    1040
    0
            bail!("Expected module name after 'module'");
    1041
        };
    1042
    1043
        // Expect opening brace
    1044
    0
        state.tokens.expect(&Token::LeftBrace)?;
    1045
    1046
        // Parse module body (can be a block or single expression)
    1047
    0
        let body = if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    1048
            // Empty module
    1049
    0
            Box::new(Expr::new(
    1050
    0
                ExprKind::Literal(Literal::Unit),
    1051
    0
                Span { start: 0, end: 0 },
    1052
            ))
    1053
        } else {
    1054
            // Parse expressions until we hit the closing brace
    1055
    0
            let mut exprs = Vec::new();
    1056
    0
            while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    1057
    0
                exprs.push(super::parse_expr_recursive(state)?);
    1058
                // Optional semicolon or comma separator
    1059
    0
                if matches!(
    1060
    0
                    state.tokens.peek(),
    1061
                    Some((Token::Semicolon | Token::Comma, _))
    1062
    0
                ) {
    1063
    0
                    state.tokens.advance();
    1064
    0
                }
    1065
            }
    1066
    1067
    0
            if exprs.len() == 1 {
    1068
    0
                Box::new(exprs.into_iter().next().expect("checked: exprs.len() == 1"))
    1069
            } else {
    1070
    0
                Box::new(Expr::new(ExprKind::Block(exprs), Span { start: 0, end: 0 }))
    1071
            }
    1072
        };
    1073
    1074
        // Expect closing brace
    1075
    0
        state.tokens.expect(&Token::RightBrace)?;
    1076
    1077
    0
        Ok(Expr::new(ExprKind::Module { name, body }, start_span))
    1078
    0
    }
    1079
    1080
    /// Parse export statements
    1081
    ///
    1082
    /// Supports:
    1083
    /// - Single exports: `export myFunction`
    1084
    /// - Multiple exports: `export { func1, func2, func3 }`
    1085
    ///
    1086
    /// # Examples
    1087
    ///
    1088
    /// ```
    1089
    /// use ruchy::frontend::parser::Parser;
    1090
    /// use ruchy::frontend::ast::ExprKind;
    1091
    ///
    1092
    /// // Single export
    1093
    /// let mut parser = Parser::new("export myFunction");
    1094
    /// let expr = parser.parse().unwrap();
    1095
    ///
    1096
    /// match &expr.kind {
    1097
    ///     ExprKind::Export { items } => {
    1098
    ///         assert_eq!(items.len(), 1);
    1099
    ///         assert_eq!(items[0], "myFunction");
    1100
    ///     }
    1101
    ///     _ => panic!("Expected Export expression"),
    1102
    /// }
    1103
    /// ```
    1104
    ///
    1105
    /// ```
    1106
    /// use ruchy::frontend::parser::Parser;
    1107
    /// use ruchy::frontend::ast::ExprKind;
    1108
    ///
    1109
    /// // Multiple exports
    1110
    /// let mut parser = Parser::new("export { add, subtract, multiply }");
    1111
    /// let expr = parser.parse().unwrap();
    1112
    ///
    1113
    /// match &expr.kind {
    1114
    ///     ExprKind::Export { items } => {
    1115
    ///         assert_eq!(items.len(), 3);
    1116
    ///         assert!(items.contains(&"add".to_string()));
    1117
    ///         assert!(items.contains(&"subtract".to_string()));
    1118
    ///         assert!(items.contains(&"multiply".to_string()));
    1119
    ///     }
    1120
    ///     _ => panic!("Expected Export expression"),
    1121
    /// }
    1122
    /// ```
    1123
    ///
    1124
    /// # Errors
    1125
    ///
    1126
    /// Returns an error if:
    1127
    /// - No identifier or brace follows the export keyword
    1128
    /// - Invalid syntax in export list
    1129
    /// - Missing closing brace in export block
    1130
    0
    pub fn parse_export(state: &mut ParserState) -> Result<Expr> {
    1131
    0
        let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume export
    1132
    1133
    0
        let mut items = Vec::new();
    1134
    1135
        // Parse export list
    1136
    0
        if matches!(state.tokens.peek(), Some((Token::LeftBrace, _))) {
    1137
            // Export block: export { item1, item2, ... }
    1138
    0
            state.tokens.advance(); // consume {
    1139
    1140
    0
            while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
    1141
    0
                if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    1142
    0
                    items.push(name.clone());
    1143
    0
                    state.tokens.advance();
    1144
    1145
    0
                    if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
    1146
    0
                        state.tokens.advance(); // consume comma
    1147
    0
                    } else {
    1148
    0
                        break;
    1149
                    }
    1150
                } else {
    1151
    0
                    bail!("Expected identifier in export list");
    1152
                }
    1153
            }
    1154
    1155
    0
            state.tokens.expect(&Token::RightBrace)?;
    1156
    0
        } else if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
    1157
    0
            // Single export: export item
    1158
    0
            items.push(name.clone());
    1159
    0
            state.tokens.advance();
    1160
    0
        } else {
    1161
    0
            bail!("Expected export list or identifier after 'export'");
    1162
        }
    1163
    1164
    0
        Ok(Expr::new(ExprKind::Export { items }, start_span))
    1165
    0
    }