1use serde::Deserialize;
2use serde::Serialize;
3
4use mago_reporting::Annotation;
5use mago_reporting::Issue;
6use mago_span::HasPosition;
7use mago_span::HasSpan;
8use mago_span::Position;
9use mago_span::Span;
10
11use crate::ast::LiteralStringKind;
12use crate::token::TokenKind;
13
14#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
15pub enum SyntaxError {
16 UnexpectedToken(u8, Position),
17 UnrecognizedToken(u8, Position),
18 UnexpectedEndOfFile(Position),
19}
20
21#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
22pub enum ParseError {
23 SyntaxError(SyntaxError),
24 UnexpectedEndOfFile(Vec<TokenKind>, Position),
25 UnexpectedToken(Vec<TokenKind>, TokenKind, Span),
26 UnclosedLiteralString(LiteralStringKind, Span),
27}
28
29impl HasSpan for ParseError {
30 fn span(&self) -> Span {
31 match &self {
32 ParseError::SyntaxError(syntax_error) => syntax_error.span(),
33 ParseError::UnexpectedEndOfFile(_, position) => Span::new(*position, *position),
34 ParseError::UnexpectedToken(_, _, span) => *span,
35 ParseError::UnclosedLiteralString(_, span) => *span,
36 }
37 }
38}
39
40impl HasSpan for SyntaxError {
41 fn span(&self) -> Span {
42 let position = match self {
43 Self::UnexpectedToken(_, p) => *p,
44 Self::UnrecognizedToken(_, p) => *p,
45 Self::UnexpectedEndOfFile(p) => *p,
46 };
47
48 Span::new(position, Position { offset: position.offset + 1, ..position })
49 }
50}
51
52impl std::fmt::Display for SyntaxError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 let message = match self {
55 Self::UnexpectedToken(token, _) => &format!("Unexpected token `{}` (0x{:02X})", *token as char, token),
56 Self::UnrecognizedToken(token, _) => &format!("Unrecognised token `{}` (0x{:02X})", *token as char, token),
57 Self::UnexpectedEndOfFile(_) => "Unexpected end of file",
58 };
59
60 write!(f, "{message}")
61 }
62}
63
64impl std::fmt::Display for ParseError {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 let message = match self {
67 ParseError::SyntaxError(e) => {
68 return write!(f, "{e}");
69 }
70 ParseError::UnexpectedEndOfFile(expected, _) => {
71 let expected = expected.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join("`, `");
72
73 if expected.is_empty() {
74 "Unexpected end of file".to_string()
75 } else if expected.len() == 1 {
76 format!("Expected `{expected}` before end of file")
77 } else {
78 format!("Expected one of `{expected}` before end of file")
79 }
80 }
81 ParseError::UnexpectedToken(expected, found, _) => {
82 let expected = expected.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join("`, `");
83
84 let found = found.to_string();
85
86 if expected.is_empty() {
87 format!("Unexpected token `{found}`")
88 } else if expected.len() == 1 {
89 format!("Expected `{expected}`, found `{found}`")
90 } else {
91 format!("Expected one of `{expected}`, found `{found}`")
92 }
93 }
94 ParseError::UnclosedLiteralString(kind, _) => match kind {
95 LiteralStringKind::SingleQuoted => "Unclosed single-quoted string".to_string(),
96 LiteralStringKind::DoubleQuoted => "Unclosed double-quoted string".to_string(),
97 },
98 };
99
100 write!(f, "{message}")
101 }
102}
103
104impl std::error::Error for SyntaxError {}
105
106impl std::error::Error for ParseError {
107 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
108 match self {
109 ParseError::SyntaxError(e) => Some(e),
110 _ => None,
111 }
112 }
113}
114impl From<SyntaxError> for Issue {
115 fn from(error: SyntaxError) -> Issue {
116 let position = error.position();
117 let span = Span::new(position, Position { offset: position.offset + 1, ..position });
118
119 Issue::error(error.to_string()).with_annotation(Annotation::primary(span).with_message("Syntax error."))
120 }
121}
122
123impl From<SyntaxError> for ParseError {
124 fn from(error: SyntaxError) -> Self {
125 ParseError::SyntaxError(error)
126 }
127}
128
129impl From<&ParseError> for Issue {
130 fn from(error: &ParseError) -> Self {
131 let span = error.span();
132
133 Issue::error(error.to_string()).with_annotation(Annotation::primary(span).with_message("Invalid syntax."))
134 }
135}