Skip to main content

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