jetcrab\parser/
recovery.rs1use crate::lexer::{Token, TokenKind};
2use crate::parser::error::ParserError;
3use crate::vm::types::ErrorCount;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum RecoveryStrategy {
7 SkipUntil(Vec<String>),
8 SkipUntilStatement,
9 SkipUntilBlock,
10 SkipUntilFunction,
11 SkipUntilClass,
12 SkipUntilModule,
13 InsertToken(String),
14 ReplaceToken(String),
15 DeleteToken,
16 NoRecovery,
17}
18
19#[derive(Debug, Clone)]
20pub struct RecoveryContext {
21 pub current_token: Option<Token>,
22 pub previous_token: Option<Token>,
23 pub recovery_tokens: Vec<String>,
24 pub context: ParsingContext,
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub enum ParsingContext {
29 TopLevel,
30 Statement,
31 Block,
32 Function,
33 Class,
34 Module,
35 Expression,
36 Declaration,
37}
38
39impl RecoveryContext {
40 pub fn new(
41 current_token: Option<Token>,
42 previous_token: Option<Token>,
43 context: ParsingContext,
44 ) -> Self {
45 Self {
46 current_token,
47 previous_token,
48 recovery_tokens: Vec::new(),
49 context,
50 }
51 }
52
53 pub fn with_recovery_tokens(mut self, tokens: Vec<String>) -> Self {
54 self.recovery_tokens = tokens;
55 self
56 }
57
58 pub fn determine_strategy(&self) -> RecoveryStrategy {
59 match &self.context {
60 ParsingContext::TopLevel => {
61 if let Some(token) = &self.current_token {
62 match token.kind {
63 TokenKind::Semicolon | TokenKind::RightBrace => {
64 RecoveryStrategy::SkipUntil(vec![";".to_string(), "}".to_string()])
65 }
66 _ => RecoveryStrategy::SkipUntilStatement,
67 }
68 } else {
69 RecoveryStrategy::NoRecovery
70 }
71 }
72
73 ParsingContext::Statement => {
74 RecoveryStrategy::SkipUntil(vec![";".to_string(), "}".to_string(), ")".to_string()])
75 }
76
77 ParsingContext::Block => RecoveryStrategy::SkipUntil(vec!["}".to_string()]),
78
79 ParsingContext::Function => {
80 RecoveryStrategy::SkipUntil(vec!["}".to_string(), ";".to_string()])
81 }
82
83 ParsingContext::Class => RecoveryStrategy::SkipUntil(vec!["}".to_string()]),
84
85 ParsingContext::Module => RecoveryStrategy::SkipUntil(vec![
86 "}".to_string(),
87 "import".to_string(),
88 "export".to_string(),
89 ]),
90
91 ParsingContext::Expression => RecoveryStrategy::SkipUntil(vec![
92 ";".to_string(),
93 ",".to_string(),
94 ")".to_string(),
95 "]".to_string(),
96 "}".to_string(),
97 ]),
98
99 ParsingContext::Declaration => {
100 RecoveryStrategy::SkipUntil(vec![";".to_string(), "}".to_string()])
101 }
102 }
103 }
104
105 pub fn is_recovery_token(&self, token: &Token) -> bool {
106 let token_str = format!("{:?}", token.kind);
107 self.recovery_tokens.iter().any(|t| token_str.contains(t))
108 }
109
110 pub fn current_position(&self) -> Option<crate::ast::Position> {
111 self.current_token.as_ref().map(|t| crate::ast::Position {
112 line: t.start().line,
113 column: t.start().column,
114 })
115 }
116}
117
118#[derive(Debug)]
119pub struct ErrorRecovery {
120 max_errors: ErrorCount,
121 error_count: ErrorCount,
122 errors: Vec<ParserError>,
123}
124
125impl ErrorRecovery {
126 pub fn new(max_errors: usize) -> Self {
127 Self {
128 max_errors: ErrorCount::new(max_errors),
129 error_count: ErrorCount::new(0),
130 errors: Vec::new(),
131 }
132 }
133
134 pub fn can_recover(&self) -> bool {
135 self.error_count.as_usize() < self.max_errors.as_usize()
136 }
137
138 pub fn add_error(&mut self, error: ParserError) {
139 self.errors.push(error);
140 self.error_count.increment();
141 }
142
143 pub fn errors(&self) -> &[ParserError] {
144 &self.errors
145 }
146
147 pub fn clear_errors(&mut self) {
148 self.errors.clear();
149 self.error_count.reset();
150 }
151
152 pub fn error_count(&self) -> usize {
153 self.error_count.as_usize()
154 }
155
156 pub fn has_errors(&self) -> bool {
157 !self.errors.is_empty()
158 }
159}
160
161impl Default for ErrorRecovery {
162 fn default() -> Self {
163 Self::new(100)
164 }
165}