Skip to main content

mate_rs/
errors.rs

1//
2// Copyright 2022-present theiskaa. All rights reserved.
3// Use of this source code is governed by MIT license
4// that can be found in the LICENSE file.
5//
6
7use crate::token::Token;
8use std::fmt;
9
10// Main structure model for errors of lexer.
11#[derive(Clone, Debug, PartialEq)]
12pub struct Error {
13    msg: String,
14}
15
16impl Error {
17    pub fn new(msg: String) -> Self {
18        Self { msg }
19    }
20
21    // The error template used to generate cool error messages by input, invalid token, title of
22    // error and explanation of error.
23    // Generated error would be like:
24    //
25    // ```
26    // <your {err} title here>
27    //
28    //      "<your input here>"
29    //         |
30    //         | > Your detailed error
31    //         | > explanation here.
32    // ```
33    fn indexed_error(input: String, point: i32, err: String, expl: Vec<&str>) -> Self {
34        let mut message = err;
35
36        let tab = "     ";
37        let space_count = if point > 1 { (point - 1) as usize } else { 0 };
38        let space: String = " ".repeat(space_count);
39
40        message.push_str(&format!("{tab}\"{}\" \n", input.trim_end()));
41        for exp in expl.iter() {
42            message.push_str(&format!(" {tab}{space}{exp}\n"));
43        }
44
45        Self { msg: message }
46    }
47
48    // A custom early made error for empty input cases.
49    pub fn empty_input() -> Self {
50        Self {
51            msg: String::from("error: cannot parse an empty input"),
52        }
53    }
54
55    // A custom early made error for empty tokens cases.
56    pub fn empty_tokens() -> Self {
57        Self {
58            msg: String::from("error: cannot calculate result from an empty token list"),
59        }
60    }
61
62    // A custom early made error for invalid tokens cases.
63    // Looks like:
64    //
65    // ```
66    // [!] error: missing some tokens to calculate result
67    //
68    //      "<your input here [X]>"
69    //                         |
70    //                         | > Cannot convert the character
71    //                         | > that represented as number,
72    //                         | > to the actual number representation.
73    // ```
74    //
75    pub fn missing_some_tokens(input: String, point: i32) -> Self {
76        let message = "error: missing some tokens to calculate result\n\n".to_string();
77
78        let mut inpt: String = input.trim_end().to_string();
79        let pointer = " {X} ";
80
81        for (i, pch) in pointer.chars().enumerate() {
82            let p: i32 = point + (i as i32) + 1;
83
84            let backid: usize = if p < 1 { 0 } else { (p - 1) as usize };
85
86            let back_ch = inpt.chars().nth(backid).unwrap_or('0');
87            let next_ch = inpt.chars().nth((p + 1) as usize).unwrap_or('0');
88
89            if (back_ch == ' ' || next_ch == ' ') && pch == ' ' {
90                continue;
91            }
92
93            inpt.insert(p as usize, pch);
94        }
95
96        // A split list of error explanation.
97        let explanation: Vec<&str> = Vec::from([
98            "|",
99            "| > Expected a token character.",
100            "| > hint: `42`, `+`, `-`, `/`, `*`, `%`, `^`.",
101        ]);
102
103        Error::indexed_error(inpt, point + 4, message, explanation)
104    }
105
106    // A custom [indexed_error] implementation for rust string -> to -> number parsing error.
107    // Looks like:
108    //
109    // ```
110    // error: cannot parse token literal: `<token-literal>` to a number
111    //
112    //      "<your input here>"
113    //         |
114    //         | > Cannot convert the character (that represented
115    //         | > as number) to the actual number representation.
116    // ```
117    pub fn cannot_parse_to_number(input: String, token: Token) -> Self {
118        let message = format!(
119            "error: cannot parse token literal: `{}` to a number\n\n",
120            token.literal
121        );
122
123        // A split list of error explanation.
124        let explanation: Vec<&str> = Vec::from([
125            "|",
126            "| > Cannot convert the character (that represented",
127            "| > as number) to the actual number representation.",
128        ]);
129
130        Error::indexed_error(input, token.index.1 + 1, message, explanation)
131    }
132
133    // A custom early made error for invalid order case of token characters.
134    pub fn invalid_order() -> Self {
135        let space = "      ";
136        let mut msg = String::from("error: invalid order of token characters\n");
137
138        msg.push_str(&format!("{space}A valid token/character order is:"));
139        msg.push_str(&format!("{space}[Numerable], [Operation], [Numerable]"));
140
141        Self { msg }
142    }
143
144    pub fn illegal_token(input: String, token: Token) -> Self {
145        let message = format!(
146            "error: found an illegal character: `{}` \n\n",
147            token.literal
148        );
149
150        // A split list of error explanation.
151        let explanation: Vec<&str> = Vec::from([
152            "|",
153            "| > We do not know how to parse this character",
154            "| > If you think this is a bug or a practical feature",
155            "| > that we do not have yet, please open an issue:",
156            "| >   -> https://github.com/theiskaa/mate/issues/new",
157        ]);
158
159        Error::indexed_error(input, token.index.1 + 1, message, explanation)
160    }
161
162    // A custom error for division by zero cases.
163    pub fn division_by_zero(input: String, point: i32) -> Self {
164        let message = String::from("error: division by zero\n\n");
165
166        let explanation: Vec<&str> = Vec::from([
167            "|",
168            "| > Cannot divide by zero.",
169            "| > hint: ensure the divisor is not zero.",
170        ]);
171
172        Error::indexed_error(input, point, message, explanation)
173    }
174
175    pub fn mismatched_parentheses(input: String, point: i32) -> Self {
176        let message = String::from("error: mismatched parentheses or brackets\n\n");
177
178        let explanation: Vec<&str> = Vec::from([
179            "|",
180            "| > Found a closing bracket without a matching opening bracket,",
181            "| > or brackets are mismatched (e.g., '(' closed with ']').",
182            "| > hint: ensure all brackets are properly paired.",
183        ]);
184
185        Error::indexed_error(input, point, message, explanation)
186    }
187}
188
189impl fmt::Display for Error {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        write!(f, "{}", self.msg)
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn new() {
201        let result: Error = Error::new(String::from("test message"));
202        assert_eq!(result.msg, String::from("test message"));
203    }
204
205    #[test]
206    fn empty_input() {
207        let result: Error = Error::empty_input();
208        assert_eq!(
209            result.msg,
210            String::from("error: cannot parse an empty input")
211        );
212    }
213
214    #[test]
215    fn empty_tokens() {
216        let result: Error = Error::empty_tokens();
217        assert_eq!(
218            result.msg,
219            String::from("error: cannot calculate result from an empty token list")
220        );
221    }
222
223    #[test]
224    fn display() {
225        let error: Error = Error::new(String::from("A new message"));
226        assert_eq!(format!("{}", error), error.msg)
227    }
228}