sqparse/parser/
error.rs

1use crate::annotation::{display_annotations, Annotation, Mode};
2use crate::parser::context::ContextType;
3use crate::token::TerminalToken;
4use crate::TokenItem;
5use std::ops::Range;
6use yansi::Paint;
7
8/// Type of [`ParseError`].
9///
10/// Implements [`std::fmt::Display`] to write a useful error message.
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum ParseErrorType {
13    /// Expected a specific terminal token but got something else.
14    ///
15    /// # Example
16    /// ```text
17    /// function MyFunc { }
18    ///                 ^ error
19    /// ```
20    ExpectedTerminal(TerminalToken),
21
22    /// Expected a specific compound terminal but got something else.
23    ExpectedCompound2(TerminalToken, TerminalToken),
24
25    /// Expected a specific compound terminal but got something else.
26    ExpectedCompound3(TerminalToken, TerminalToken, TerminalToken),
27
28    /// Expected an identifier but got something else.
29    ///
30    /// # Example
31    /// ```text
32    /// global var !?!?! = "guh??"
33    ///            ^ error
34    /// ```
35    ExpectedIdentifier,
36
37    /// Expected a literal but got something else.
38    ExpectedLiteral,
39
40    /// Expected a token that starts an expression but got something else.
41    ///
42    /// # Example
43    /// ```text
44    /// int What = globalize_all_functions
45    ///            ^ error
46    /// ```
47    ExpectedExpression,
48
49    /// Expected a token that starts a value in an expression but got something else.
50    ///
51    /// # Example
52    /// ```text
53    /// local sum = 1 + ?
54    ///                 ^ error
55    /// ```
56    ExpectedValue,
57
58    /// Expected an operator but got something else.
59    ExpectedOperator,
60
61    /// Expected a prefix operator but got something else.
62    ExpectedPrefixOperator,
63
64    /// Expected a postfix operator but got something else.
65    ExpectedPostfixOperator,
66
67    /// Expected a binary operator but got something else.
68    ExpectedBinaryOperator,
69
70    /// Expected a type but got something else.
71    ///
72    /// # Example
73    /// ```text
74    /// typedef Five 5
75    ///              ^ error
76    /// ```
77    ExpectedType,
78
79    /// Expected a type modifier but got something else.
80    ///
81    /// # Example
82    /// ```text
83    /// typedef help table&-
84    ///                    ^ error
85    /// ```
86    ExpectedTypeModifier,
87
88    /// Expected a token that starts a table slot but got something else.
89    ///
90    /// # Example
91    /// ```text
92    /// my_table = {
93    ///     class MyTableClass {}
94    ///     ^ error
95    /// }
96    /// ```
97    ExpectedTableSlot,
98
99    /// Expected a token that starts a class member but got something else.
100    ///
101    /// # Example
102    /// ```text
103    /// class MyClass {
104    ///     globalize_all_functions
105    ///     ^ error
106    /// }
107    /// ```
108    ExpectedClassMember,
109
110    /// Expected a token that starts a statement but got something else.
111    ///
112    /// # Example
113    /// ```text
114    /// > hey
115    /// ^ error
116    /// ```
117    ExpectedStatement,
118
119    /// Expected a newline or semicolon to end a statement but got something else.
120    ///
121    /// # Example
122    /// ```text
123    /// { 1 } + 2
124    ///       ^ error
125    /// ```
126    ExpectedEndOfStatement,
127
128    /// Expected a token that starts a global definition but got something else.
129    ///
130    /// # Example
131    /// ```text
132    /// global if ()
133    ///        ^ error
134    /// ```
135    ExpectedGlobalDefinition,
136
137    /// Found a linebreak in a place where one is not allowed.
138    IllegalLineBreak,
139
140    /// An expression was not allowed due to precedence rules.
141    Precedence,
142
143    /// Expected a slot in a class or table.
144    ExpectedSlot,
145
146    /// Expected a string literal.
147    ExpectedStringLiteral,
148}
149
150/// An error emitted while trying to parse a token list.
151///
152/// Each error has a type with more information, the token where the error occurred, and possibly
153/// some contextual information.
154#[derive(Debug, Clone)]
155pub struct ParseError {
156    /// The type of error.
157    pub ty: ParseErrorType,
158
159    /// The index of the token where the error occurred.
160    pub token_index: usize,
161
162    /// Affinity of the token.
163    pub token_affinity: TokenAffinity,
164
165    /// Contextual information if available.
166    pub context: Option<ParseErrorContext>,
167
168    pub(crate) is_fatal: bool,
169}
170
171/// Affinity of the token index in [`ParseError`].
172///
173/// This controls how a token range is printed when the token is at the start of a newline. For
174/// example, in this input:
175/// ```text
176/// a +
177/// b
178/// ```
179///
180/// An affinity of [`Before`] on `b` would highlight the end of the line before `b`, indicating the
181/// error is not necessarily related to `b` itself but to something missing after `+`:
182/// ```text
183/// a +
184///    ^
185/// b
186/// ```
187///
188/// An affinity of [`Inline`] would highlight `b` itself, indicating it is the problematic token:
189/// ```text
190/// a +
191/// b
192/// ^
193/// ```
194///
195/// [`Before`]: TokenAffinity::Before
196/// [`Inline`]: TokenAffinity::Inline
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum TokenAffinity {
199    Before,
200    Inline,
201}
202
203/// Context attached to a [`ParseError`].
204///
205/// This is generally attached to an error when the parser knows the context of what it is parsing
206/// with confidence.
207///
208/// # Example
209/// In this code, the parser knows that it is parsing the RHS of an expression when the error
210/// occurs.
211/// ```text
212/// 1 + function
213///     ^ error
214/// ```
215/// So it will attach a context to the error with an [`Expression`] context.
216///
217/// [`Expression`]: ContextType::Expression
218#[derive(Debug, Clone)]
219pub struct ParseErrorContext {
220    /// The range of tokens that this context applies.
221    ///
222    /// For example, if the context is a [`FunctionDefinitionStatement`], the range will include
223    /// the entire function.
224    ///
225    /// In some cases this will end at the token where the error is encountered, however in many
226    /// cases the parser can match delimiters like `{` and `}` to provide more context.
227    ///
228    /// [`FunctionDefinitionStatement`]: crate::ast::FunctionDefinitionStatement
229    pub token_range: Range<usize>,
230
231    /// Affinity of the last token in the range.
232    pub end_affinity: TokenAffinity,
233
234    /// The type of context.
235    pub ty: ContextType,
236}
237
238impl ParseError {
239    /// Creates a new `ParseError`.
240    pub fn new(ty: ParseErrorType, token_index: usize, token_affinity: TokenAffinity) -> Self {
241        ParseError {
242            ty,
243            token_index,
244            token_affinity,
245            context: None,
246            is_fatal: false,
247        }
248    }
249
250    /// Attaches some context to the error.
251    pub fn with_context(
252        self,
253        ty: ContextType,
254        token_range: Range<usize>,
255        end_affinity: TokenAffinity,
256    ) -> Self {
257        self.replace_context(ContextType::Span, ty, token_range, end_affinity)
258    }
259
260    /// Replaces an existing context with a new one, if it matches.
261    pub fn replace_context(
262        mut self,
263        from_ty: ContextType,
264        to_ty: ContextType,
265        token_range: Range<usize>,
266        end_affinity: TokenAffinity,
267    ) -> Self {
268        // Sanity check, ensure the range includes the actual token.
269        let token_range = (self.token_index.min(token_range.start))
270            ..((self.token_index + 1).max(token_range.end));
271
272        match &mut self.context {
273            // Set a new context if there isn't one already.
274            None => {
275                self.context = Some(ParseErrorContext {
276                    token_range,
277                    ty: to_ty,
278                    end_affinity,
279                });
280            }
281
282            // Replace the existing context if it matches the replace type.
283            Some(context) if context.ty == from_ty => {
284                // Ensure the range contains both, allowing an inner context to expand the outer context.
285                let token_range = (token_range.start.min(context.token_range.start))
286                    ..(token_range.end.max(context.token_range.end));
287                let end_affinity = if context.end_affinity == TokenAffinity::Inline {
288                    TokenAffinity::Inline
289                } else {
290                    end_affinity
291                };
292                *context = ParseErrorContext {
293                    token_range,
294                    ty: to_ty,
295                    end_affinity,
296                };
297            }
298
299            // Otherwise, leave the existing context intact.
300            _ => {}
301        }
302
303        self
304    }
305
306    /// Returns an implementation of [`std::fmt::Display`] that pretty-prints the error and context
307    /// using [`display_annotations`].
308    pub fn display<'s>(
309        &'s self,
310        source: &'s str,
311        tokens: &'s [TokenItem<'s>],
312        file_name: Option<&'s str>,
313    ) -> impl std::fmt::Display + 's {
314        Display {
315            error: self,
316            source,
317            tokens,
318            file_name,
319        }
320    }
321
322    pub(crate) fn into_fatal(mut self) -> Self {
323        self.is_fatal = true;
324        self
325    }
326
327    pub(crate) fn into_non_fatal(mut self) -> Self {
328        self.is_fatal = false;
329        self
330    }
331}
332
333impl std::fmt::Display for ParseErrorType {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        match self {
336            ParseErrorType::ExpectedTerminal(terminal) => {
337                write!(f, "expected `{}`", terminal.as_str())
338            }
339            ParseErrorType::ExpectedCompound2(token1, token2) => {
340                write!(f, "expected `{}{}`", token1.as_str(), token2.as_str())
341            }
342            ParseErrorType::ExpectedCompound3(token1, token2, token3) => write!(
343                f,
344                "expected `{}{}{}`",
345                token1.as_str(),
346                token2.as_str(),
347                token3.as_str()
348            ),
349            ParseErrorType::ExpectedIdentifier => write!(f, "expected an identifier"),
350            ParseErrorType::ExpectedLiteral => write!(f, "expected a literal"),
351            ParseErrorType::ExpectedExpression => write!(f, "expected an expression"),
352            ParseErrorType::ExpectedValue => write!(f, "expected a value"),
353            ParseErrorType::ExpectedOperator => write!(f, "expected an operator"),
354            ParseErrorType::ExpectedPrefixOperator => write!(f, "expected a prefix operator"),
355            ParseErrorType::ExpectedPostfixOperator => write!(f, "expected a postfix operator"),
356            ParseErrorType::ExpectedBinaryOperator => write!(f, "expected a binary operator"),
357            ParseErrorType::ExpectedType => write!(f, "expected a type"),
358            ParseErrorType::ExpectedTypeModifier => write!(f, "expected a type modifier"),
359            ParseErrorType::ExpectedTableSlot => write!(f, "expected a table slot"),
360            ParseErrorType::ExpectedClassMember => write!(f, "expected a class member"),
361            ParseErrorType::ExpectedStatement => write!(f, "expected a statement"),
362            ParseErrorType::ExpectedEndOfStatement => {
363                write!(f, "expected a newline or `;`")
364            }
365            ParseErrorType::ExpectedGlobalDefinition => write!(f, "expected a global definition"),
366            ParseErrorType::ExpectedSlot => write!(f, "expected a slot"),
367            ParseErrorType::ExpectedStringLiteral => write!(f, "expected a string literal"),
368
369            // todo: these need rewording to fit with "<>, found a <>"
370            ParseErrorType::IllegalLineBreak => {
371                write!(f, "expected anything but `\\n`; got it anyway")
372            }
373            ParseErrorType::Precedence => write!(f, "not allowed due to precedence rules"),
374        }
375    }
376}
377
378struct Display<'s> {
379    error: &'s ParseError,
380    source: &'s str,
381    tokens: &'s [TokenItem<'s>],
382    file_name: Option<&'s str>,
383}
384
385impl std::fmt::Display for Display<'_> {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        write!(
388            f,
389            "{}{}{}",
390            Paint::red("error").bold(),
391            Paint::white(": ").bold(),
392            Paint::white(self.error.ty).bold()
393        )?;
394        match self.tokens.get(self.error.token_index) {
395            Some(item) => writeln!(
396                f,
397                "{}{}",
398                Paint::white(", found a ").bold(),
399                Paint::white(item.token.ty).bold()
400            )?,
401            None => writeln!(f, "{}", Paint::white(", found the end of input").bold())?,
402        }
403
404        let src_range = token_src_range(
405            self.error.token_index,
406            self.error.token_affinity,
407            self.tokens,
408        );
409        let note = match &self.error.context {
410            Some(context) => {
411                let is_end = self.error.token_index + 1 == context.token_range.end
412                    && context.end_affinity == TokenAffinity::Before;
413                let context_text = if is_end { "for this" } else { "in this" };
414                format!("{} {}:", context_text, context.ty)
415            }
416            None => "".to_string(),
417        };
418
419        let mut annotations = vec![Annotation {
420            mode: Mode::Error,
421            text: format!("{}", self.error.ty),
422            note,
423            highlight: src_range.clone(),
424            visible: src_range.clone(),
425        }];
426
427        if let Some(context) = &self.error.context {
428            let start_range = token_src_range(
429                context.token_range.start,
430                TokenAffinity::Inline,
431                self.tokens,
432            );
433            let end_range = token_src_range(
434                context.token_range.end - 1,
435                context.end_affinity,
436                self.tokens,
437            );
438            annotations.push(Annotation {
439                mode: Mode::Info,
440                text: "".to_string(),
441                note: "".to_string(),
442                highlight: start_range.start..end_range.end,
443                visible: src_range,
444            });
445        }
446
447        write!(
448            f,
449            "{}",
450            display_annotations(self.file_name, self.source, &annotations)
451        )?;
452        Ok(())
453    }
454}
455
456fn token_src_range(
457    token_index: usize,
458    affinity: TokenAffinity,
459    tokens: &[TokenItem],
460) -> Range<usize> {
461    let Some(last_item) = tokens.last() else { return 0..0; };
462
463    if affinity == TokenAffinity::Before {
464        if token_index > 0 {
465            let last_item = &tokens[token_index.min(tokens.len()) - 1];
466            last_item.token.range.end..last_item.token.range.end
467        } else {
468            0..0
469        }
470    } else if token_index < tokens.len() {
471        let item = &tokens[token_index];
472        item.token.range.clone()
473    } else {
474        last_item.token.range.end..last_item.token.range.end
475    }
476}