Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/frontend/error_recovery.rs
Line
Count
Source
1
//! Error recovery parser for better error messages and IDE support
2
3
#![allow(clippy::items_after_statements)] // Recovery parser needs constants in local scopes
4
5
use crate::frontend::ast::{
6
    BinaryOp, Expr, ExprKind, Literal, Param, Pattern, Span, Type, TypeKind,
7
};
8
use crate::frontend::lexer::{Token, TokenStream};
9
use anyhow::Result;
10
use std::fmt;
11
12
/// Parse error with recovery information
13
#[derive(Debug, Clone)]
14
pub struct ParseError {
15
    pub message: String,
16
    pub span: Span,
17
    pub recovery_hint: Option<String>,
18
    pub expected: Vec<Token>,
19
    pub found: Option<Token>,
20
    pub severity: ErrorSeverity,
21
    pub error_code: ErrorCode,
22
    pub context: Vec<String>, // Stack of parsing contexts for better error messages
23
}
24
25
/// Error severity levels
26
#[derive(Debug, Clone, PartialEq)]
27
pub enum ErrorSeverity {
28
    Error,
29
    Warning,
30
    Info,
31
    Hint,
32
}
33
34
/// Error codes for categorizing different types of errors
35
#[derive(Debug, Clone, PartialEq)]
36
pub enum ErrorCode {
37
    // Syntax errors
38
    UnexpectedToken,
39
    MissingToken,
40
    InvalidSyntax,
41
42
    // Type errors
43
    TypeMismatch,
44
    UndefinedVariable,
45
    DuplicateDefinition,
46
47
    // Pattern matching errors
48
    UnreachablePattern,
49
    NonExhaustivePattern,
50
51
    // Import/module errors
52
    ModuleNotFound,
53
    SymbolNotFound,
54
    CircularImport,
55
56
    // General errors
57
    InvalidOperation,
58
    InternalError,
59
}
60
61
impl fmt::Display for ParseError {
62
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63
        // Write severity and error code
64
0
        write!(f, "[{:?}:{:?}] ", self.severity, self.error_code)?;
65
66
        // Write context if available
67
0
        if !self.context.is_empty() {
68
0
            write!(f, "In {}: ", self.context.join(" -> "))?;
69
0
        }
70
71
        // Write main message
72
0
        write!(f, "{}", self.message)?;
73
74
        // Write location info
75
0
        write!(
76
0
            f,
77
0
            " at line {}, column {}",
78
0
            self.span.start + 1, // Convert to 1-based indexing for user display
79
0
            self.span.end - self.span.start + 1
80
0
        )?;
81
82
        // Write expected vs found tokens if available
83
0
        if !self.expected.is_empty() {
84
0
            write!(f, " (expected: {:?}", self.expected)?;
85
0
            if let Some(ref found) = self.found {
86
0
                write!(f, ", found: {found:?}")?;
87
0
            }
88
0
            write!(f, ")")?;
89
0
        }
90
91
        // Write recovery hint
92
0
        if let Some(ref hint) = self.recovery_hint {
93
0
            write!(f, "\n  💡 Hint: {hint}")?;
94
0
        }
95
96
0
        Ok(())
97
0
    }
