seraphine_core/
error.rs

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// Clippy warns that the Error suffix should be removed, but it makes sense here
14#[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            // TODO: Change this once returns are allowed outside of functions
262            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            // TODO: Handle "\r\n" once tokenizer can handle it
295            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}