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