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 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 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 out.push_str(&format!(
39 "\x1b[1;31merror[{}]\x1b[0m: {}\n",
40 err.kind_str(),
41 err.message
42 ));
43 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 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 {}