1use std::sync::Arc;
3
4use compact_str::CompactString;
5use thiserror::Error;
6
7use crate::{
8 parser::{ast::AstParseError, span::SpanWithSource},
9 val::custom::CustomValError,
10};
11
12pub type VmResult<T> = Result<T, VmError>;
14
15#[derive(Debug, PartialEq)]
17pub enum VmError {
18 TypeError {
19 src: Option<SpanWithSource<Arc<str>>>,
20 context: &'static str,
21 expected: &'static str,
22 actual: &'static str,
23 value: String,
24 },
25 ArityError {
26 function: CompactString,
27 expected: usize,
28 actual: usize,
29 },
30 CompileError(CompileError),
31 InvalidVmState(BacktraceError),
32 SymbolNotDefined {
33 src: Option<SpanWithSource<Arc<str>>>,
34 symbol: String,
35 },
36 MaximumFunctionCallDepth {
37 max_depth: usize,
38 call_stack: Vec<CompactString>,
39 },
40 CustomValError(CustomValError),
41 CustomError(String),
42}
43
44impl VmError {
45 pub fn with_src(self, src: SpanWithSource<Arc<str>>) -> VmError {
47 match self {
48 VmError::TypeError {
49 src: _src,
50 context,
51 expected,
52 actual,
53 value,
54 } => VmError::TypeError {
55 src: Some(src),
56 context,
57 expected,
58 actual,
59 value,
60 },
61 VmError::ArityError {
62 function,
63 expected,
64 actual,
65 } => VmError::ArityError {
66 function,
67 expected,
68 actual,
69 },
70 VmError::CompileError(e) => VmError::CompileError(e),
71 VmError::InvalidVmState(e) => VmError::InvalidVmState(e),
72 VmError::SymbolNotDefined { symbol, .. } => VmError::SymbolNotDefined {
73 src: Some(src),
74 symbol,
75 },
76 VmError::MaximumFunctionCallDepth {
77 max_depth,
78 call_stack,
79 } => VmError::MaximumFunctionCallDepth {
80 max_depth,
81 call_stack,
82 },
83 VmError::CustomValError(e) => VmError::CustomValError(e),
84 VmError::CustomError(e) => VmError::CustomError(e),
85 }
86 }
87}
88
89impl std::fmt::Display for VmError {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 let format_src = |f: &mut std::fmt::Formatter<'_>,
92 src: &Option<SpanWithSource<Arc<str>>>| {
93 if let Some(src) = src {
94 write!(f, "\n{}\n", src.contextual_formatter())?;
95 }
96 Ok(())
97 };
98 match self {
99 VmError::TypeError {
100 src,
101 context,
102 expected,
103 actual,
104 value,
105 } => {
106 write!(
107 f,
108 "{context} expected type {expected} but got {actual}: {value}"
109 )?;
110 format_src(f, src)
111 }
112 VmError::ArityError {
113 function,
114 expected,
115 actual,
116 } => write!(f, "{function} expected {expected} args but got {actual}."),
117 VmError::CompileError(e) => write!(f, "{e}"),
118 VmError::InvalidVmState(bt) => write!(f, "VM reached invalid state.\n{bt}"),
119 VmError::SymbolNotDefined { symbol, src } => {
120 write!(f, "Value {symbol} is not defined.")?;
121 format_src(f, src)
122 }
123 VmError::MaximumFunctionCallDepth {
124 max_depth,
125 call_stack,
126 } => write!(
127 f,
128 "Maximum function call depth of {max_depth} reached: {call_stack:#?}"
129 ),
130 VmError::CustomValError(e) => write!(f, "{e}"),
131 VmError::CustomError(e) => write!(f, "{e}"),
132 }
133 }
134}
135
136impl std::error::Error for VmError {
137 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138 match self {
139 VmError::TypeError { .. }
140 | VmError::ArityError { .. }
141 | VmError::SymbolNotDefined { .. }
142 | VmError::MaximumFunctionCallDepth { .. }
143 | VmError::CustomError(_) => None,
144 VmError::CompileError(e) => Some(e),
145 VmError::InvalidVmState(e) => Some(e),
146 VmError::CustomValError(e) => Some(e),
147 }
148 }
149}
150
151impl From<String> for VmError {
152 fn from(v: String) -> VmError {
153 VmError::CustomError(v)
154 }
155}
156
157impl From<BacktraceError> for VmError {
158 fn from(v: BacktraceError) -> VmError {
159 VmError::InvalidVmState(v)
160 }
161}
162
163impl From<CompileError> for VmError {
164 fn from(v: CompileError) -> VmError {
165 VmError::CompileError(v)
166 }
167}
168
169impl From<CustomValError> for VmError {
170 fn from(v: CustomValError) -> VmError {
171 VmError::CustomValError(v)
172 }
173}
174
175pub struct BacktraceError(std::backtrace::Backtrace);
177
178impl BacktraceError {
179 #[inline(always)]
180 pub fn capture() -> BacktraceError {
181 BacktraceError(std::backtrace::Backtrace::capture())
182 }
183}
184
185impl std::error::Error for BacktraceError {}
186
187impl std::fmt::Display for BacktraceError {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 self.0.fmt(f)
190 }
191}
192
193impl std::fmt::Debug for BacktraceError {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 self.0.fmt(f)
196 }
197}
198
199impl PartialEq for BacktraceError {
200 fn eq(&self, _: &Self) -> bool {
203 false
204 }
205}
206
207#[derive(Debug, Error, PartialEq)]
209pub enum CompileError {
210 #[error("syntax error occurred: {0}")]
211 AstError(#[from] AstParseError),
212 #[error("found unexpected empty expression")]
213 EmptyExpression,
214 #[error("constant {0} is not callable")]
215 ConstantNotCallable(String),
216 #[error("expression {expression} expected {expected} arguments but found {actual}")]
217 ExpressionHasWrongArgs {
218 expression: &'static str,
219 expected: usize,
220 actual: usize,
221 },
222 #[error("expected an identifier")]
223 ExpectedIdentifier,
224 #[error("{context} expected expression but sub-expression is not a valid expression")]
225 ExpectedExpression { context: &'static str },
226 #[error("define is not allowed in this context, define is only allowed at the top level")]
227 DefineNotAllowed,
228 #[error("{context} expected identifier list")]
229 ExpectedIdentifierList { context: &'static str },
230 #[error("let expected form: (let ([binding-a expr-a] [binding-b expr-b] ..) (exprs..))")]
231 BadLetBindings,
232 #[error("argument {0} was defined multiple times")]
233 ArgumentDefinedMultipleTimes(CompactString),
234}
235
236#[cfg(test)]
237mod tests {
238 use std::error::Error;
239
240 use crate::Vm;
241
242 use super::*;
243
244 #[test]
245 fn vm_error_can_print_out_related_source_code() {
246 let mut vm = Vm::default();
247 let src = r#"
248(define x 10)
249(+ x (+ x "string"))
250"#;
251 let err = vm.eval_str(src).unwrap_err();
252 assert_eq!(
253 err.to_string(),
254 r#"+ expected type int or float but got string: "string"
255Source:
256 3: (+ x (+ x "string"))
257
258"#
259 );
260 }
261
262 #[test]
263 fn backtraces_are_all_eq() {
264 assert_ne!(BacktraceError::capture(), BacktraceError::capture());
267 }
268
269 #[test]
270 fn hacks_for_code_coverage() {
271 VmError::CustomError("".to_string()).source();
273 assert_ne!(CompileError::EmptyExpression.to_string(), "");
274 assert_ne!(format!("{:?}", CompileError::EmptyExpression), "");
275 assert_ne!(BacktraceError::capture().to_string(), "");
276 assert_ne!(format!("{:?}", BacktraceError::capture()), "");
277 }
278}