linkrs_query/parser/core/
error.rs1use crate::QueryError;
8use std::error::Error;
9use std::fmt;
10
11use linkrs_core::types::Position;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ParseErrorKind {
15 LexicalError,
16 SyntaxError,
17 UnexpectedToken,
18 UnterminatedString,
19 UnterminatedComment,
20 InvalidNumber,
21 InvalidEscapeSequence,
22 UnicodeEscapeError,
23 UnexpectedEndOfInput,
24 InvalidCharacter,
25 UnknownKeyword,
26 RecursionLimitExceeded,
27 UnsupportedFeature,
28 SemanticError,
29}
30
31#[derive(Debug)]
32pub struct ParseError {
33 pub kind: ParseErrorKind,
34 pub message: Box<str>,
35 pub position: Position,
36 pub offset: Option<usize>,
37 pub unexpected_token: Option<Box<str>>,
38 pub expected_tokens: Box<[String]>,
39 pub context: Option<Box<dyn Error + Send + Sync>>,
40 pub hints: Box<[String]>,
41 pub recovered: bool,
44}
45
46impl ParseError {
47 pub fn new(kind: ParseErrorKind, message: impl Into<Box<str>>, position: Position) -> Self {
48 ParseError {
49 kind,
50 message: message.into(),
51 position,
52 offset: None,
53 unexpected_token: None,
54 expected_tokens: Box::new([]),
55 context: None,
56 hints: Box::new([]),
57 recovered: false,
58 }
59 }
60
61 pub fn new_simple(message: impl Into<Box<str>>, position: Position) -> Self {
62 ParseError::new(ParseErrorKind::SyntaxError, message, position)
63 }
64
65 pub fn syntax_error<T: fmt::Display>(msg: T, position: Position) -> ParseError {
66 ParseError::new(
67 ParseErrorKind::SyntaxError,
68 format!("Syntax error: {}", msg),
69 position,
70 )
71 }
72
73 pub fn unexpected_token<T: fmt::Display>(token: T, position: Position) -> ParseError {
74 ParseError::new(
75 ParseErrorKind::UnexpectedToken,
76 format!("Unexpected token: {}", token),
77 position,
78 )
79 }
80
81 pub fn unterminated_string(position: Position) -> ParseError {
82 ParseError::new(
83 ParseErrorKind::UnterminatedString,
84 "Unterminated string literal",
85 position,
86 )
87 }
88
89 pub fn unterminated_comment(position: Position) -> ParseError {
90 ParseError::new(
91 ParseErrorKind::UnterminatedComment,
92 "Unterminated multi-line comment",
93 position,
94 )
95 }
96
97 pub fn with_offset(mut self, offset: usize) -> Self {
98 self.offset = Some(offset);
99 self
100 }
101
102 pub fn with_unexpected_token<T: fmt::Display>(mut self, token: T) -> Self {
103 self.unexpected_token = Some(token.to_string().into_boxed_str());
104 self
105 }
106
107 pub fn with_expected_tokens(mut self, tokens: Vec<String>) -> Self {
108 self.expected_tokens = tokens.into_boxed_slice();
109 self
110 }
111
112 pub fn with_context<E: Error + Send + Sync + 'static>(mut self, context: E) -> Self {
113 self.context = Some(Box::new(context));
114 self
115 }
116
117 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
118 self.hints = std::iter::once(hint.into())
119 .collect::<Vec<String>>()
120 .into_boxed_slice();
121 self
122 }
123
124 pub fn with_hints(mut self, hints: Vec<impl Into<String>>) -> Self {
125 self.hints = hints
126 .into_iter()
127 .map(|h| h.into())
128 .collect::<Vec<String>>()
129 .into_boxed_slice();
130 self
131 }
132
133 pub fn add_hint(&mut self, hint: impl Into<String>) {
134 let mut new_hints = self
135 .hints
136 .iter()
137 .map(|s| s.to_string())
138 .collect::<Vec<String>>();
139 new_hints.push(hint.into());
140 self.hints = new_hints.into_boxed_slice();
141 }
142
143 pub fn mark_recovered(mut self) -> Self {
146 self.recovered = true;
147 self
148 }
149}
150
151impl fmt::Display for ParseError {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 write!(
154 f,
155 "Parse error at line {}, column {}: {}",
156 self.position.line, self.position.column, self.message
157 )?;
158
159 if let Some(ref token) = self.unexpected_token {
160 writeln!(f, "\n Unexpected token: {}", token)?;
161 }
162
163 if !self.expected_tokens.is_empty() {
164 writeln!(
165 f,
166 "\n Expected one of: {}",
167 self.expected_tokens.join(", ")
168 )?;
169 }
170
171 if let Some(ref context) = self.context {
172 writeln!(f, "\n Context: {}", context)?;
173 }
174
175 if !self.hints.is_empty() {
176 writeln!(f, "\n Hint(s):")?;
177 for hint in &self.hints {
178 writeln!(f, " - {}", hint)?;
179 }
180 }
181
182 if self.recovered {
183 writeln!(f, "\n (recovered, parsing continued)")?;
184 }
185
186 Ok(())
187 }
188}
189
190impl Error for ParseError {}
191
192impl From<String> for ParseError {
193 fn from(message: String) -> Self {
194 ParseError::new(ParseErrorKind::SyntaxError, message, Position::new(0, 0))
195 }
196}
197
198impl From<super::super::lexing::LexError> for ParseError {
199 fn from(lex_error: super::super::lexing::LexError) -> Self {
200 let mut parse_error = ParseError::new(
201 ParseErrorKind::LexicalError,
202 lex_error.message,
203 lex_error.position,
204 );
205 if let Some(offset) = lex_error.offset {
207 parse_error = parse_error.with_offset(offset);
208 }
209 parse_error
210 }
211}
212
213impl From<ParseError> for QueryError {
214 fn from(parse_error: ParseError) -> Self {
215 use linkrs_core::error::query::{
216 ParseErrorKind as QueryParseErrorKind, StructuredParseError,
217 };
218 use linkrs_core::types::Position;
219
220 let kind = match parse_error.kind {
221 ParseErrorKind::LexicalError => QueryParseErrorKind::LexicalError,
222 ParseErrorKind::SyntaxError => QueryParseErrorKind::SyntaxError,
223 ParseErrorKind::UnexpectedToken => QueryParseErrorKind::UnexpectedToken,
224 ParseErrorKind::UnterminatedString => QueryParseErrorKind::UnterminatedString,
225 ParseErrorKind::UnterminatedComment => QueryParseErrorKind::UnterminatedComment,
226 ParseErrorKind::InvalidNumber => QueryParseErrorKind::InvalidNumber,
227 ParseErrorKind::InvalidEscapeSequence => QueryParseErrorKind::InvalidEscapeSequence,
228 ParseErrorKind::UnicodeEscapeError => QueryParseErrorKind::UnicodeEscapeError,
229 ParseErrorKind::UnexpectedEndOfInput => QueryParseErrorKind::UnexpectedEndOfInput,
230 ParseErrorKind::InvalidCharacter => QueryParseErrorKind::InvalidCharacter,
231 ParseErrorKind::UnknownKeyword => QueryParseErrorKind::UnknownKeyword,
232 ParseErrorKind::RecursionLimitExceeded => QueryParseErrorKind::RecursionLimitExceeded,
233 ParseErrorKind::UnsupportedFeature => QueryParseErrorKind::UnsupportedFeature,
234 ParseErrorKind::SemanticError => QueryParseErrorKind::SemanticError,
235 };
236
237 let structured = StructuredParseError {
238 kind,
239 message: parse_error.message.into(),
240 position: Position::new(parse_error.position.line, parse_error.position.column),
241 offset: parse_error.offset,
242 unexpected_token: parse_error.unexpected_token.map(|t| t.into()),
243 expected_tokens: parse_error.expected_tokens.into_vec(),
244 hints: parse_error.hints.into_vec(),
245 context: parse_error.context.as_ref().map(|c| c.to_string()),
246 recovered: parse_error.recovered,
247 };
248
249 QueryError::structured_parse_error(structured)
250 }
251}
252
253#[derive(Debug)]
254pub struct ParseErrors {
255 pub errors: Vec<ParseError>,
256}
257
258impl ParseErrors {
259 pub fn new() -> Self {
260 ParseErrors { errors: Vec::new() }
261 }
262
263 pub fn add(&mut self, error: ParseError) {
264 self.errors.push(error);
265 }
266
267 pub fn is_empty(&self) -> bool {
268 self.errors.is_empty()
269 }
270
271 pub fn len(&self) -> usize {
272 self.errors.len()
273 }
274
275 pub fn push(&mut self, error: ParseError) {
276 self.errors.push(error);
277 }
278
279 pub fn extend(&mut self, errors: &mut ParseErrors) {
280 self.errors.append(&mut errors.errors);
281 }
282
283 pub fn take(&mut self) -> Vec<ParseError> {
284 std::mem::take(&mut self.errors)
285 }
286
287 pub fn iter(&self) -> impl Iterator<Item = &ParseError> {
288 self.errors.iter()
289 }
290}
291
292impl IntoIterator for ParseErrors {
293 type Item = ParseError;
294 type IntoIter = std::vec::IntoIter<ParseError>;
295
296 fn into_iter(self) -> Self::IntoIter {
297 self.errors.into_iter()
298 }
299}
300
301impl Default for ParseErrors {
302 fn default() -> Self {
303 ParseErrors::new()
304 }
305}
306
307impl fmt::Display for ParseErrors {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 for (i, error) in self.errors.iter().enumerate() {
310 if i > 0 {
311 writeln!(f)?;
312 }
313 write!(f, "{}", error)?;
314 }
315 Ok(())
316 }
317}
318
319impl Error for ParseErrors {}
320
321impl From<Vec<ParseError>> for ParseErrors {
322 fn from(errors: Vec<ParseError>) -> Self {
323 ParseErrors { errors }
324 }
325}
326
327impl From<ParseErrors> for QueryError {
328 fn from(parse_errors: ParseErrors) -> Self {
329 QueryError::parse_error(parse_errors.to_string())
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_parse_error_display() {
339 let error = ParseError::unexpected_token("IDENTIFIER", Position::new(10, 5));
340 let display = error.to_string();
341 assert!(display.contains("line 10, column 5"));
342 assert!(display.contains("Unexpected token: IDENTIFIER"));
343 }
344
345 #[test]
346 fn test_parse_error_with_hint() {
347 let error = ParseError::syntax_error("invalid syntax", Position::new(5, 10))
348 .with_hint("Try adding a semicolon at the end".to_string());
349
350 let display = error.to_string();
351 assert!(display.contains("Hint"));
352 assert!(display.contains("semicolon"));
353 }
354
355 #[test]
356 fn test_parse_error_with_context() {
357 let context_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
358 let error =
359 ParseError::syntax_error("error", Position::new(1, 1)).with_context(context_error);
360
361 let display = error.to_string();
362 assert!(display.contains("Context"));
363 }
364}