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}