98
}
99
100
impl std::error::Error for ParseError {}
101
102
impl ParseError {
103
    /// Create a new parse error with basic information
104
338
    pub fn new(message: String, span: Span) -> Self {
105
338
        Self {
106
338
            message,
107
338
            span,
108
338
            recovery_hint: None,
109
338
            expected: Vec::new(),
110
338
            found: None,
111
338
            severity: ErrorSeverity::Error,
112
338
            error_code: ErrorCode::InvalidSyntax,
113
338
            context: Vec::new(),
114
338
        }
115
338
    }
116
117
    /// Create an error for unexpected token
118
0
    pub fn unexpected_token(expected: Vec<Token>, found: Token, span: Span) -> Self {
119
0
        let message = format!("Unexpected token '{found:?}'");
120
0
        Self {
121
0
            message,
122
0
            span,
123
0
            recovery_hint: Some(
124
0
                "Check for missing operators, parentheses, or semicolons".to_string(),
125
0
            ),
126
0
            expected,
127
0
            found: Some(found),
128
0
            severity: ErrorSeverity::Error,
129
0
            error_code: ErrorCode::UnexpectedToken,
130
0
            context: Vec::new(),
131
0
        }
132
0
    }
133
134
    /// Create an error for missing token
135
0
    pub fn missing_token(expected: Token, span: Span) -> Self {
136
0
        let message = format!("Missing '{expected:?}'");
137
0
        Self {
138
0
            message,
139
0
            span,
140
0
            recovery_hint: Some(format!("Insert '{expected:?}' here")),
141
0
            expected: vec![expected],
142
0
            found: None,
143
0
            severity: ErrorSeverity::Error,
144
0
            error_code: ErrorCode::MissingToken,
145
0
            context: Vec::new(),
146
0
        }
147
0
    }
148
149
    /// Add parsing context to the error
150
    #[must_use]
151
0
    pub fn with_context(mut self, context: String) -> Self {
152
0
        self.context.push(context);
153
0
        self
154
0
    }
155
156
    /// Add recovery hint
157
    #[must_use]
158
336
    pub fn with_hint(mut self, hint: String) -> Self {
159
336
        self.recovery_hint = Some(hint);
160
336
        self
161
336
    }
162
163
    /// Set severity level
164
    #[must_use]
165
0
    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
166
0
        self.severity = severity;
167
0
        self
168
0
    }
169
170
    /// Set error code
171
    #[must_use]
172
0
    pub fn with_code(mut self, code: ErrorCode) -> Self {
173
0
        self.error_code = code;
174
0
        self
175
0
    }
176
}
177
178
/// Result of parsing with error recovery
179
#[derive(Debug)]
180
pub struct ParseResult {
181
    pub ast: Option<Expr>,
182
    pub errors: Vec<ParseError>,
183
    pub partial_ast: bool,
184
}
185
186
/// Parser with error recovery capabilities
187
pub struct RecoveryParser<'a> {
188
    tokens: TokenStream<'a>,
189
    errors: Vec<ParseError>,
190
    recovery_mode: bool,
191
    ghost_node_count: usize,
192
    recursion_depth: usize,
