Skip to main content

atoxide_parser/
error.rs

1//! Error types for the Ato parser with miette integration.
2
3// False positive from thiserror/miette derive macros on Rust 1.93+
4#![allow(unused_assignments)]
5
6use atoxide_lexer::{Span, TokenKind};
7use miette::{Diagnostic, SourceSpan};
8use thiserror::Error;
9
10/// A parse error with rich context for display.
11#[derive(Debug, Clone, Error, Diagnostic)]
12pub enum ParseError {
13    #[error("unexpected token")]
14    #[diagnostic(code(ato::parse::unexpected_token))]
15    UnexpectedToken {
16        expected: String,
17        found: TokenKind,
18        #[label("found {found:?} here")]
19        span: SourceSpan,
20    },
21
22    #[error("unexpected end of file")]
23    #[diagnostic(code(ato::parse::unexpected_eof))]
24    UnexpectedEof {
25        expected: String,
26        #[label("expected {expected}")]
27        span: SourceSpan,
28    },
29
30    #[error("expected {expected}")]
31    #[diagnostic(code(ato::parse::expected))]
32    Expected {
33        expected: String,
34        #[label("expected {expected} here")]
35        span: SourceSpan,
36    },
37
38    #[error("invalid number literal")]
39    #[diagnostic(code(ato::parse::invalid_number))]
40    InvalidNumber {
41        #[label("invalid number")]
42        span: SourceSpan,
43    },
44
45    #[error("invalid indentation")]
46    #[diagnostic(code(ato::parse::invalid_indentation))]
47    InvalidIndentation {
48        #[label("expected indented block")]
49        span: SourceSpan,
50    },
51
52    #[error("mixed connection operators")]
53    #[diagnostic(
54        code(ato::parse::mixed_connection),
55        help("use either ~> or <~ consistently, not both")
56    )]
57    MixedConnectionOperators {
58        #[label("mixed operators in connection chain")]
59        span: SourceSpan,
60    },
61
62    #[error("empty block")]
63    #[diagnostic(
64        code(ato::parse::empty_block),
65        help("add a 'pass' statement if the block should be empty")
66    )]
67    EmptyBlock {
68        #[label("block has no statements")]
69        span: SourceSpan,
70    },
71
72    #[error("lexer error: {message}")]
73    #[diagnostic(code(ato::lexer::error))]
74    LexerError {
75        message: String,
76        #[label("{message}")]
77        span: SourceSpan,
78    },
79}
80
81impl ParseError {
82    /// Create an unexpected token error.
83    pub fn unexpected_token(expected: impl Into<String>, found: TokenKind, span: Span) -> Self {
84        ParseError::UnexpectedToken {
85            expected: expected.into(),
86            found,
87            span: span_to_source_span(span),
88        }
89    }
90
91    /// Create an unexpected EOF error.
92    pub fn unexpected_eof(expected: impl Into<String>, span: Span) -> Self {
93        ParseError::UnexpectedEof {
94            expected: expected.into(),
95            span: span_to_source_span(span),
96        }
97    }
98
99    /// Create an expected error.
100    pub fn expected(expected: impl Into<String>, span: Span) -> Self {
101        ParseError::Expected {
102            expected: expected.into(),
103            span: span_to_source_span(span),
104        }
105    }
106
107    /// Create an invalid number error.
108    pub fn invalid_number(span: Span) -> Self {
109        ParseError::InvalidNumber {
110            span: span_to_source_span(span),
111        }
112    }
113
114    /// Create an invalid indentation error.
115    pub fn invalid_indentation(span: Span) -> Self {
116        ParseError::InvalidIndentation {
117            span: span_to_source_span(span),
118        }
119    }
120
121    /// Create a mixed connection operators error.
122    pub fn mixed_connection_operators(span: Span) -> Self {
123        ParseError::MixedConnectionOperators {
124            span: span_to_source_span(span),
125        }
126    }
127
128    /// Create an empty block error.
129    pub fn empty_block(span: Span) -> Self {
130        ParseError::EmptyBlock {
131            span: span_to_source_span(span),
132        }
133    }
134
135    /// Create a lexer error.
136    pub fn lexer_error(message: impl Into<String>, span: Span) -> Self {
137        ParseError::LexerError {
138            message: message.into(),
139            span: span_to_source_span(span),
140        }
141    }
142}
143
144/// Convert our Span to miette's SourceSpan.
145fn span_to_source_span(span: Span) -> SourceSpan {
146    SourceSpan::new(span.start.into(), span.end - span.start)
147}
148
149/// Result type for parsing operations.
150pub type ParseResult<T> = Result<T, ParseError>;