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