193
}
194
195
impl<'a> RecoveryParser<'a> {
196
    #[must_use]
197
577
    pub fn new(input: &'a str) -> Self {
198
577
        Self {
199
577
            tokens: TokenStream::new(input),
200
577
            errors: Vec::new(),
201
577
            recovery_mode: false,
202
577
            ghost_node_count: 0,
203
577
            recursion_depth: 0,
204
577
        }
205
577
    }
206
207
    /// Parse with error recovery, always producing some AST
208
577
    pub fn parse_with_recovery(&mut self) -> ParseResult {
209
577
        match self.parse_expr_recovery() {
210
577
            Ok(ast) => ParseResult {
211
577
                ast: Some(ast),
212
577
                errors: self.errors.clone(),
213
577
                partial_ast: !self.errors.is_empty(),
214
577
            },
215
0
            Err(_) if !self.errors.is_empty() => {
216
                // We have errors but can produce a partial AST
217
0
                let ghost = self.create_ghost_node("Failed to parse expression");
218
0
                ParseResult {
219
0
                    ast: Some(ghost),
220
0
                    errors: self.errors.clone(),
221
0
                    partial_ast: true,
222
0
                }
223
            }
224
0
            Err(e) => {
225
                // Fatal error, no recovery possible
226
0
                self.errors.push(
227
0
                    ParseError::new(e.to_string(), Span::new(0, 0))
228
0
                        .with_code(ErrorCode::InternalError),
229
                );
230
0
                ParseResult {
231
0
                    ast: None,
232
0
                    errors: self.errors.clone(),
233
0
                    partial_ast: false,
234
0
                }
235
            }
236
        }
237
577
    }
238
239
630
    fn parse_expr_recovery(&mut self) -> Result<Expr> {
240
630
        self.parse_expr_with_precedence_recovery(0)
241
630
    }
242
243
947
    fn parse_expr_with_precedence_recovery(&mut self, min_prec: i32) -> Result<Expr> {
244
947
        let mut left = self.parse_prefix_recovery()
?0
;
245
        const MAX_ITERATIONS: usize = 1000; // Prevent infinite loops
246
947
        let mut iteration_count = 0;
247
248
        loop {
249
1.26k
            iteration_count += 1;
250
1.26k
            if iteration_count > MAX_ITERATIONS {
251
0
                self.record_error(
252
0
                    "Expression too complex or malformed".to_string(),
253
0
                    Some("Simplify the expression".to_string()),
254
                );
255
0
                break;
256
1.26k
            }
257
258
1.26k
            let Some((
token934
, _)) = self.tokens.peek() else {
259
330
                break;
260
            };
261
262
934
            if !token.is_binary_op() {
263
524
                break;
264
410
            }
265
266
410
            let token_clone = token.clone();
267
410
            let prec = Self::precedence(&token_clone);
268
410
            if prec < min_prec {
269
93
                break;
270
317
            }
271
272
317
            match self.tokens.advance() {
273
317
                Some((op_token, _op_span)) => {
274
317
                    let op = match Self::token_to_binary_op(&op_token) {
275
317
                        Ok(op) => op,
276
0
                        Err(e) => {
277
0
                            self.record_error(format!("Invalid operator: {e}"), None);
278
                            // Skip the invalid operator and continue
279
0
                            continue;
280
                        }
281
                    };
282
283
317
                    let right = if let Ok(expr) = self.parse_expr_with_precedence_recovery(prec + 1)
284
                    {
285
317
                        expr
286
                    } else {
287
                        // Create ghost node for missing right operand
288
0
                        let ghost = self.create_ghost_node("Missing right operand");
289
0
                        self.record_error(
290
0
                            format!("Expected expression after '{op_token:?}'"),
291
0
                            Some("Add the right side of the operation".to_string()),
292
                        );
293
0
                        ghost
294
                    };
295
296
317
                    let span = left.span.merge(right.span);
297
317
                    left = Expr::new(
298
317
                        ExprKind::Binary {
299
317
                            left: Box::new(left),
300
317
                            op,
301
317
                            right: Box::new(right),
302
317
                        },
303
317
                        span,
304
                    );
305
                }
306
0
                None => break,
307
            }
308
        }
309
310
947
        Ok(left)
311
947
    }
312
313
1.11k
    fn parse_prefix_recovery(&mut self) -> Result<Expr> {
314
        const MAX_RECURSION_DEPTH: usize = 100;
315
316
1.11k
        self.recursion_depth += 1;
317
1.11k
        if self.recursion_depth > MAX_RECURSION_DEPTH {
318
0
            self.recursion_depth -= 1;
319
0
            self.record_error(
320
0
                "Expression too deeply nested".to_string(),
321
0
                Some("Simplify the expression".to_string()),
322
            );
323
0
            return Ok(self.create_ghost_node("Max recursion depth"));
324
1.11k
        }
325
326
1.11k
        let 
result947
= match self.tokens.peek() {
327
384
            Some((Token::Integer(n), span)) => {
328
384
                let n = *n;
329
384
                let span = *span;
330
384
                self.tokens.advance();
331
384
                Ok(Expr::new(ExprKind::Literal(Literal::Integer(n)), span))
332
            }
333
0
            Some((Token::Float(f), span)) => {
334
0
                let f = *f;
335
0
                let span = *span;
336
0
                self.tokens.advance();
337
0
                Ok(Expr::new(ExprKind::Literal(Literal::Float(f)), span))
338
            }
339
0
            Some((Token::String(s) | Token::RawString(s), span)) => {
340
0
                let s = s.clone();
341
0
                let span = *span;
342
0
                self.tokens.advance();
343
0
                Ok(Expr::new(ExprKind::Literal(Literal::String(s)), span))
344
            }
345
0
            Some((Token::Bool(b), span)) => {
346
0
                let b = *b;
347
0
                let span = *span;
348
0
                self.tokens.advance();
349
0
                Ok(Expr::new(ExprKind::Literal(Literal::Bool(b)), span))
350
            }
351
390
            Some((Token::Identifier(name), span)) => {
352
390
                let name = name.clone();
353
390
                let span = *span;
354
390
                self.tokens.advance();
355
390
                Ok(Expr::new(ExprKind::Identifier(name), span))
356
            }
357
1
            Some((Token::If, _)) => Ok(self.parse_if_recovery()),
358
1
            Some((Token::Let, _)) => Ok(self.parse_let_recovery()),
359
1
            Some((Token::Fun, _)) => Ok(self.parse_function_recovery()),
360
0
            Some((Token::LeftBracket, _)) => Ok(self.parse_list_recovery()),
361
49
            Some((Token::LeftParen, _)) => self.parse_paren_recovery(),
362
206
            Some((token, _span)) => {
363
206
                let token_clone = token.clone();
364
365
                // Special handling for binary operators in prefix position
366
206
                if token.is_binary_op() {
367
164
                    self.tokens.advance(); // Consume the misplaced operator
368
164
                    self.record_error(
369
164
                        format!("Unexpected operator: {token_clone:?}"),
370
164
                        Some("An expression was expected here, not an operator".to_string()),
371
                    );
372
                    // Try to parse what comes after the misplaced operator
373
164
                    return self.parse_prefix_recovery();
374
42
                }
375
376
42
                self.tokens.advance(); // Advance before recording error
377
42
                self.record_error(
378
42
                    format!("Unexpected token: {token_clone:?}"),
379
42
                    Some("Expected an expression".to_string()),
380
                );
381
                // Try to recover
382
42
                self.synchronize();
383
42
                Ok(self.create_ghost_node("Unexpected token"))
384
            }
385
            None => {
386
79
                self.record_error(
387
79
                    "Unexpected end of input".to_string(),
388
79
                    Some("Add more code to complete the expression".to_string()),
389
                );
390
79
                Ok(self.create_ghost_node("Unexpected EOF"))
391
            }
392
        };
393
394
947
        self.recursion_depth -= 1;
395
947
        result
396
1.11k
    }
397
398
1
    fn parse_if_recovery(&mut self) -> Expr {
399
1
        let start_span = self.expect_or_recover(&Token::If);
400
401
1
        let condition = if let Ok(expr) = self.parse_expr_recovery() {
402
1
            Box::new(expr)
403
        } else {
404
0
            self.record_error(
405
0
                "Missing condition in if expression".to_string(),
406
0
                Some("Add a condition after 'if'".to_string()),
407
            );
408
0
            Box::new(self.create_ghost_node("Missing condition"))
409
        };
410
411
1
        let _ = self.expect_or_recover(&Token::LeftBrace);
412
1
        let then_branch = Box::new(self.parse_block_recovery());
413
414
1
        let else_branch = if matches!(self.tokens.peek(), Some((Token::Else, _))) {
415
0
            self.tokens.advance();
416
0
            let _ = self.expect_or_recover(&Token::LeftBrace);
417
0
            Some(Box::new(self.parse_block_recovery()))
418
        } else {
419
1
            None
420
        };
421
422
1
        let span = if let Some(
ref else_br0
) = else_branch {
423
0
            start_span.merge(else_br.span)
424
        } else {
425
1
            start_span.merge(then_branch.span)
426
        };
427
428
1
        Expr::new(
429
1
            ExprKind::If {
430
1
                condition,
431
1
                then_branch,
432
1
                else_branch,
433
1
            },
434
1
            span,
435
        )
436
1
    }
437
438
1
    fn parse_let_recovery(&mut self) -> Expr {
439
1
        let start_span = self.expect_or_recover(&Token::Let);
440
441
1
        let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
442
1
            name
443
        } else {
444
0
            self.record_error(
445
0
                "Expected identifier after 'let'".to_string(),
446
0
                Some("Add a variable name".to_string()),
447
            );
448
0
            format!("_ghost_{}", self.ghost_node_count)
449
        };
450
451
1
        let _ = self.expect_or_recover(&Token::Equal);
452
453
1
        let value = if let Ok(expr) = self.parse_expr_recovery() {
454
1
            Box::new(expr)
455
        } else {
456
0
            self.record_error(
457
0
                "Missing value in let binding".to_string(),
458
0
                Some("Add a value after '='".to_string()),
459
            );
460
0
            Box::new(self.create_ghost_node("Missing value"))
461
        };
462
463
1
        let body = if matches!(self.tokens.peek(), Some((Token::In, _))) {
464
0
            self.tokens.advance();
465
0
            match self.parse_expr_recovery() {
466
0
                Ok(expr) => Box::new(expr),
467
0
                Err(_) => Box::new(self.create_ghost_node("Missing body")),
468
            }
469
        } else {
470
1
            Box::new(Expr::new(ExprKind::Literal(Literal::Unit), value.span))
471
        };
472
473
1
        let span = start_span.merge(body.span);
474
1
        Expr::new(
475
1
            ExprKind::Let {
476
1
                name,
477
1
                type_annotation: None,
478
1
                value,
479
1
                body,
480
1
                is_mutable: false,
481
1
            },
482
1
            span,
483
        )
484
1
    }
