1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, LexerError>;
7
8#[derive(Debug, Clone, Error)]
10pub enum LexerError {
11 #[error("Unterminated string literal starting at position {position}")]
13 UnterminatedString {
14 position: usize,
16 },
17
18 #[error("Unterminated regex starting at position {position}")]
20 UnterminatedRegex {
21 position: usize,
23 },
24
25 #[error("Invalid escape sequence '\\{char}' at position {position}")]
27 InvalidEscape {
28 char: char,
30 position: usize,
32 },
33
34 #[error("Invalid numeric literal at position {position}: {reason}")]
36 InvalidNumber {
37 position: usize,
39 reason: String,
41 },
42
43 #[error("Unexpected character '{char}' at position {position}")]
45 UnexpectedChar {
46 char: char,
48 position: usize,
50 },
51
52 #[error("Invalid UTF-8 at position {position}")]
54 InvalidUtf8 {
55 position: usize,
57 },
58
59 #[error("Heredoc error at position {position}: {reason}")]
61 HeredocError {
62 position: usize,
64 reason: String,
66 },
67
68 #[error("{0}")]
70 Other(String),
71}
72
73impl LexerError {
74 pub fn position(&self) -> Option<usize> {
76 match self {
77 LexerError::UnterminatedString { position }
78 | LexerError::UnterminatedRegex { position }
79 | LexerError::InvalidEscape { position, .. }
80 | LexerError::InvalidNumber { position, .. }
81 | LexerError::UnexpectedChar { position, .. }
82 | LexerError::InvalidUtf8 { position }
83 | LexerError::HeredocError { position, .. } => Some(*position),
84 LexerError::Other(_) => None,
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::LexerError;
92
93 #[test]
96 fn lexer_error_unterminated_string_returns_position() -> Result<(), Box<dyn std::error::Error>>
97 {
98 let err = LexerError::UnterminatedString { position: 7 };
99 assert_eq!(err.position(), Some(7));
100 Ok(())
101 }
102
103 #[test]
104 fn lexer_error_unterminated_regex_returns_position() -> Result<(), Box<dyn std::error::Error>> {
105 let err = LexerError::UnterminatedRegex { position: 42 };
106 assert_eq!(err.position(), Some(42));
107 Ok(())
108 }
109
110 #[test]
111 fn lexer_error_invalid_escape_returns_position() -> Result<(), Box<dyn std::error::Error>> {
112 let err = LexerError::InvalidEscape { char: 'z', position: 15 };
113 assert_eq!(err.position(), Some(15));
114 Ok(())
115 }
116
117 #[test]
118 fn lexer_error_invalid_number_returns_position() -> Result<(), Box<dyn std::error::Error>> {
119 let err = LexerError::InvalidNumber { position: 3, reason: "bad digit".into() };
120 assert_eq!(err.position(), Some(3));
121 Ok(())
122 }
123
124 #[test]
125 fn lexer_error_unexpected_char_returns_position() -> Result<(), Box<dyn std::error::Error>> {
126 let err = LexerError::UnexpectedChar { char: '@', position: 99 };
127 assert_eq!(err.position(), Some(99));
128 Ok(())
129 }
130
131 #[test]
132 fn lexer_error_invalid_utf8_returns_position() -> Result<(), Box<dyn std::error::Error>> {
133 let err = LexerError::InvalidUtf8 { position: 0 };
134 assert_eq!(err.position(), Some(0));
135 Ok(())
136 }
137
138 #[test]
139 fn lexer_error_heredoc_error_returns_position() -> Result<(), Box<dyn std::error::Error>> {
140 let err = LexerError::HeredocError { position: 256, reason: "no terminator".into() };
141 assert_eq!(err.position(), Some(256));
142 Ok(())
143 }
144
145 #[test]
148 fn lexer_error_other_returns_none() -> Result<(), Box<dyn std::error::Error>> {
149 let err = LexerError::Other("something went wrong".into());
150 assert_eq!(err.position(), None);
151 Ok(())
152 }
153
154 #[test]
157 fn lexer_error_position_zero_is_returned_correctly() -> Result<(), Box<dyn std::error::Error>> {
158 let err = LexerError::UnterminatedString { position: 0 };
159 assert_eq!(err.position(), Some(0));
160 Ok(())
161 }
162
163 #[test]
164 fn lexer_error_position_usize_max_is_returned_correctly()
165 -> Result<(), Box<dyn std::error::Error>> {
166 let err = LexerError::UnterminatedString { position: usize::MAX };
167 assert_eq!(err.position(), Some(usize::MAX));
168 Ok(())
169 }
170}