/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 == expected8 => { |
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 | | } |