1use std::{
2 fmt::{self, Display, Formatter},
3 io,
4};
5
6use crate::{
7 common::Pos,
8 eval::{ControlFlow, Type},
9 tokenizer::{Operator, Token, TokenKind},
10};
11
12#[derive(Debug)]
13#[allow(clippy::enum_variant_names)]
15pub enum SeraphineError {
16 TokenizeError(TokenizeError),
17 ParseError(ParseError),
18 EvalError(EvalError),
19 IoError(io::Error),
20}
21
22impl Display for SeraphineError {
23 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
24 use SeraphineError::*;
25 match self {
26 TokenizeError(e) => write!(f, "Tokenize error: {}", e),
27 ParseError(e) => write!(f, "Parse error: {}", e),
28 EvalError(e) => write!(f, "Eval error: {}", e),
29 IoError(e) => write!(f, "IO error: {}", e),
30 }
31 }
32}
33
34impl SeraphineError {
35 pub fn format(&self, input: &str, file_name: &str) -> String {
36 use SeraphineError::*;
37 match self {
38 TokenizeError(e) => e.format(input, file_name),
39 ParseError(e) => e.format(input, file_name),
40 e => e.to_string(),
41 }
42 }
43}
44
45impl From<TokenizeError> for SeraphineError {
46 fn from(e: TokenizeError) -> Self {
47 Self::TokenizeError(e)
48 }
49}
50
51impl From<ParseError> for SeraphineError {
52 fn from(e: ParseError) -> Self {
53 Self::ParseError(e)
54 }
55}
56
57impl From<EvalError> for SeraphineError {
58 fn from(e: EvalError) -> Self {
59 match e {
60 EvalError::Io(e) => Self::IoError(e),
61 _ => Self::EvalError(e),
62 }
63 }
64}
65
66impl From<io::Error> for SeraphineError {
67 fn from(e: io::Error) -> Self {
68 Self::IoError(e)
69 }
70}
71
72#[derive(Debug)]
73pub enum TokenizeError {
74 UnexpectedChar { got: char, pos: Pos },
75 MalformedNumber { number_str: String, pos: Pos },
76 UnterminatedString { pos: Pos },
77}
78
79impl Display for TokenizeError {
80 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81 use TokenizeError::*;
82 match self {
83 UnexpectedChar { got, .. } => write!(f, "Unexpected char {:?}", got),
84 MalformedNumber { number_str, .. } => write!(f, "Malformed number {}", number_str),
85 UnterminatedString { .. } => write!(f, "Unterminated string"),
86 }
87 }
88}
89
90impl TokenizeError {
91 fn format(&self, input: &str, file_name: &str) -> String {
92 let error = self.to_string();
93 match self {
94 Self::UnexpectedChar { pos, .. } => format_error(error, input, file_name, *pos),
95 Self::MalformedNumber { pos, .. } => format_error(error, input, file_name, *pos),
96 Self::UnterminatedString { pos } => format_error(error, input, file_name, *pos),
97 }
98 }
99}
100
101#[derive(Debug)]
102pub enum OperatorKind {
103 Unary,
104 Binary,
105}
106
107impl Display for OperatorKind {
108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 match self {
110 OperatorKind::Unary => write!(f, "unary"),
111 OperatorKind::Binary => write!(f, "binary"),
112 }
113 }
114}
115
116#[derive(Debug)]
117pub enum ParseError {
118 NoTokensLeft,
119 UnexpectedToken {
120 token: Token,
121 expected: Option<TokenKind>,
122 },
123 ExpectedIdentifier {
124 pos: Pos,
125 },
126 InvalidOperator {
127 op: Operator,
128 kind: OperatorKind,
129 pos: Pos,
130 },
131}
132
133impl Display for ParseError {
134 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
135 use ParseError::*;
136 match self {
137 NoTokensLeft => write!(f, "No tokens left to parse"),
138 UnexpectedToken { token, expected } => {
139 write!(f, "Unexpected token '{:?}'", token.kind)?;
140 if let Some(expected) = expected {
141 write!(f, ", expected token '{:?}'", expected)?;
142 }
143 Ok(())
144 }
145 ExpectedIdentifier { .. } => write!(f, "Expected identifier"),
146 InvalidOperator { op, kind, .. } => write!(f, "Invalid {} operator {:?}", kind, op),
147 }
148 }
149}
150
151impl ParseError {
152 fn format(&self, input: &str, file_name: &str) -> String {
153 let error = self.to_string();
154 match self {
155 Self::NoTokensLeft => error,
156 Self::UnexpectedToken { token, .. } => format_error(error, input, file_name, token.pos),
157 Self::ExpectedIdentifier { pos } => format_error(error, input, file_name, *pos),
158 Self::InvalidOperator { pos, .. } => format_error(error, input, file_name, *pos),
159 }
160 }
161}
162
163#[derive(Debug)]
164pub enum EvalError {
165 GenericError(String),
166 VariableNotDefined(String),
167 FunctionWrongArgAmount {
168 name: Option<String>,
169 expected: usize,
170 got: usize,
171 },
172 DuplicateArgName {
173 func_name: Option<String>,
174 arg_name: String,
175 },
176 WrongType {
177 expected: Type,
178 got: Type,
179 },
180 NoSuchMember {
181 r#type: Type,
182 member_name: String,
183 },
184 IndexOutOfBounds {
185 index: usize,
186 length: usize,
187 },
188 TypeError(String),
189 CallStackOverflow,
190 ContinueOutsideOfLoop,
191 BreakOutsideOfLoop,
192 InternalControlFlow(ControlFlow),
193 Io(io::Error),
194}
195
196impl From<io::Error> for EvalError {
197 fn from(e: io::Error) -> Self {
198 Self::Io(e)
199 }
200}
201
202impl Display for EvalError {
203 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204 use EvalError::*;
205 match self {
206 GenericError(msg) => write!(f, "{}", msg),
207 VariableNotDefined(name) => write!(f, "Variable with name '{}' is not defined", name),
208 FunctionWrongArgAmount {
209 name,
210 expected,
211 got,
212 } => {
213 match name {
214 Some(name) => write!(f, "Function '{}'", name)?,
215 None => write!(f, "Unnamed function")?,
216 };
217 write!(
218 f,
219 " was called with {} arguments but expects {}",
220 got, expected
221 )
222 }
223 DuplicateArgName {
224 func_name,
225 arg_name,
226 } => {
227 match func_name {
228 Some(name) => write!(f, "Function '{}'", name)?,
229 None => write!(f, "Unnamed function")?,
230 };
231 write!(f, " has duplicate argument name '{}'", arg_name)
232 }
233 WrongType { expected, got } => {
234 write!(
235 f,
236 "Expected value of type '{}' but got value of type ‘{}' instead",
237 expected, got
238 )
239 }
240 NoSuchMember {
241 r#type: t,
242 member_name,
243 } => {
244 write!(f, "Type '{}' has no member named '{}'", t, member_name)
245 }
246 IndexOutOfBounds { index, length } => {
247 write!(
248 f,
249 "Index {} is out of bounds for list of length {}",
250 index, length
251 )
252 }
253 TypeError(e) => write!(f, "{}", e),
254 CallStackOverflow => write!(f, "Call stack overflow (too many nested function calls)"),
255 ContinueOutsideOfLoop => {
256 write!(f, "Continue statement outside of loop")
257 }
258 BreakOutsideOfLoop => {
259 write!(f, "Break statement outside of loop")
260 }
261 InternalControlFlow(ControlFlow::Return(_)) => {
263 write!(f, "Return statement outside of function")
264 }
265 InternalControlFlow(ControlFlow::Continue) => {
266 write!(f, "Continue statement outside of loop")
267 }
268 InternalControlFlow(ControlFlow::Break) => {
269 write!(f, "Break statement outside of loop")
270 }
271 Io(e) => {
272 write!(f, "IO error: {}", e)
273 }
274 }
275 }
276}
277
278struct ErrorContext {
279 line_num: usize,
280 column_num: usize,
281 line: String,
282}
283
284fn error_pos_to_error_context(input: &str, input_pos: Pos) -> Option<ErrorContext> {
285 let mut line_num = 0;
286 let mut column_num = 0;
287 let mut line_chars = Vec::new();
288
289 let mut chars = input.chars();
290 for _ in 0..input_pos {
291 let c = chars.next();
292 match c {
293 None => return None,
294 Some('\n') => {
296 line_num += 1;
297 column_num = 0;
298 line_chars.clear();
299 }
300 Some(c) => {
301 column_num += 1;
302 line_chars.push(c);
303 }
304 }
305 }
306
307 chars
308 .take_while(|c| c != &'\n')
309 .for_each(|c| line_chars.push(c));
310
311 Some(ErrorContext {
312 line_num,
313 column_num,
314 line: line_chars.into_iter().collect(),
315 })
316}
317
318fn highlight_pos(line: &str, line_num: usize, column_num: usize) -> String {
319 let one_based_line_num = line_num + 1;
320 let one_based_line_num_string = one_based_line_num.to_string();
321 let delimiter = " | ";
322
323 let mut error_string = format!("{}{}{}\n", one_based_line_num_string, delimiter, line);
324 let padding = one_based_line_num_string.len() + delimiter.len() + column_num;
325 for _ in 0..padding {
326 error_string.push(' ');
327 }
328 error_string.push('^');
329 error_string
330}
331
332fn format_pos(file_name: &str, line_num: usize, column_num: usize) -> String {
333 format!("{}:{}:{}", file_name, line_num + 1, column_num + 1)
334}
335
336fn format_error(
337 error_message_prefix: impl Into<String>,
338 input: &str,
339 file_name: &str,
340 pos: Pos,
341) -> String {
342 let mut error_message = error_message_prefix.into();
343 match error_pos_to_error_context(input, pos) {
344 Some(ErrorContext {
345 line_num,
346 column_num,
347 line,
348 }) => {
349 error_message.push_str(&format!(
350 " at {}\n",
351 format_pos(file_name, line_num, column_num)
352 ));
353 error_message.push_str(&highlight_pos(&line, line_num, column_num));
354 }
355 None => {
356 error_message.push_str(&format!(
357 " at position {} which is outside of the input",
358 pos
359 ));
360 }
361 }
362 error_message
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_format_error() {
371 let input = concat!(
372 "fn some_function {\n",
373 " return 42\n",
374 "}\n",
375 "42 + error_here\n"
376 );
377 let got_error = format_error("A test error occured", input, "test_file.sr", 40);
378 let want_error = concat!(
379 "A test error occured at test_file.sr:4:6\n",
380 "4 | 42 + error_here\n",
381 " ^"
382 );
383 assert_eq!(got_error, want_error);
384 }
385}