mago_syntax/
error.rs

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