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    UnterminatedGenericTypeSignature,
29}
30
31impl ParserErrorKind {
32    /// Get a short description of this kind of error.
33    pub const fn describe(self) -> &'static str {
34        use ParserErrorKind::*;
35
36        match self {
37            EncounteredUnknownToken => "encountered unknown token",
38            EncounteredUnterminatedComment => {
39                "encountered unterminated multiline comment while parsing"
40            }
41            EncounteredUnterminatedString => {
42                "encountered unterminated string literal while parsing"
43            }
44            ExpectedAtomicTypeSignature => "expected atomic primitive type",
45            ExpectedBooleanLiteral => "expected boolean literal",
46            ExpectedIdentifier => "expected identifier",
47            ExpectedImportDeclaration => "expected import declaration",
48            ExpectedIntegerLiteral => "expected integer literal",
49            ExpectedPath => "expected path or identifier",
50            ExpectedReferenceTypeSignature => "expected reference type signature",
51            ExpectedTypeSignature => "expected type signature",
52            ExpectedWhitespace => "expected whitespace character(s)",
53            ImportMustEndWithSemicolon => "import declarations must end with a semicolon",
54            UnterminatedGenericTypeSignature => "generic type signature must end with a `>`",
55        }
56    }
57
58    /// Construct a [ParserError] by adding a location [Fragment] to this error variant.
59    pub const fn at(self, f: Fragment) -> ParserError {
60        ParserError {
61            kind: self,
62            location: f,
63            help: Vec::new(),
64        }
65    }
66}
67
68/// An error that occurred while parsing.
69/// This error structure is pretty simple compared to what can be represented using a diagnostic. That's fine,
70/// since most of the more complex errors arise when typechecking, rather than checking syntax.
71#[derive(Debug)]
72pub struct ParserError {
73    /// What type/cause there is for this error.
74    pub kind: ParserErrorKind,
75
76    /// Where this error occurred.
77    pub location: Fragment,
78
79    /// Optional help strings that can be printed with this error.
80    pub help: Vec<Cow<'static, str>>,
81}
82
83impl ParserError {
84    /// Builder-style method to add a help string to a [ParserError].
85    pub fn with_help(mut self, help: impl Into<Cow<'static, str>>) -> Self {
86        self.help.push(help.into());
87        self
88    }
89
90    /// Turn this parser error into a full blown compiler error.
91    pub fn as_diagnostic(self) -> Diagnostic {
92        let description = self.kind.describe();
93
94        // Put a little clarification if the parser reached end of source and then produced an error.
95        let message = if self.location.is_empty_at_end_of_source() {
96            Cow::Borrowed("found end of source here")
97        } else {
98            Cow::Borrowed("")
99        };
100
101        let mut diagnostic = Diagnostic::error()
102            .with_message(description)
103            .with_highlights([Highlight::primary(self.location.clone(), message)]);
104
105        if !self.help.is_empty() {
106            diagnostic = diagnostic.with_notes(self.help);
107        }
108
109        diagnostic
110    }
111}