qql_core/parser/
recover.rs1use super::{AstLowerer, MAX_STATEMENTS};
9use crate::ast::Stmt;
10use crate::error::{QqlError, Span};
11use crate::token::TokenKind;
12use alloc::vec::Vec;
13
14#[derive(Debug, Clone)]
16pub struct RecoveredScript {
17 pub statements: Vec<(Stmt, Span)>,
19 pub errors: Vec<QqlError>,
21}
22
23impl RecoveredScript {
24 pub fn is_valid(&self) -> bool {
26 self.errors.is_empty()
27 }
28}
29
30impl<'a> AstLowerer<'a> {
31 pub(crate) fn lower_script_recovering(input: &'a str) -> RecoveredScript {
32 let tokens = match Self::lex(input) {
33 Ok(tokens) => tokens,
34 Err(error) => {
35 return RecoveredScript {
36 statements: Vec::new(),
37 errors: alloc::vec![error],
38 };
39 }
40 };
41 let mut parser = AstLowerer::new(input, tokens);
42 let mut statements = Vec::new();
43 let mut errors = Vec::new();
44
45 while parser.peek_kind() != TokenKind::Eof {
46 if statements.len() >= MAX_STATEMENTS {
47 if let Ok(tok) = parser.peek() {
48 errors.push(QqlError::parse(
49 "QQL-PARSE-STATEMENT-LIMIT",
50 alloc::format!("a script may contain at most {MAX_STATEMENTS} statements"),
51 tok.span,
52 ));
53 }
54 break;
55 }
56
57 while parser.peek_kind() == TokenKind::Semicolon {
58 if let Ok(tok) = parser.peek() {
59 errors.push(QqlError::parse(
60 "QQL-PARSE-EMPTY-STATEMENT",
61 "leading or empty statements are not allowed",
62 tok.span,
63 ));
64 }
65 let _ = parser.advance();
66 if parser.peek_kind() == TokenKind::Eof {
67 return RecoveredScript { statements, errors };
68 }
69 }
70
71 let Ok(start_tok) = parser.peek() else {
72 break;
73 };
74 if start_tok.kind == TokenKind::Eof {
75 break;
76 }
77 let start_pos = start_tok.span.start;
78 match parser.parse_stmt() {
79 Ok(stmt) => match parser.peek() {
80 Ok(next) if next.kind == TokenKind::Semicolon => {
81 let end = next.span.end;
82 let _ = parser.advance();
83 statements.push((stmt, Span::new(start_pos, end)));
84 }
85 Ok(next) if next.kind == TokenKind::Eof => {
86 let end = parser.prev_span().end;
87 statements.push((stmt, Span::new(start_pos, end)));
88 }
89 Ok(next) if next.kind.is_statement_start() => {
90 errors.push(QqlError::parse(
91 "QQL-PARSE-SEPARATOR",
92 "multiple statements must be separated by a semicolon",
93 next.span,
94 ));
95 let end = parser.prev_span().end;
96 statements.push((stmt, Span::new(start_pos, end)));
97 }
98 Ok(next) => {
99 errors.push(QqlError::parse(
100 "QQL-PARSE-TRAILING",
101 alloc::format!("unexpected trailing token '{}'", next.text),
102 next.span,
103 ));
104 let end = parser.prev_span().end;
105 statements.push((stmt, Span::new(start_pos, end)));
106 parser.synchronize();
107 }
108 Err(error) => {
109 errors.push(error);
110 parser.synchronize();
111 }
112 },
113 Err(error) => {
114 errors.push(error);
115 let stuck_at = parser.index;
116 parser.synchronize();
117 if parser.index == stuck_at && !parser.peek_kind().is_recovery_sync() {
121 let _ = parser.advance();
122 }
123 }
124 }
125 }
126
127 RecoveredScript { statements, errors }
128 }
129
130 fn peek_kind(&mut self) -> TokenKind {
131 self.peek().map(|t| t.kind).unwrap_or(TokenKind::Eof)
132 }
133
134 fn synchronize(&mut self) {
137 loop {
138 let Ok(tok) = self.peek() else {
139 return;
140 };
141 match tok.kind {
142 TokenKind::Eof => return,
143 TokenKind::Semicolon => {
144 let _ = self.advance();
145 return;
146 }
147 kind if kind.is_recovery_sync() => return,
148 _ => {
149 let _ = self.advance();
150 }
151 }
152 }
153 }
154}