Skip to main content

ion_core/
error.rs

1use std::fmt;
2
3#[derive(Debug, Clone)]
4pub struct IonError {
5    pub kind: ErrorKind,
6    pub message: String,
7    pub line: usize,
8    pub col: usize,
9    /// Additional errors (for multi-error reporting from parser).
10    pub additional: Vec<IonError>,
11}
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum ErrorKind {
15    LexError,
16    ParseError,
17    RuntimeError,
18    TypeError,
19    NameError,
20    PropagatedErr,
21    PropagatedNone,
22}
23
24impl IonError {
25    /// Format error with source context showing the offending line.
26    pub fn format_with_source(&self, source: &str) -> String {
27        let mut out = Self::format_single(self, source);
28        for extra in &self.additional {
29            out.push('\n');
30            out.push_str(&Self::format_single(extra, source));
31        }
32        out
33    }
34
35    fn format_single(err: &IonError, source: &str) -> String {
36        let mut out = String::new();
37        // Header
38        out.push_str(&format!(
39            "\x1b[1;31merror[{}]\x1b[0m: {}\n",
40            err.kind_str(),
41            err.message
42        ));
43        // Source context
44        if err.line > 0 {
45            let lines: Vec<&str> = source.lines().collect();
46            if err.line <= lines.len() {
47                let line_str = lines[err.line - 1];
48                let line_num = format!("{}", err.line);
49                let padding = " ".repeat(line_num.len());
50                out.push_str(&format!(" {} \x1b[34m|\x1b[0m\n", padding));
51                out.push_str(&format!(" \x1b[34m{} |\x1b[0m {}\n", line_num, line_str));
52                out.push_str(&format!(" {} \x1b[34m|\x1b[0m ", padding));
53                if err.col > 0 && err.col <= line_str.len() + 1 {
54                    out.push_str(&" ".repeat(err.col - 1));
55                    out.push_str("\x1b[1;31m^\x1b[0m");
56                }
57                out.push('\n');
58            }
59        }
60        // Suggestion hint
61        if let Some(hint) = Self::suggest_hint(&err.kind, &err.message) {
62            out.push_str(&format!(" \x1b[1;36mhelp\x1b[0m: {}\n", hint));
63        }
64        out
65    }
66
67    fn suggest_hint(kind: &ErrorKind, msg: &str) -> Option<&'static str> {
68        match kind {
69            ErrorKind::NameError => {
70                if msg.contains(&*ion_str!("undefined variable")) {
71                    Some(ion_static_str!(
72                        "check spelling, or ensure the variable is declared with `let` before use"
73                    ))
74                } else {
75                    None
76                }
77            }
78            ErrorKind::TypeError => {
79                if msg.contains(&*ion_str!("cannot assign to immutable")) {
80                    Some(ion_static_str!(
81                        "declare with `let mut` to allow reassignment"
82                    ))
83                } else if msg.contains(&*ion_str!("cannot add"))
84                    || msg.contains(&*ion_str!("cannot subtract"))
85                {
86                    Some(ion_static_str!("Ion has no implicit type coercions \u{2014} convert explicitly with `int()`, `float()`, or `str()`"))
87                } else if msg.contains(&*ion_str!("no method")) {
88                    Some(ion_static_str!("use `.to_string()` to inspect the value's type, or check LANGUAGE.md for available methods"))
89                } else {
90                    None
91                }
92            }
93            ErrorKind::ParseError => {
94                if msg.contains(&*ion_str!("expected ';'")) {
95                    Some(ion_static_str!("Ion requires semicolons after statements"))
96                } else if msg.contains(&*ion_str!("expected '}'")) {
97                    Some(ion_static_str!("check for unmatched `{` braces"))
98                } else {
99                    None
100                }
101            }
102            ErrorKind::RuntimeError => {
103                if msg.contains(&*ion_str!("division by zero")) {
104                    Some(ion_static_str!(
105                        "check the divisor before dividing, or use a try/catch block"
106                    ))
107                } else if msg.contains(&*ion_str!("stack overflow")) {
108                    Some(ion_static_str!(
109                        "check for infinite recursion, or increase the stack depth limit"
110                    ))
111                } else if msg.contains(&*ion_str!("index out of bounds")) {
112                    Some(ion_static_str!(
113                        "use `.len()` to check the collection size, or `.get()` for safe access"
114                    ))
115                } else {
116                    None
117                }
118            }
119            _ => None,
120        }
121    }
122
123    fn kind_str(&self) -> &str {
124        match &self.kind {
125            ErrorKind::LexError => ion_static_str!("lex"),
126            ErrorKind::ParseError => ion_static_str!("parse"),
127            ErrorKind::RuntimeError => ion_static_str!("runtime"),
128            ErrorKind::TypeError => ion_static_str!("type"),
129            ErrorKind::NameError => ion_static_str!("name"),
130            ErrorKind::PropagatedErr => ion_static_str!("propagated_err"),
131            ErrorKind::PropagatedNone => ion_static_str!("propagated_none"),
132        }
133    }
134
135    pub fn lex(message: impl Into<String>, line: usize, col: usize) -> Self {
136        Self {
137            kind: ErrorKind::LexError,
138            message: message.into(),
139            line,
140            col,
141            additional: Vec::new(),
142        }
143    }
144
145    pub fn parse(message: impl Into<String>, line: usize, col: usize) -> Self {
146        Self {
147            kind: ErrorKind::ParseError,
148            message: message.into(),
149            line,
150            col,
151            additional: Vec::new(),
152        }
153    }
154
155    pub fn runtime(message: impl Into<String>, line: usize, col: usize) -> Self {
156        Self {
157            kind: ErrorKind::RuntimeError,
158            message: message.into(),
159            line,
160            col,
161            additional: Vec::new(),
162        }
163    }
164
165    pub fn type_err(message: impl Into<String>, line: usize, col: usize) -> Self {
166        Self {
167            kind: ErrorKind::TypeError,
168            message: message.into(),
169            line,
170            col,
171            additional: Vec::new(),
172        }
173    }
174
175    pub fn name(message: impl Into<String>, line: usize, col: usize) -> Self {
176        Self {
177            kind: ErrorKind::NameError,
178            message: message.into(),
179            line,
180            col,
181            additional: Vec::new(),
182        }
183    }
184
185    pub fn propagated_err(message: impl Into<String>, line: usize, col: usize) -> Self {
186        Self {
187            kind: ErrorKind::PropagatedErr,
188            message: message.into(),
189            line,
190            col,
191            additional: Vec::new(),
192        }
193    }
194
195    pub fn propagated_none(line: usize, col: usize) -> Self {
196        Self {
197            kind: ErrorKind::PropagatedNone,
198            message: String::new(),
199            line,
200            col,
201            additional: Vec::new(),
202        }
203    }
204}
205
206impl fmt::Display for IonError {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        let kind = match &self.kind {
209            ErrorKind::LexError => ion_str!("LexError"),
210            ErrorKind::ParseError => ion_str!("ParseError"),
211            ErrorKind::RuntimeError => ion_str!("RuntimeError"),
212            ErrorKind::TypeError => ion_str!("TypeError"),
213            ErrorKind::NameError => ion_str!("NameError"),
214            ErrorKind::PropagatedErr => ion_str!("PropagatedErr"),
215            ErrorKind::PropagatedNone => ion_str!("PropagatedNone"),
216        };
217        write!(
218            f,
219            "{} at {}:{}: {}",
220            kind, self.line, self.col, self.message
221        )
222    }
223}
224
225impl std::error::Error for IonError {}