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/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
0
    pub fn new(message: String, span: Span) -> Self {
105
0
        Self {
106
0
            message,
107
0
            span,
108
0
            recovery_hint: None,
109
0
            expected: Vec::new(),
110
0
            found: None,
111
0
            severity: ErrorSeverity::Error,
112
0
            error_code: ErrorCode::InvalidSyntax,
113
0
            context: Vec::new(),
114
0
        }
115
0
    }
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
0
    pub fn with_hint(mut self, hint: String) -> Self {
159
0
        self.recovery_hint = Some(hint);
160
0
        self
161
0
    }
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
0
    pub fn new(input: &'a str) -> Self {
198
0
        Self {
199
0
            tokens: TokenStream::new(input),
200
0
            errors: Vec::new(),
201
0
            recovery_mode: false,
202
0
            ghost_node_count: 0,
203
0
            recursion_depth: 0,
204
0
        }
205
0
    }
206
207
    /// Parse with error recovery, always producing some AST
208
0
    pub fn parse_with_recovery(&mut self) -> ParseResult {
209
0
        match self.parse_expr_recovery() {
210
0
            Ok(ast) => ParseResult {
211
0
                ast: Some(ast),
212
0
                errors: self.errors.clone(),
213
0
                partial_ast: !self.errors.is_empty(),
214
0
            },
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
0
    }
238
239
0
    fn parse_expr_recovery(&mut self) -> Result<Expr> {
240
0
        self.parse_expr_with_precedence_recovery(0)
241
0
    }
242
243
0
    fn parse_expr_with_precedence_recovery(&mut self, min_prec: i32) -> Result<Expr> {
244
0
        let mut left = self.parse_prefix_recovery()?;
245
        const MAX_ITERATIONS: usize = 1000; // Prevent infinite loops
246
0
        let mut iteration_count = 0;
247
248
        loop {
249
0
            iteration_count += 1;
250
0
            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
0
            }
257
258
0
            let Some((token, _)) = self.tokens.peek() else {
259
0
                break;
260
            };
261
262
0
            if !token.is_binary_op() {
263
0
                break;
264
0
            }
265
266
0
            let token_clone = token.clone();
267
0
            let prec = Self::precedence(&token_clone);
268
0
            if prec < min_prec {
269
0
                break;
270
0
            }
271
272
0
            match self.tokens.advance() {
273
0
                Some((op_token, _op_span)) => {
274
0
                    let op = match Self::token_to_binary_op(&op_token) {
275
0
                        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
0
                    let right = if let Ok(expr) = self.parse_expr_with_precedence_recovery(prec + 1)
284
                    {
285
0
                        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
0
                    let span = left.span.merge(right.span);
297
0
                    left = Expr::new(
298
0
                        ExprKind::Binary {
299
0
                            left: Box::new(left),
300
0
                            op,
301
0
                            right: Box::new(right),
302
0
                        },
303
0
                        span,
304
                    );
305
                }
306
0
                None => break,
307
            }
308
        }
309
310
0
        Ok(left)
311
0
    }
312
313
0
    fn parse_prefix_recovery(&mut self) -> Result<Expr> {
314
        const MAX_RECURSION_DEPTH: usize = 100;
315
316
0
        self.recursion_depth += 1;
317
0
        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
0
        }
325
326
0
        let result = match self.tokens.peek() {
327
0
            Some((Token::Integer(n), span)) => {
328
0
                let n = *n;
329
0
                let span = *span;
330
0
                self.tokens.advance();
331
0
                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
0
            Some((Token::Identifier(name), span)) => {
352
0
                let name = name.clone();
353
0
                let span = *span;
354
0
                self.tokens.advance();
355
0
                Ok(Expr::new(ExprKind::Identifier(name), span))
356
            }
357
0
            Some((Token::If, _)) => Ok(self.parse_if_recovery()),
358
0
            Some((Token::Let, _)) => Ok(self.parse_let_recovery()),
359
0
            Some((Token::Fun, _)) => Ok(self.parse_function_recovery()),
360
0
            Some((Token::LeftBracket, _)) => Ok(self.parse_list_recovery()),
361
0
            Some((Token::LeftParen, _)) => self.parse_paren_recovery(),
362
0
            Some((token, _span)) => {
363
0
                let token_clone = token.clone();
364
365
                // Special handling for binary operators in prefix position
366
0
                if token.is_binary_op() {
367
0
                    self.tokens.advance(); // Consume the misplaced operator
368
0
                    self.record_error(
369
0
                        format!("Unexpected operator: {token_clone:?}"),
370
0
                        Some("An expression was expected here, not an operator".to_string()),
371
                    );
372
                    // Try to parse what comes after the misplaced operator
373
0
                    return self.parse_prefix_recovery();
374
0
                }
375
376
0
                self.tokens.advance(); // Advance before recording error
377
0
                self.record_error(
378
0
                    format!("Unexpected token: {token_clone:?}"),
379
0
                    Some("Expected an expression".to_string()),
380
                );
381
                // Try to recover
382
0
                self.synchronize();
383
0
                Ok(self.create_ghost_node("Unexpected token"))
384
            }
385
            None => {
386
0
                self.record_error(
387
0
                    "Unexpected end of input".to_string(),
388
0
                    Some("Add more code to complete the expression".to_string()),
389
                );
390
0
                Ok(self.create_ghost_node("Unexpected EOF"))
391
            }
392
        };
393
394
0
        self.recursion_depth -= 1;
395
0
        result
396
0
    }
397
398
0
    fn parse_if_recovery(&mut self) -> Expr {
399
0
        let start_span = self.expect_or_recover(&Token::If);
400
401
0
        let condition = if let Ok(expr) = self.parse_expr_recovery() {
402
0
            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
0
        let _ = self.expect_or_recover(&Token::LeftBrace);
412
0
        let then_branch = Box::new(self.parse_block_recovery());
413
414
0
        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
0
            None
420
        };
421
422
0
        let span = if let Some(ref else_br) = else_branch {
423
0
            start_span.merge(else_br.span)
424
        } else {
425
0
            start_span.merge(then_branch.span)
426
        };
427
428
0
        Expr::new(
429
0
            ExprKind::If {
430
0
                condition,
431
0
                then_branch,
432
0
                else_branch,
433
0
            },
434
0
            span,
435
        )
436
0
    }
437
438
0
    fn parse_let_recovery(&mut self) -> Expr {
439
0
        let start_span = self.expect_or_recover(&Token::Let);
440
441
0
        let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
442
0
            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
0
        let _ = self.expect_or_recover(&Token::Equal);
452
453
0
        let value = if let Ok(expr) = self.parse_expr_recovery() {
454
0
            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
0
        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
0
            Box::new(Expr::new(ExprKind::Literal(Literal::Unit), value.span))
471
        };
472
473
0
        let span = start_span.merge(body.span);
474
0
        Expr::new(
475
0
            ExprKind::Let {
476
0
                name,
477
0
                type_annotation: None,
478
0
                value,
479
0
                body,
480
0
                is_mutable: false,
481
0
            },
482
0
            span,
483
        )
484
0
    }
485
486
0
    fn parse_function_recovery(&mut self) -> Expr {
487
0
        let start_span = self.expect_or_recover(&Token::Fun);
488
489
0
        let name = if let Some((Token::Identifier(name), _)) = self.tokens.advance() {
490
0
            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
0
        let _ = self.expect_or_recover(&Token::LeftParen);
500
0
        let params = self.parse_params_recovery();
501
0
        let _ = self.expect_or_recover(&Token::RightParen);
502
503
0
        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
0
            None
508
        };
509
510
0
        let _ = self.expect_or_recover(&Token::LeftBrace);
511
0
        let body = Box::new(self.parse_block_recovery());
512
513
0
        let span = start_span.merge(body.span);
514
0
        Expr::new(
515
0
            ExprKind::Function {
516
0
                name,
517
0
                type_params: vec![],
518
0
                params,
519
0
                return_type,
520
0
                body,
521
0
                is_async: false,
522
0
                is_pub: false,
523
0
            },
524
0
            span,
525
        )
526
0
    }
527
528
0
    fn parse_block_recovery(&mut self) -> Expr {
529
0
        let mut exprs = Vec::new();
530
0
        let start_span = self
531
0
            .tokens
532
0
            .peek()
533
0
            .map_or(Span::new(0, 0), |(_, span)| *span);
534
535
0
        while !matches!(self.tokens.peek(), Some((Token::RightBrace, _)) | None) {
536
0
            if let Ok(expr) = self.parse_expr_recovery() {
537
0
                exprs.push(expr);
538
0
            } 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
0
            if matches!(self.tokens.peek(), Some((Token::Semicolon, _))) {
548
0
                self.tokens.advance();
549
0
            }
550
        }
551
552
0
        let _ = self.expect_or_recover(&Token::RightBrace);
553
554
0
        let span = if let Some(last) = exprs.last() {
555
0
            start_span.merge(last.span)
556
        } else {
557
0
            start_span
558
        };
559
560
0
        Expr::new(ExprKind::Block(exprs), span)
561
0
    }
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
0
    fn parse_paren_recovery(&mut self) -> Result<Expr> {
589
0
        self.tokens.advance(); // consume (
590
0
        let expr = self.parse_expr_recovery()?;
591
0
        let _ = self.expect_or_recover(&Token::RightParen);
592
0
        Ok(expr)
593
0
    }
594
595
0
    fn parse_params_recovery(&mut self) -> Vec<Param> {
596
0
        let mut params = Vec::new();
597
598
0
        if matches!(self.tokens.peek(), Some((Token::RightParen, _))) {
599
0
            return params;
600
0
        }
601
602
        loop {
603
0
            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
0
            let ty = if matches!(self.tokens.peek(), Some((Token::Colon, _))) {
613
0
                self.tokens.advance();
614
0
                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
0
            params.push(Param {
624
0
                pattern: Pattern::Identifier(name),
625
0
                ty,
626
0
                span: name_span,
627
0
                is_mutable: false,
628
0
                default_value: None,
629
0
            });
630
631
0
            match self.tokens.peek() {
632
0
                Some((Token::Comma, _)) => {
633
0
                    self.tokens.advance();
634
0
                }
635
0
                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
0
        params
644
0
    }
645
646
0
    fn parse_type_recovery(&mut self) -> Type {
647
0
        let (base_type, span) = if let Some((Token::Identifier(name), span)) = self.tokens.advance()
648
        {
649
0
            (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
0
        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
0
            base_type
666
        };
667
668
0
        Type { kind, span }
669
0
    }
670
671
    /// Create a ghost node for error recovery
672
0
    fn create_ghost_node(&mut self, reason: &str) -> Expr {
673
0
        self.ghost_node_count += 1;
674
0
        Expr::new(
675
0
            ExprKind::Identifier(format!(
676
0
                "_ghost_{}_{}",
677
0
                self.ghost_node_count,
678
0
                reason.replace(' ', "_")
679
0
            )),
680
0
            Span::new(0, 0),
681
        )
682
0
    }
683
684
    /// Record an error for later reporting
685
0
    fn record_error(&mut self, message: String, hint: Option<String>) {
686
0
        let span = self.tokens.peek().map_or(Span::new(0, 0), |(_, s)| *s);
687
688
0
        let mut error = ParseError::new(message, span);
689
0
        if let Some(hint) = hint {
690
0
            error = error.with_hint(hint);
691
0
        }
692
0
        if let Some((found_token, _)) = self.tokens.peek() {
693
0
            error.found = Some(found_token.clone());
694
0
        }
695
0
        self.errors.push(error);
696
0
    }
697
698
    /// Expect a token or record error and try to recover
699
0
    fn expect_or_recover(&mut self, expected: &Token) -> Span {
700
0
        match self.tokens.peek() {
701
0
            Some((token, span)) if token == expected => {
702
0
                let span = *span;
703
0
                self.tokens.advance();
704
0
                span
705
            }
706
            _ => {
707
0
                self.record_error(
708
0
                    format!("Expected {expected:?}"),
709
0
                    Some(format!("Add '{expected:?}' here")),
710
                );
711
0
                self.recovery_mode = true;
712
0
                Span::new(0, 0)
713
            }
714
        }
715
0
    }
716
717
    /// Synchronize to a known recovery point
718
0
    fn synchronize(&mut self) {
719
0
        self.recovery_mode = true;
720
721
        // Synchronization tokens - statement boundaries
722
0
        let sync_tokens = [
723
0
            Token::Semicolon,
724
0
            Token::RightBrace,
725
0
            Token::Fun,
726
0
            Token::Let,
727
0
            Token::If,
728
0
            Token::For,
729
0
            Token::Match,
730
0
        ];
731
732
0
        let mut tokens_consumed = 0;
733
        const MAX_SYNC_TOKENS: usize = 100; // Prevent infinite loops
734
735
0
        while let Some((token, _)) = self.tokens.peek() {
736
0
            if tokens_consumed >= MAX_SYNC_TOKENS {
737
                // Force exit to prevent infinite loop
738
0
                break;
739
0
            }
740
741
0
            if sync_tokens.iter().any(|t| t == token) {
742
0
                if matches!(token, Token::Semicolon) {
743
0
                    self.tokens.advance(); // consume semicolon
744
0
                }
745
0
                break;
746
0
            }
747
0
            self.tokens.advance();
748
0
            tokens_consumed += 1;
749
        }
750
751
0
        self.recovery_mode = false;
752
0
    }
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
0
    fn precedence(token: &Token) -> i32 {
774
0
        match token {
775
0
            Token::OrOr => 1,
776
0
            Token::AndAnd => 2,
777
0
            Token::Pipe => 3,
778
0
            Token::Caret => 4,
779
0
            Token::Ampersand => 5,
780
0
            Token::EqualEqual | Token::NotEqual => 6,
781
0
            Token::Less | Token::LessEqual | Token::Greater | Token::GreaterEqual => 7,
782
0
            Token::LeftShift => 8,
783
0
            Token::Plus | Token::Minus => 9,
784
0
            Token::Star | Token::Slash | Token::Percent => 10,
785
0
            Token::Power => 11,
786
0
            _ => 0,
787
        }
788
0
    }
789
790
0
    fn token_to_binary_op(token: &Token) -> Result<BinaryOp> {
791
0
        Ok(match token {
792
0
            Token::Plus => BinaryOp::Add,
793
0
            Token::Minus => BinaryOp::Subtract,
794
0
            Token::Star => BinaryOp::Multiply,
795
0
            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
0
            Token::Less => BinaryOp::Less,
801
0
            Token::LessEqual => BinaryOp::LessEqual,
802
0
            Token::Greater => BinaryOp::Greater,
803
0
            Token::GreaterEqual => BinaryOp::GreaterEqual,
804
0
            Token::AndAnd => BinaryOp::And,
805
0
            Token::OrOr => BinaryOp::Or,
806
0
            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
0
    }
813
}
814
815
#[cfg(test)]
816
#[allow(clippy::unwrap_used)]
817
mod tests {
818
    use super::*;
819
820
    #[test]
821
    fn test_recovery_missing_operand() {
822
        let mut parser = RecoveryParser::new("1 +");
823
        let result = parser.parse_with_recovery();
824
825
        assert!(result.ast.is_some());
826
        assert!(!result.errors.is_empty());
827
        assert!(result.partial_ast);
828
    }
829
830
    #[test]
831
    fn test_recovery_missing_paren() {
832
        let mut parser = RecoveryParser::new("(1 + 2");
833
        let result = parser.parse_with_recovery();
834
835
        assert!(result.ast.is_some());
836
        assert!(!result.errors.is_empty());
837
    }
838
839
    #[test]
840
    fn test_recovery_invalid_token() {
841
        let mut parser = RecoveryParser::new("let x = @ + 1");
842
        let result = parser.parse_with_recovery();
843
844
        assert!(result.ast.is_some());
845
        assert!(!result.errors.is_empty());
846
    }
847
848
    #[test]
849
    fn test_recovery_incomplete_if() {
850
        let mut parser = RecoveryParser::new("if x > 0 { print(x)");
851
        let result = parser.parse_with_recovery();
852
853
        assert!(result.ast.is_some());
854
        assert!(!result.errors.is_empty());
855
        assert!(result.partial_ast);
856
    }
857
858
    #[test]
859
    fn test_recovery_missing_function_body() {
860
        let mut parser = RecoveryParser::new("fun foo(x: i32)");
861
        let result = parser.parse_with_recovery();
862
863
        assert!(result.ast.is_some());
864
        assert!(!result.errors.is_empty());
865
    }
866
867
    #[test]
868
    fn test_no_errors_on_valid_code() {
869
        let mut parser = RecoveryParser::new("1 + 2 * 3");
870
        let result = parser.parse_with_recovery();
871
872
        assert!(result.ast.is_some());
873
        assert!(result.errors.is_empty());
874
        assert!(!result.partial_ast);
875
    }
876
}