485
486
1
    fn parse_function_recovery(&mut self) -> Expr {
487
1
        let start_span = self.expect_or_recover(&Token::Fun);
488
489
1
        let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
490
1
            name
491
        } else {
492
0
            self.record_error(
493
0
                "Expected function name".to_string(),
494
0
                Some("Add a function name after 'fun'".to_string()),
495
            );
496
0
            format!("_ghost_fn_{}", self.ghost_node_count)
497
        };
498
499
1
        let _ = self.expect_or_recover(&Token::LeftParen);
500
1
        let params = self.parse_params_recovery();
501
1
        let _ = self.expect_or_recover(&Token::RightParen);
502
503
1
        let return_type = if matches!(self.tokens.peek(), Some((Token::Arrow, _))) {
504
0
            self.tokens.advance();
505
0
            Some(self.parse_type_recovery())
506
        } else {
507
1
            None
508
        };
509
510
1
        let _ = self.expect_or_recover(&Token::LeftBrace);
511
1
        let body = Box::new(self.parse_block_recovery());
512
513
1
        let span = start_span.merge(body.span);
514
1
        Expr::new(
515
1
            ExprKind::Function {
516
1
                name,
517
1
                type_params: vec![],
518
1
                params,
519
1
                return_type,
520
1
                body,
521
1
                is_async: false,
522
1
                is_pub: false,
523
1
            },
524
1
            span,
525
        )
