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