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