1use crate::frontend::ast::{Expr, ExprKind, Literal, Param, Span};
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct ErrorNode {
11 pub message: String,
13 pub location: SourceLocation,
15 pub context: ErrorContext,
17 pub recovery: RecoveryStrategy,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct SourceLocation {
23 pub line: usize,
24 pub column: usize,
25 pub file: Option<String>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum ErrorContext {
30 FunctionDecl {
31 name: Option<String>,
32 params: Option<Vec<Param>>,
33 body: Option<Box<Expr>>,
34 },
35 LetBinding {
36 name: Option<String>,
37 value: Option<Box<Expr>>,
38 },
39 IfExpression {
40 condition: Option<Box<Expr>>,
41 then_branch: Option<Box<Expr>>,
42 else_branch: Option<Box<Expr>>,
43 },
44 ArrayLiteral {
45 elements: Vec<Expr>,
46 error_at_index: usize,
47 },
48 BinaryOp {
49 left: Option<Box<Expr>>,
50 op: Option<String>,
51 right: Option<Box<Expr>>,
52 },
53 StructLiteral {
54 name: Option<String>,
55 fields: Vec<(String, Expr)>,
56 error_field: Option<String>,
57 },
58}
59
60#[derive(Debug, Clone, PartialEq)]
61pub enum RecoveryStrategy {
62 SkipUntilSync,
64 InsertToken(String),
66 DefaultValue,
68 PartialParse,
70 PanicMode,
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum ExprWithError {
77 Valid(Expr),
78 Error(ErrorNode),
79}
80
81impl From<Expr> for ExprWithError {
82 fn from(expr: Expr) -> Self {
83 ExprWithError::Valid(expr)
84 }
85}
86
87impl From<ErrorNode> for ExprWithError {
88 fn from(error: ErrorNode) -> Self {
89 ExprWithError::Error(error)
90 }
91}
92
93pub struct ErrorRecovery {
95 sync_tokens: Vec<String>,
97 max_errors: usize,
99 error_count: usize,
101}
102
103impl Default for ErrorRecovery {
104 fn default() -> Self {
105 Self {
106 sync_tokens: vec![
107 ";".to_string(),
108 "}".to_string(),
109 "fun".to_string(),
110 "let".to_string(),
111 "if".to_string(),
112 "for".to_string(),
113 "while".to_string(),
114 "return".to_string(),
115 "struct".to_string(),
116 "enum".to_string(),
117 ],
118 max_errors: 100,
119 error_count: 0,
120 }
121 }
122}
123
124impl ErrorRecovery {
125 #[must_use]
126 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn missing_function_name(&mut self, location: SourceLocation) -> ErrorNode {
132 self.error_count += 1;
133 ErrorNode {
134 message: "expected function name".to_string(),
135 location,
136 context: ErrorContext::FunctionDecl {
137 name: None,
138 params: None,
139 body: None,
140 },
141 recovery: RecoveryStrategy::InsertToken("error_fn".to_string()),
142 }
143 }
144
145 pub fn missing_function_params(&mut self, name: String, location: SourceLocation) -> ErrorNode {
147 self.error_count += 1;
148 ErrorNode {
149 message: "expected function parameters".to_string(),
150 location,
151 context: ErrorContext::FunctionDecl {
152 name: Some(name),
153 params: None,
154 body: None,
155 },
156 recovery: RecoveryStrategy::DefaultValue,
157 }
158 }
159
160 pub fn missing_function_body(
162 &mut self,
163 name: String,
164 params: Vec<Param>,
165 location: SourceLocation,
166 ) -> ErrorNode {
167 self.error_count += 1;
168 ErrorNode {
169 message: "expected function body".to_string(),
170 location,
171 context: ErrorContext::FunctionDecl {
172 name: Some(name),
173 params: Some(params),
174 body: None,
175 },
176 recovery: RecoveryStrategy::InsertToken("{ /* missing body */ }".to_string()),
177 }
178 }
179
180 pub fn malformed_let_binding(
182 &mut self,
183 partial_name: Option<String>,
184 partial_value: Option<Box<Expr>>,
185 location: SourceLocation,
186 ) -> ErrorNode {
187 self.error_count += 1;
188 ErrorNode {
189 message: "malformed let binding".to_string(),
190 location,
191 context: ErrorContext::LetBinding {
192 name: partial_name,
193 value: partial_value,
194 },
195 recovery: RecoveryStrategy::PartialParse,
196 }
197 }
198
199 pub fn incomplete_if_expr(
201 &mut self,
202 condition: Option<Box<Expr>>,
203 then_branch: Option<Box<Expr>>,
204 location: SourceLocation,
205 ) -> ErrorNode {
206 self.error_count += 1;
207 ErrorNode {
208 message: "incomplete if expression".to_string(),
209 location,
210 context: ErrorContext::IfExpression {
211 condition,
212 then_branch,
213 else_branch: None,
214 },
215 recovery: RecoveryStrategy::DefaultValue,
216 }
217 }
218
219 #[must_use]
221 pub fn should_continue(&self) -> bool {
222 self.error_count < self.max_errors
223 }
224
225 pub fn reset(&mut self) {
227 self.error_count = 0;
228 }
229
230 #[must_use]
232 pub fn is_sync_token(&self, token: &str) -> bool {
233 self.sync_tokens.contains(&token.to_string())
234 }
235
236 pub fn skip_until_sync<'a, I>(&self, tokens: &mut I) -> Option<String>
238 where
239 I: Iterator<Item = &'a str>,
240 {
241 for token in tokens {
242 if self.is_sync_token(token) {
243 return Some(token.to_string());
244 }
245 }
246 None
247 }
248}
249
250pub struct RecoveryRules;
252
253impl RecoveryRules {
254 #[must_use]
256 pub fn select_strategy(context: &ErrorContext) -> RecoveryStrategy {
257 match context {
258 ErrorContext::FunctionDecl { name, params, body } => {
259 if name.is_none() {
260 RecoveryStrategy::InsertToken("error_fn".to_string())
261 } else if params.is_none() {
262 RecoveryStrategy::DefaultValue
263 } else if body.is_none() {
264 RecoveryStrategy::InsertToken("{ }".to_string())
265 } else {
266 RecoveryStrategy::PartialParse
267 }
268 }
269 ErrorContext::LetBinding { .. } => RecoveryStrategy::SkipUntilSync,
270 ErrorContext::IfExpression { .. } => RecoveryStrategy::DefaultValue,
271 ErrorContext::ArrayLiteral { .. } | ErrorContext::StructLiteral { .. } => {
272 RecoveryStrategy::PartialParse
273 }
274 ErrorContext::BinaryOp { .. } => RecoveryStrategy::PanicMode,
275 }
276 }
277
278 #[must_use]
280 pub fn synthesize_ast(error: &ErrorNode) -> Expr {
281 let default_span = Span::new(0, 0);
282 match &error.context {
283 ErrorContext::FunctionDecl { .. } => {
284 Expr::new(
286 ExprKind::Lambda {
287 params: vec![],
288 body: Box::new(Expr::new(ExprKind::Literal(Literal::Unit), default_span)),
289 },
290 default_span,
291 )
292 }
293 ErrorContext::LetBinding { name, value } => {
294 Expr::new(
296 ExprKind::Let {
297 name: name.clone().unwrap_or_else(|| "_error".to_string()),
298 type_annotation: None,
299 value: value.clone().unwrap_or_else(|| {
300 Box::new(Expr::new(ExprKind::Literal(Literal::Unit), default_span))
301 }),
302 body: Box::new(Expr::new(ExprKind::Literal(Literal::Unit), default_span)),
303 is_mutable: false,
304 },
305 default_span,
306 )
307 }
308 ErrorContext::IfExpression {
309 condition,
310 then_branch,
311 ..
312 } => {
313 Expr::new(
315 ExprKind::If {
316 condition: condition.clone().unwrap_or_else(|| {
317 Box::new(Expr::new(
318 ExprKind::Literal(Literal::Bool(false)),
319 default_span,
320 ))
321 }),
322 then_branch: then_branch.clone().unwrap_or_else(|| {
323 Box::new(Expr::new(ExprKind::Literal(Literal::Unit), default_span))
324 }),
325 else_branch: Some(Box::new(Expr::new(
326 ExprKind::Literal(Literal::Unit),
327 default_span,
328 ))),
329 },
330 default_span,
331 )
332 }
333 ErrorContext::ArrayLiteral { elements, .. } => {
334 Expr::new(ExprKind::List(elements.clone()), default_span)
336 }
337 ErrorContext::BinaryOp { left, .. } => {
338 if let Some(left) = left {
340 *left.clone()
341 } else {
342 Expr::new(ExprKind::Literal(Literal::Unit), default_span)
343 }
344 }
345 ErrorContext::StructLiteral { name, fields, .. } => {
346 if let Some(name) = name {
348 Expr::new(
349 ExprKind::StructLiteral {
350 name: name.clone(),
351 fields: fields.clone(),
352 },
353 default_span,
354 )
355 } else {
356 Expr::new(ExprKind::Literal(Literal::Unit), default_span)
357 }
358 }
359 }
360 }
361}
362
363pub trait ErrorRecoverable {
365 fn recover_from_error(&mut self, error: ErrorNode) -> Option<Expr>;
367
368 fn can_recover(&self) -> bool;
370
371 fn get_errors(&self) -> Vec<ErrorNode>;
373}
374
375#[cfg(test)]
376#[allow(clippy::unwrap_used, clippy::panic)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_error_recovery_creation() {
382 let mut recovery = ErrorRecovery::new();
383
384 let error = recovery.missing_function_name(SourceLocation {
385 line: 1,
386 column: 5,
387 file: None,
388 });
389
390 assert_eq!(error.message, "expected function name");
391 assert_eq!(recovery.error_count, 1);
392 assert!(recovery.should_continue());
393 }
394
395 #[test]
396 fn test_recovery_strategy_selection() {
397 let context = ErrorContext::FunctionDecl {
398 name: None,
399 params: None,
400 body: None,
401 };
402
403 let strategy = RecoveryRules::select_strategy(&context);
404
405 match strategy {
406 RecoveryStrategy::InsertToken(token) => {
407 assert_eq!(token, "error_fn");
408 }
409 _ => panic!("Expected InsertToken strategy"),
410 }
411 }
412
413 #[test]
414 fn test_synthetic_ast_generation() {
415 let error = ErrorNode {
416 message: "test error".to_string(),
417 location: SourceLocation {
418 line: 1,
419 column: 1,
420 file: None,
421 },
422 context: ErrorContext::LetBinding {
423 name: Some("x".to_string()),
424 value: None,
425 },
426 recovery: RecoveryStrategy::DefaultValue,
427 };
428
429 let ast = RecoveryRules::synthesize_ast(&error);
430
431 match ast.kind {
432 ExprKind::Let { name, type_annotation: _, value, .. } => {
433 assert_eq!(name, "x");
434 match value.kind {
435 ExprKind::Literal(Literal::Unit) => {}
436 _ => panic!("Expected Unit value"),
437 }
438 }
439 _ => panic!("Expected Let expression"),
440 }
441 }
442
443 #[test]
444 fn test_sync_token_detection() {
445 let recovery = ErrorRecovery::new();
446
447 assert!(recovery.is_sync_token(";"));
448 assert!(recovery.is_sync_token("fun"));
449 assert!(recovery.is_sync_token("let"));
450 assert!(!recovery.is_sync_token("="));
451 assert!(!recovery.is_sync_token("+"));
452 }
453
454 #[test]
455 fn test_max_errors_limit() {
456 let mut recovery = ErrorRecovery::new();
457 recovery.max_errors = 3;
458
459 for i in 0..5 {
460 if recovery.should_continue() {
461 recovery.missing_function_name(SourceLocation {
462 line: i,
463 column: 0,
464 file: None,
465 });
466 }
467 }
468
469 assert_eq!(recovery.error_count, 3);
470 assert!(!recovery.should_continue());
471 }
472}