wright/parser/
error.rs

1//! Representation and implementation relating to errors that may be encountered in parsing.
2
3use crate::{
4    reporting::{Diagnostic, Highlight},
5    source_tracking::fragment::Fragment,
6};
7use std::borrow::Cow;
8
9/// All the different errors that can be produced in the process of parsing.
10/// The names of these should be self-describing, but in cases when one of these needs to appear in a diagnostic,
11/// use [ParserErrorKind::describe].
12#[allow(missing_docs)]
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ParserErrorKind {
15    EncounteredUnknownToken,
16    EncounteredUnterminatedComment,
17    EncounteredUnterminatedString,
18    ExpectedAtomicTypeSignature,
19    ExpectedBooleanLiteral,
20    ExpectedIdentifier,
21    ExpectedImportDeclaration,
22    ExpectedIntegerLiteral,
23    ExpectedPath,
24    ExpectedReferenceTypeSignature,
25    ExpectedTypeSignature,
26    ExpectedWhitespace,
27    ImportMustEndWithSemicolon,
28}
29
30impl ParserErrorKind {
31    /// Get a short description of this kind of error.
32    pub const fn describe(self) -> &'static str {
33        use ParserErrorKind::*;
34
35        match self {
36            EncounteredUnknownToken => "encountered unknown token",
37            EncounteredUnterminatedComment => {
38                "encountered unterminated multiline comment while parsing"
39            }
40            EncounteredUnterminatedString => {
41                "encountered unterminated string literal while parsing"
42            }
43            ExpectedAtomicTypeSignature => "expected atomic primitive type",
44            ExpectedBooleanLiteral => "expected boolean literal",
45            ExpectedIdentifier => "expected identifier",
46            ExpectedImportDeclaration => "expected import declaration",
47            ExpectedIntegerLiteral => "expected integer literal",
48            ExpectedPath => "expected path or identifier",
49            ExpectedReferenceTypeSignature => "expected reference type signature",
50            ExpectedTypeSignature => "expected type signature",
51            ExpectedWhitespace => "expected whitespace character(s)",
52            ImportMustEndWithSemicolon => "import declarations must end with a semicolon",
53        }
54    }
55
56    /// Construct a [ParserError] by adding a location [Fragment] to this error variant.
57    pub const fn at(self, f: Fragment) -> ParserError {
58        ParserError {
59            kind: self,
60            location: f,
61            help: Vec::new(),
62        }
63    }
64}
65
66/// An error that occurred while parsing.
67/// This error structure is pretty simple compared to what can be represented using a diagnostic. That's fine,
68/// since most of the more complex errors arise when typechecking, rather than checking syntax.
69#[derive(Debug)]
70pub struct ParserError {
71    /// What type/cause there is for this error.
72    pub kind: ParserErrorKind,
73
74    /// Where this error occurred.
75    pub location: Fragment,
76
77    /// Optional help strings that can be printed with this error.
78    pub help: Vec<Cow<'static, str>>,
79}
80
81impl ParserError {
82    /// Builder-style method to add a help string to a [ParserError].
83    pub fn with_help(mut self, help: impl Into<Cow<'static, str>>) -> Self {
84        self.help.push(help.into());
85        self
86    }
87
88    /// Turn this parser error into a full blown compiler error.
89    pub fn as_diagnostic(self) -> Diagnostic {
90        let description = self.kind.describe();
91
92        // Put a little clarification if the parser reached end of source and then produced an error.
93        let message = if self.location.is_empty_at_end_of_source() {
94            Cow::Borrowed("found end of source here")
95        } else {
96            Cow::Borrowed("")
97        };
98
99        let mut diagnostic = Diagnostic::error()
100            .with_message(description)
101            .with_highlights([Highlight::primary(self.location.clone(), message)]);
102
103        if !self.help.is_empty() {
104            diagnostic = diagnostic.with_notes(self.help);
105        }
106
107        diagnostic
108    }
109}