1use std::fmt;
2use std::rc::Rc;
3
4use crate::value::Value;
5
6pub const RECURSION_LIMIT: usize = 1000;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ErrorKind {
14 TypeError,
16 DivisionByZero,
18 Overflow,
23 IndexOutOfBounds,
25 KeyError,
27 ValueError,
29 IOError,
32 AttrError,
35 Bonk,
37 AssertError,
39 RecursionLimit,
41}
42
43impl ErrorKind {
44 pub fn as_str(self) -> &'static str {
46 match self {
47 ErrorKind::TypeError => "TypeError",
48 ErrorKind::DivisionByZero => "DivisionByZero",
49 ErrorKind::Overflow => "Overflow",
50 ErrorKind::IndexOutOfBounds => "IndexOutOfBounds",
51 ErrorKind::KeyError => "KeyError",
52 ErrorKind::ValueError => "ValueError",
53 ErrorKind::IOError => "IOError",
54 ErrorKind::AttrError => "AttrError",
55 ErrorKind::Bonk => "Bonk",
56 ErrorKind::AssertError => "AssertError",
57 ErrorKind::RecursionLimit => "RecursionLimit",
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ErrorLocation {
67 pub file: Rc<str>,
68 pub line: u32,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct DogeError {
74 pub kind: ErrorKind,
75 pub message: String,
76 pub location: Option<ErrorLocation>,
80}
81
82impl DogeError {
83 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
84 DogeError {
85 kind,
86 message: message.into(),
87 location: None,
88 }
89 }
90
91 pub fn type_error(message: impl Into<String>) -> Self {
92 DogeError::new(ErrorKind::TypeError, message)
93 }
94
95 pub fn division_by_zero(message: impl Into<String>) -> Self {
96 DogeError::new(ErrorKind::DivisionByZero, message)
97 }
98
99 pub fn overflow(message: impl Into<String>) -> Self {
100 DogeError::new(ErrorKind::Overflow, message)
101 }
102
103 pub fn index_out_of_bounds(message: impl Into<String>) -> Self {
104 DogeError::new(ErrorKind::IndexOutOfBounds, message)
105 }
106
107 pub fn key_error(message: impl Into<String>) -> Self {
108 DogeError::new(ErrorKind::KeyError, message)
109 }
110
111 pub fn value_error(message: impl Into<String>) -> Self {
112 DogeError::new(ErrorKind::ValueError, message)
113 }
114
115 pub fn attr_error(message: impl Into<String>) -> Self {
116 DogeError::new(ErrorKind::AttrError, message)
117 }
118
119 pub fn io_error(message: impl Into<String>) -> Self {
120 DogeError::new(ErrorKind::IOError, message)
121 }
122}
123
124impl fmt::Display for DogeError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}", self.message)
127 }
128}
129
130impl std::error::Error for DogeError {}
131
132#[derive(Debug)]
135pub struct ErrorData {
136 pub kind: ErrorKind,
137 pub message: Rc<str>,
138 pub file: Rc<str>,
139 pub line: u32,
140}
141
142pub fn bonk_error(value: &Value) -> DogeError {
147 match value {
148 Value::Error(e) => DogeError {
149 kind: e.kind,
150 message: e.message.to_string(),
151 location: Some(ErrorLocation {
152 file: e.file.clone(),
153 line: e.line,
154 }),
155 },
156 _ => DogeError::new(ErrorKind::Bonk, value.to_string()),
157 }
158}
159
160const ASSERT_DEFAULT_MESSAGE: &str = "such amaze. much false.";
162
163pub fn assert_error(message: Option<&Value>) -> DogeError {
167 let text = match message {
168 Some(value) => value.to_string(),
169 None => ASSERT_DEFAULT_MESSAGE.to_string(),
170 };
171 DogeError::new(ErrorKind::AssertError, text)
172}
173
174pub fn error_value(err: &DogeError, file: &str, line: u32) -> Value {
178 let (file, line) = match &err.location {
179 Some(loc) => (loc.file.clone(), loc.line),
180 None => (Rc::from(file), line),
181 };
182 Value::error(err.kind, &err.message, file, line)
183}
184
185pub fn error_field(err: &ErrorData, name: &str) -> DogeResult {
188 match name {
189 "type" => Ok(Value::str(err.kind.as_str())),
190 "message" => Ok(Value::Str(err.message.clone())),
191 "file" => Ok(Value::Str(err.file.clone())),
192 "line" => Ok(Value::int(err.line)),
193 _ => Err(DogeError::attr_error(format!(
194 "an Error has no field {name}"
195 ))),
196 }
197}
198
199pub fn enter_call(depth: &mut usize) -> DogeResult<()> {
202 if *depth >= RECURSION_LIMIT {
203 return Err(DogeError::new(
204 ErrorKind::RecursionLimit,
205 "too much recursion — more than 1000 calls deep",
206 ));
207 }
208 *depth += 1;
209 Ok(())
210}
211
212pub fn exit_call(depth: &mut usize) {
214 *depth = depth.saturating_sub(1);
215}
216
217pub type DogeResult<T = crate::Value> = Result<T, DogeError>;
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn bonk_error_message_is_the_barked_form() {
228 assert_eq!(bonk_error(&Value::int(5)).message, "5");
229 assert_eq!(bonk_error(&Value::str("much fail")).message, "much fail");
230 assert_eq!(bonk_error(&Value::int(5)).kind, ErrorKind::Bonk);
231 }
232
233 #[test]
234 fn assert_error_uses_message_or_default() {
235 let with_message = assert_error(Some(&Value::str("age much wrong")));
236 assert_eq!(with_message.kind, ErrorKind::AssertError);
237 assert_eq!(with_message.message, "age much wrong");
238
239 let without = assert_error(None);
240 assert_eq!(without.kind, ErrorKind::AssertError);
241 assert_eq!(without.message, ASSERT_DEFAULT_MESSAGE);
242 }
243
244 #[test]
245 fn re_bonking_an_error_preserves_type_and_location() {
246 let caught = error_value(&DogeError::key_error("no such key"), "main.doge", 7);
247 let re_raised = bonk_error(&caught);
248 assert_eq!(re_raised.kind, ErrorKind::KeyError);
249 assert_eq!(re_raised.message, "no such key");
250 let loc = re_raised
251 .location
252 .expect("re-raised error keeps its location");
253 assert_eq!(&*loc.file, "main.doge");
254 assert_eq!(loc.line, 7);
255 }
256
257 #[test]
258 fn error_value_carries_type_message_and_catch_site() {
259 let err = DogeError::type_error("nope");
260 match error_value(&err, "script.doge", 3) {
261 Value::Error(e) => {
262 assert_eq!(e.kind, ErrorKind::TypeError);
263 assert_eq!(&*e.message, "nope");
264 assert_eq!(&*e.file, "script.doge");
265 assert_eq!(e.line, 3);
266 }
267 other => panic!("expected an Error, got {other:?}"),
268 }
269 }
270
271 #[test]
272 fn error_value_prefers_an_embedded_location_over_the_catch_site() {
273 let raised = error_value(&DogeError::overflow("too big"), "raise.doge", 2);
274 let re_raised = error_value(&bonk_error(&raised), "catch.doge", 99);
275 match re_raised {
276 Value::Error(e) => {
277 assert_eq!(&*e.file, "raise.doge");
278 assert_eq!(e.line, 2);
279 }
280 other => panic!("expected an Error, got {other:?}"),
281 }
282 }
283
284 #[test]
285 fn error_field_reads_the_four_fields_and_rejects_others() {
286 let value = error_value(&DogeError::value_error("bad"), "f.doge", 4);
287 let Value::Error(e) = value else {
288 panic!("expected an Error");
289 };
290 assert!(matches!(error_field(&e, "type").unwrap(), Value::Str(s) if &*s == "ValueError"));
291 assert!(matches!(error_field(&e, "message").unwrap(), Value::Str(s) if &*s == "bad"));
292 assert!(matches!(error_field(&e, "file").unwrap(), Value::Str(s) if &*s == "f.doge"));
293 assert!(crate::values_equal(
294 &error_field(&e, "line").unwrap(),
295 &Value::int(4)
296 ));
297 assert_eq!(
298 error_field(&e, "nope").unwrap_err().kind,
299 ErrorKind::AttrError
300 );
301 }
302
303 #[test]
304 fn enter_call_errors_past_limit() {
305 let mut depth = 0;
306 for _ in 0..RECURSION_LIMIT {
307 enter_call(&mut depth).expect("within the limit");
308 }
309 let err = enter_call(&mut depth).expect_err("one past the limit");
310 assert_eq!(err.kind, ErrorKind::RecursionLimit);
311 assert_eq!(depth, RECURSION_LIMIT);
312 }
313
314 #[test]
315 fn exit_call_saturates_at_zero() {
316 let mut depth = 0;
317 exit_call(&mut depth);
318 assert_eq!(depth, 0);
319 }
320}