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
}