spore_vm/
error.rs

1//! Contains errors definitions for the Spore VM.
2use 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
12/// A `Result` with `VmError` as the error branch.
13pub type VmResult<T> = Result<T, VmError>;
14
15/// Describes an error encountered when running the Vm.
16#[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    /// Return the error with the given source context added.
46    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
175/// Describes a generic error along with its stacktrace.
176pub 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    // We say that all backtrace errors are equivalent to make unit tests accept any backtrace. This
201    // is simpler as it is not possible to compare backtraces.
202    fn eq(&self, _: &Self) -> bool {
203        false
204    }
205}
206
207/// Describes an error during compilation to Spore bytecode.
208#[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        // Backtraces are abstract so we assume (mostly for tests sake) that they are not
265        // equivalent.
266        assert_ne!(BacktraceError::capture(), BacktraceError::capture());
267    }
268
269    #[test]
270    fn hacks_for_code_coverage() {
271        // A collection of functions that are not worth testing.
272        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}