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