Skip to main content

mago_type_syntax/
error.rs

1use serde::Serialize;
2
3use mago_database::file::FileId;
4use mago_span::HasSpan;
5use mago_span::Position;
6use mago_span::Span;
7
8use crate::token::TypeTokenKind;
9
10#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
11pub enum SyntaxError {
12    UnexpectedToken(FileId, u8, Position),
13    UnrecognizedToken(FileId, u8, Position),
14    UnexpectedEndOfFile(FileId, Position),
15}
16
17#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
18pub enum ParseError {
19    SyntaxError(SyntaxError),
20    UnexpectedEndOfFile(FileId, Vec<TypeTokenKind>, Position),
21    UnexpectedToken(Vec<TypeTokenKind>, TypeTokenKind, Span),
22    UnclosedLiteralString(Span),
23}
24
25impl ParseError {
26    /// Provides a detailed, user-friendly note explaining the context of the parse error.
27    #[must_use]
28    pub fn note(&self) -> String {
29        match self {
30            ParseError::SyntaxError(SyntaxError::UnrecognizedToken(_, _, _)) => {
31                "An invalid character was found that is not part of any valid type syntax.".to_string()
32            }
33            ParseError::SyntaxError(_) => {
34                "A low-level syntax error occurred while parsing the type string.".to_string()
35            }
36            ParseError::UnexpectedEndOfFile(_, expected, _) => {
37                if expected.is_empty() {
38                    "The type declaration ended prematurely.".to_string()
39                } else {
40                    let expected_str = expected.iter().map(|t| format!("`{t}`")).collect::<Vec<_>>().join(" or ");
41                    format!("The parser reached the end of the input but expected one of: {expected_str}.")
42                }
43            }
44            ParseError::UnexpectedToken(expected, _, _) => {
45                if expected.is_empty() {
46                    "The parser encountered a token that was not expected at this position.".to_string()
47                } else {
48                    let expected_str = expected.iter().map(|t| format!("`{t}`")).collect::<Vec<_>>().join(" or ");
49                    format!("The parser expected one of the following here: {expected_str}.")
50                }
51            }
52            ParseError::UnclosedLiteralString(_) => {
53                "String literals within type declarations must be closed with a matching quote.".to_string()
54            }
55        }
56    }
57
58    /// Provides a concise, actionable help message suggesting a fix for the error.
59    #[must_use]
60    pub fn help(&self) -> String {
61        match self {
62            ParseError::SyntaxError(SyntaxError::UnrecognizedToken(_, _, _)) => {
63                "Remove or replace the invalid character.".to_string()
64            }
65            ParseError::SyntaxError(_) => "Review the syntax of the type declaration for errors.".to_string(),
66            ParseError::UnexpectedEndOfFile(_, _, _) => {
67                "Complete the type declaration. Check for unclosed parentheses `()`, angle brackets `<>`, or curly braces `{}`.".to_string()
68            }
69            ParseError::UnexpectedToken(_, _, _) => {
70                "Review the type syntax near the unexpected token.".to_string()
71            }
72            ParseError::UnclosedLiteralString(_) => {
73                "Add a closing quote (`'` or `\"`) to complete the string literal.".to_string()
74            }
75        }
76    }
77}
78
79impl HasSpan for SyntaxError {
80    fn span(&self) -> Span {
81        let (file_id, position) = match self {
82            SyntaxError::UnexpectedToken(file_id, _, position) => (*file_id, *position),
83            SyntaxError::UnrecognizedToken(file_id, _, position) => (*file_id, *position),
84            SyntaxError::UnexpectedEndOfFile(file_id, position) => (*file_id, *position),
85        };
86
87        Span::new(file_id, position, position)
88    }
89}
90
91impl HasSpan for ParseError {
92    fn span(&self) -> Span {
93        match self {
94            ParseError::SyntaxError(error) => error.span(),
95            ParseError::UnexpectedEndOfFile(file_id, _, position) => Span::new(*file_id, *position, *position),
96            ParseError::UnexpectedToken(_, _, span) => *span,
97            ParseError::UnclosedLiteralString(span) => *span,
98        }
99    }
100}
101
102impl std::fmt::Display for SyntaxError {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            SyntaxError::UnexpectedToken(_, token, _) => {
106                write!(f, "Unexpected character '{}'", *token as char)
107            }
108            SyntaxError::UnrecognizedToken(_, token, _) => {
109                write!(f, "Unrecognized character '{}'", *token as char)
110            }
111            SyntaxError::UnexpectedEndOfFile(_, _) => {
112                write!(f, "Unexpected end of input")
113            }
114        }
115    }
116}
117
118impl std::fmt::Display for ParseError {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        match self {
121            ParseError::SyntaxError(err) => write!(f, "{err}"),
122            ParseError::UnexpectedEndOfFile(_, _, _) => {
123                write!(f, "Unexpected end of type declaration")
124            }
125            ParseError::UnexpectedToken(_, token, _) => {
126                write!(f, "Unexpected token `{token}`")
127            }
128            ParseError::UnclosedLiteralString(_) => {
129                write!(f, "Unclosed string literal in type")
130            }
131        }
132    }
133}
134
135impl std::error::Error for SyntaxError {
136    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
137        None
138    }
139}
140
141impl std::error::Error for ParseError {
142    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143        match self {
144            ParseError::SyntaxError(err) => Some(err),
145            _ => None,
146        }
147    }
148}
149
150impl From<SyntaxError> for ParseError {
151    fn from(error: SyntaxError) -> Self {
152        ParseError::SyntaxError(error)
153    }
154}