526
1
    }
527
528
2
    fn parse_block_recovery(&mut self) -> Expr {
529
2
        let mut exprs = Vec::new();
530
2
        let start_span = self
531
2
            .tokens
532
2
            .peek()
533
2
            .map_or(Span::new(0, 0), |(_, span)| *span);
534
535
4
        while !
matches!2
(self.tokens.peek(), Some((Token::RightBrace, _)) | None) {
536
2
            if let Ok(expr) = self.parse_expr_recovery() {
537
2
                exprs.push(expr);
538
2
            } else {
539
                // Try to recover by finding next statement
540
0
                self.synchronize();
541
0
                if self.recovery_mode {
542
0
                    exprs.push(self.create_ghost_node("Recovery statement"));
543
0
                }
544
            }
545
546
            // Optional semicolon
547
2
            if matches!(self.tokens.peek(), Some((Token::Semicolon, _))) {
548
0
                self.tokens.advance();
549
2
            }
550
        }
551
552
2
        let _ = self.expect_or_recover(&Token::RightBrace);
553
554
2
        let span = if let Some(
last1
) = exprs.last() {
555
1
            start_span.merge(last.span)
556
        } else {
557
1
            start_span
558
        };
559
560
2
        Expr::new(ExprKind::Block(exprs), span)
561
2
    }
