mago_syntax/
error.rs

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