Skip to main content

mago_syntax/
error.rs

1use mago_database::file::FileId;
2use mago_database::file::HasFileId;
3use mago_reporting::Annotation;
4use mago_reporting::Issue;
5use mago_span::HasSpan;
6use mago_span::Position;
7use mago_span::Span;
8
9use crate::cst::LiteralStringKind;
10use crate::token::TokenKind;
11
12const SYNTAX_ERROR_CODE: &str = "syntax";
13const PARSE_ERROR_CODE: &str = "parse";
14
15#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17pub enum SyntaxError {
18    UnexpectedToken(FileId, u8, Position),
19    UnrecognizedToken(FileId, u8, Position),
20    UnexpectedEndOfFile(FileId, Position),
21    RecursionLimitExceeded(FileId, Position),
22}
23
24/// The token kinds a parser expected at the point an error was raised.
25#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub enum Expected {
28    Exactly(TokenKind),
29    OneOf(&'static [TokenKind]),
30}
31
32impl Expected {
33    #[must_use]
34    pub fn kinds(&self) -> &[TokenKind] {
35        match self {
36            Expected::Exactly(kind) => std::slice::from_ref(kind),
37            Expected::OneOf(kinds) => kinds,
38        }
39    }
40}
41
42#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44pub enum ParseError {
45    SyntaxError(SyntaxError),
46    UnexpectedEndOfFile(Expected, FileId, Position),
47    UnexpectedToken(Expected, TokenKind, Span),
48    UnclosedLiteralString(LiteralStringKind, Span),
49    RecursionLimitExceeded(Span),
50}
51
52impl HasFileId for SyntaxError {
53    fn file_id(&self) -> FileId {
54        match self {
55            Self::UnexpectedToken(file_id, _, _) => *file_id,
56            Self::UnrecognizedToken(file_id, _, _) => *file_id,
57            Self::UnexpectedEndOfFile(file_id, _) => *file_id,
58            Self::RecursionLimitExceeded(file_id, _) => *file_id,
59        }
60    }
61}
62
63impl HasFileId for ParseError {
64    fn file_id(&self) -> FileId {
65        match self {
66            ParseError::SyntaxError(syntax_error) => syntax_error.file_id(),
67            ParseError::UnexpectedEndOfFile(_, file_id, _) => *file_id,
68            ParseError::UnexpectedToken(_, _, span) => span.file_id,
69            ParseError::UnclosedLiteralString(_, span) => span.file_id,
70            ParseError::RecursionLimitExceeded(span) => span.file_id,
71        }
72    }
73}
74
75impl HasSpan for SyntaxError {
76    fn span(&self) -> Span {
77        let (file_id, position) = match self {
78            Self::UnexpectedToken(file_id, _, p) => (file_id, p),
79            Self::UnrecognizedToken(file_id, _, p) => (file_id, p),
80            Self::UnexpectedEndOfFile(file_id, p) => (file_id, p),
81            Self::RecursionLimitExceeded(file_id, p) => (file_id, p),
82        };
83
84        Span::new(*file_id, *position, position.forward(1))
85    }
86}
87
88impl HasSpan for ParseError {
89    fn span(&self) -> Span {
90        match &self {
91            ParseError::SyntaxError(syntax_error) => syntax_error.span(),
92            ParseError::UnexpectedEndOfFile(_, file_id, position) => Span::new(*file_id, *position, *position),
93            ParseError::UnexpectedToken(_, _, span) => *span,
94            ParseError::UnclosedLiteralString(_, span) => *span,
95            ParseError::RecursionLimitExceeded(span) => *span,
96        }
97    }
98}
99
100impl std::fmt::Display for SyntaxError {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        let message = match self {
103            Self::UnexpectedToken(_, token, _) => &format!("Unexpected token `{}` (0x{:02X})", *token as char, token),
104            Self::UnrecognizedToken(_, token, _) => {
105                &format!("Unrecognised token `{}` (0x{:02X})", *token as char, token)
106            }
107            Self::UnexpectedEndOfFile(_, _) => "Unexpected end of file",
108            Self::RecursionLimitExceeded(_, _) => "Maximum recursion depth exceeded",
109        };
110
111        write!(f, "{message}")
112    }
113}
114
115impl std::fmt::Display for ParseError {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        let message = match self {
118            ParseError::SyntaxError(e) => {
119                return write!(f, "{e}");
120            }
121            ParseError::UnexpectedEndOfFile(expected, _, _) => {
122                let expected = expected.kinds().iter().map(ToString::to_string).collect::<Vec<_>>().join("`, `");
123
124                if expected.is_empty() {
125                    "Unexpected end of file".to_string()
126                } else if expected.len() == 1 {
127                    format!("Expected `{expected}` before end of file")
128                } else {
129                    format!("Expected one of `{expected}` before end of file")
130                }
131            }
132            ParseError::UnexpectedToken(expected, found, _) => {
133                let expected = expected.kinds().iter().map(ToString::to_string).collect::<Vec<_>>().join("`, `");
134
135                let found = found.to_string();
136
137                if expected.is_empty() {
138                    format!("Unexpected token `{found}`")
139                } else if expected.len() == 1 {
140                    format!("Expected `{expected}`, found `{found}`")
141                } else {
142                    format!("Expected one of `{expected}`, found `{found}`")
143                }
144            }
145            ParseError::UnclosedLiteralString(kind, _) => match kind {
146                LiteralStringKind::SingleQuoted => "Unclosed single-quoted string".to_string(),
147                LiteralStringKind::DoubleQuoted => "Unclosed double-quoted string".to_string(),
148            },
149            ParseError::RecursionLimitExceeded(_) => "Maximum recursion depth exceeded".to_string(),
150        };
151
152        write!(f, "{message}")
153    }
154}
155
156impl std::error::Error for SyntaxError {}
157
158impl std::error::Error for ParseError {
159    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
160        match self {
161            ParseError::SyntaxError(e) => Some(e),
162            _ => None,
163        }
164    }
165}
166impl From<&SyntaxError> for Issue {
167    fn from(error: &SyntaxError) -> Issue {
168        let span = error.span();
169
170        Issue::error("Syntax error encountered during lexing")
171            .with_code(SYNTAX_ERROR_CODE)
172            .with_annotation(Annotation::primary(span).with_message(error.to_string()))
173            .with_note("This error indicates that the lexer encountered a syntax issue.")
174            .with_help("Check the syntax of your code.")
175    }
176}
177
178impl From<SyntaxError> for ParseError {
179    fn from(error: SyntaxError) -> Self {
180        ParseError::SyntaxError(error)
181    }
182}
183
184impl From<&ParseError> for Issue {
185    fn from(error: &ParseError) -> Self {
186        if let ParseError::SyntaxError(syntax_error) = error {
187            syntax_error.into()
188        } else {
189            Issue::error("Parse error encountered during parsing")
190                .with_code(PARSE_ERROR_CODE)
191                .with_annotation(Annotation::primary(error.span()).with_message(error.to_string()))
192                .with_note("This error indicates that the parser encountered a parse issue.")
193                .with_help("Check the syntax of your code.")
194        }
195    }
196}