562
563
0
    fn parse_list_recovery(&mut self) -> Expr {
564
0
        let start_span = self.expect_or_recover(&Token::LeftBracket);
565
0
        let mut elements = Vec::new();
566
567
0
        while !matches!(self.tokens.peek(), Some((Token::RightBracket, _))) {
568
0
            match self.parse_expr_recovery() {
569
0
                Ok(expr) => elements.push(expr),
570
0
                Err(_) => {
571
0
                    self.synchronize_to(&[Token::Comma, Token::RightBracket]);
572
0
                }
573
            }
574
575
0
            if matches!(self.tokens.peek(), Some((Token::Comma, _))) {
576
0
                self.tokens.advance();
577
0
            } else {
578
0
                break;
579
            }
580
        }
581
582
0
        let end_span = self.expect_or_recover(&Token::RightBracket);
583
0
        let span = start_span.merge(end_span);
584
585
0
        Expr::new(ExprKind::List(elements), span)
586
0
    }
587
588
49
    fn parse_paren_recovery(&mut self) -> Result<Expr> {
589
49
        self.tokens.advance(); // consume (
590
49
        let expr = self.parse_expr_recovery()
?0
;
591
49
        let _ = self.expect_or_recover(&Token::RightParen);
592
49
        Ok(expr)
593
49
    }
594
595
1
    fn parse_params_recovery(&mut self) -> Vec<Param> {
596
1
        let mut params = Vec::new();
597
598
1
        if matches!(self.tokens.peek(), Some((Token::RightParen, _))) {
599
0
            return params;
600
1
        }
601
602
        loop {
603
1
            let Some((Token::Identifier(name), name_span)) = self.tokens.advance() else {
604
0
                self.record_error(
605
0
                    "Expected parameter name".to_string(),
606
0
                    Some("Add a parameter name".to_string()),
607
                );
608
0
                self.synchronize_to(&[Token::Comma, Token::RightParen]);
609
0
                continue;
610
            };
611
612
1
            let ty = if 
matches!0
(self.tokens.peek(), Some((Token::Colon, _))) {
613
1
                self.tokens.advance();
614
1
                self.parse_type_recovery()
615
            } else {
616
                // Default to inferred type
617
0
                Type {
618
0
                    kind: TypeKind::Named("_".to_string()),
619
0
                    span: name_span,
620
0
                }
621
            };
622
623
1
            params.push(Param {
624
1
                pattern: Pattern::Identifier(name),
625
1
                ty,
626
1
                span: name_span,
627
1
                is_mutable: false,
628
1
                default_value: None,
629
1
            });
630
631
1
            match self.tokens.peek() {
632
0
                Some((Token::Comma, _)) => {
633
0
                    self.tokens.advance();
634
0
                }
635
1
                Some((Token::RightParen, _)) => break,
636
                _ => {
637
0
                    self.record_error("Expected ',' or ')' in parameter list".to_string(), None);
638
0
                    break;
639
                }
640
            }
641
        }
642
643
1
        params
644
1
    }
645
646
1
    fn parse_type_recovery(&mut self) -> Type {
647
1
        let (base_type, span) = if let Some((Token::Identifier(name), span)) = self.tokens.advance()
648
        {
649
1
            (TypeKind::Named(name), span)
650
        } else {
651
0
            self.record_error(
652
0
                "Expected type".to_string(),
653
0
                Some("Add a type annotation".to_string()),
654
            );
655
0
            (TypeKind::Named("_".to_string()), Span::new(0, 0))
656
        };
657
658
1
        let kind = if matches!(self.tokens.peek(), Some((Token::Question, _))) {
659
0
            self.tokens.advance();
660
0
            TypeKind::Optional(Box::new(Type {
661
0
                kind: base_type,
662
0
                span,
663
0
            }))
664
        } else {
665
1
            base_type
666
        };
667
668
1
        Type { kind, span }
669
1
    }
670
671
    /// Create a ghost node for error recovery
