Skip to main content

mago_syntax/
error.rs

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