672
121
    fn create_ghost_node(&mut self, reason: &str) -> Expr {
673
121
        self.ghost_node_count += 1;
674
121
        Expr::new(
675
121
            ExprKind::Identifier(format!(
676
121
                "_ghost_{}_{}",
677
121
                self.ghost_node_count,
678
121
                reason.replace(' ', "_")
679
121
            )),
680
121
            Span::new(0, 0),
681
        )
682
121
    }
683
684
    /// Record an error for later reporting
685
336
    fn record_error(&mut self, message: String, hint: Option<String>) {
686
336
        let span = self.tokens.peek().map_or(Span::new(0, 0), |(_, s)| *s);
687
688
336
        let mut error = ParseError::new(message, span);
689
336
        if let Some(hint) = hint {
690
336
            error = error.with_hint(hint);
691
336
        
}0
692
336
        if let Some((
found_token224
, _)) = self.tokens.peek() {
693
224
            error.found = Some(found_token.clone());
694
224
        
}112
695
336
        self.errors.push(error);
696
336
    }
697
698
    /// Expect a token or record error and try to recover
699
59
    fn expect_or_recover(&mut self, expected: &Token) -> Span {
700
59
        match self.tokens.peek() {
701
39
            Some((token, span)) if token == expecte
d8
=> {
702
8
                let span = *span;
703
8
                self.tokens.advance();
704
8
                span
705
            }
706
            _ => {
707
51
                self.record_error(
708
51
                    format!("Expected {expected:?}"),
709
51
                    Some(format!("Add '{expected:?}' here")),
710
                );
711
51
                self.recovery_mode = true;
712
51
                Span::new(0, 0)
713
            }
714
        }
715
59
    }
716
717
    /// Synchronize to a known recovery point
718
42
    fn synchronize(&mut self) {
719
42
        self.recovery_mode = true;
720
721
        // Synchronization tokens - statement boundaries
722
42
        let sync_tokens = [
723
42
            Token::Semicolon,
724
42
            Token::RightBrace,
725
42
            Token::Fun,
726
42
            Token::Let,
727
42
            Token::If,
728
42
            Token::For,
729
42
            Token::Match,
730
42
        ];
731
732
42
        let mut tokens_consumed = 0;
733
        const MAX_SYNC_TOKENS: usize = 100; // Prevent infinite loops
734
735
388
        while let Some((
token346
, _)) = self.tokens.peek() {
736
346
            if tokens_consumed >= MAX_SYNC_TOKENS {
737
                // Force exit to prevent infinite loop
738
0
                break;
739
346
            }
740
741
2.42k
            if 
sync_tokens.iter()346
.
any346
(|t| t == token) {
742
0
                if matches!(token, Token::Semicolon) {
743
0
                    self.tokens.advance(); // consume semicolon
744
0
                }
745
0
                break;
746
346
            }
747
346
            self.tokens.advance();
748
346
            tokens_consumed += 1;
749
        }
750
751
42
        self.recovery_mode = false;
752
42
    }
753
754
    /// Synchronize to specific tokens
755
0
    fn synchronize_to(&mut self, targets: &[Token]) {
756
0
        let mut tokens_consumed = 0;
757
        const MAX_SYNC_TOKENS: usize = 100; // Prevent infinite loops
758
759
0
        while let Some((token, _)) = self.tokens.peek() {
760
0
            if tokens_consumed >= MAX_SYNC_TOKENS {
761
                // Force exit to prevent infinite loop
762
0
                break;
763
0
            }
764
765
0
            if targets.iter().any(|t| t == token) {
766
0
                break;
767
0
            }
768
0
            self.tokens.advance();
769
0
            tokens_consumed += 1;
770
        }
771
0
    }
772
773
410
    fn precedence(token: &Token) -> i32 {
774
410
        match token {
775
0
            Token::OrOr => 1,
776
0
            Token::AndAnd => 2,
777
0
            Token::Pipe => 3,
778
0
            Token::Caret => 4,
779
1
            Token::Ampersand => 5,
780
0
            Token::EqualEqual | Token::NotEqual => 6,
781
2
            Token::Less | Token::LessEqual | Token::Greater | Token::GreaterEqual => 7,
782
0
            Token::LeftShift => 8,
783
353
            Token::Plus | Token::Minus => 9,
784
54
            Token::Star | Token::Slash | Token::Percent => 10,
785
0
            Token::Power => 11,
786
0
            _ => 0,
787
        }
788
410
    }
789
790
317
    fn token_to_binary_op(token: &Token) -> Result<BinaryOp> {
791
317
        Ok(match token {
792
53
            Token::Plus => BinaryOp::Add,
793
207
            Token::Minus => BinaryOp::Subtract,
794
26
            Token::Star => BinaryOp::Multiply,
795
28
            Token::Slash => BinaryOp::Divide,
796
0
            Token::Percent => BinaryOp::Modulo,
797
0
            Token::Power => BinaryOp::Power,
798
0
            Token::EqualEqual => BinaryOp::Equal,
799
0
            Token::NotEqual => BinaryOp::NotEqual,
800
1
            Token::Less => BinaryOp::Less,
801
0
            Token::LessEqual => BinaryOp::LessEqual,
802
1
            Token::Greater => BinaryOp::Greater,
803
0
            Token::GreaterEqual => BinaryOp::GreaterEqual,
804
0
            Token::AndAnd => BinaryOp::And,
805
0
            Token::OrOr => BinaryOp::Or,
806
1
            Token::Ampersand => BinaryOp::BitwiseAnd,
807
0
            Token::Pipe => BinaryOp::BitwiseOr,
808
0
            Token::Caret => BinaryOp::BitwiseXor,
809
0
            Token::LeftShift => BinaryOp::LeftShift,
810
0
            _ => anyhow::bail!("Not a binary operator: {:?}", token),
811
        })
812
317
    }
813
}
814
815
#[cfg(test)]
816
#[allow(clippy::unwrap_used)]
817
mod tests {
818
    use super::*;
819
820
    #[test]
821
1
    fn test_recovery_missing_operand() {
822
1
        let mut parser = RecoveryParser::new("1 +");
823
1
        let result = parser.parse_with_recovery();
824
825
1
        assert!(result.ast.is_some());
826
1
        assert!(!result.errors.is_empty());
827
1
        assert!(result.partial_ast);
828
1
    }
829
830
    #[test]
831
1
    fn test_recovery_missing_paren() {
832
1
        let mut parser = RecoveryParser::new("(1 + 2");
833
1
        let result = parser.parse_with_recovery();
834
835
1
        assert!(result.ast.is_some());
836
1
        assert!(!result.errors.is_empty());
837
1
    }
838
839
    #[test]
840
1
    fn test_recovery_invalid_token() {
841
1
        let mut parser = RecoveryParser::new("let x = @ + 1");
842
1
        let result = parser.parse_with_recovery();
843
844
1
        assert!(result.ast.is_some());
845
1
        assert!(!result.errors.is_empty());
846
1
    }
847
848
    #[test]
849
1
    fn test_recovery_incomplete_if() {
850
1
        let mut parser = RecoveryParser::new("if x > 0 { print(x)");
851
1
        let result = parser.parse_with_recovery();
852
853
1
        assert!(result.ast.is_some());
854
1
        assert!(!result.errors.is_empty());
855
1
        assert!(result.partial_ast);
856
1
    }
857
858
    #[test]
859
1
    fn test_recovery_missing_function_body() {
860
1
        let mut parser = RecoveryParser::new("fun foo(x: i32)");
861
1
        let result = parser.parse_with_recovery();
862
863
1
        assert!(result.ast.is_some());
864
1
        assert!(!result.errors.is_empty());
865
1
    }
866
867
    #[test]
868
1
    fn test_no_errors_on_valid_code() {
869
1
        let mut parser = RecoveryParser::new("1 + 2 * 3");
870
1
        let result = parser.parse_with_recovery();
871
872
1
        assert!(result.ast.is_some());
873
1
        assert!(result.errors.is_empty());
874
1
        assert!(!result.partial_ast);
875
1
    }
876
}