Skip to main content

espy_paws/
error.rs

1use crate::{ComplexType, Value};
2use espy_heart::debug;
3use std::num::NonZero;
4
5#[must_use]
6pub struct Error {
7    /// Program counter relative to the start of the file rather than the start of a block.
8    ///
9    /// This can be used to derive the block, local pc, line number, etc.
10    /// It is "nonzero" to reduce the size of the error structure;
11    /// the first byte of an espy binary is never executable code.
12    absolute_program_counter: Option<NonZero<u32>>,
13    kind: ErrorKind,
14}
15
16impl Error {
17    pub fn kind(&self) -> &ErrorKind {
18        &self.kind
19    }
20
21    pub fn into_kind(self) -> ErrorKind {
22        self.kind
23    }
24
25    pub fn suggest_location(self, absolute_program_counter: usize) -> Self {
26        Self {
27            absolute_program_counter: self.absolute_program_counter.or_else(|| {
28                u32::try_from(absolute_program_counter)
29                    .ok()?
30                    .try_into()
31                    .ok()
32            }),
33            ..self
34        }
35    }
36
37    /// Converts the error's location hint into the source code span it originated from.
38    ///
39    /// This may result in `None`--not all errors have locations.
40    ///
41    /// # Errors
42    ///
43    /// Returns a [`DebugInfoError`] if the debug info is malformed.
44    ///
45    /// The reported position of this error may be trusted and will never be the
46    /// source of an error.
47    pub fn locate(&self, debug_info: &[u8]) -> Result<Option<(usize, usize)>, DebugInfoError> {
48        let Some(absolute_program_counter) = self.absolute_program_counter.map(|x| {
49            u32::from(x)
50                .try_into()
51                .expect("program counter should not exceed usize")
52        }) else {
53            return Ok(None);
54        };
55        let mut this_pc = 0;
56        let mut last_span = None;
57        let mut debug_info = debug_info.iter().copied();
58        while let Some(directive) = debug_info.next() {
59            match directive {
60                debug::SPAN => {
61                    let mut get_u64 = || {
62                        let mut unpacked = [0; size_of::<u64>()];
63                        for dest in unpacked.iter_mut().take(debug_info.next()? as usize) {
64                            *dest = debug_info.next()?;
65                        }
66                        Some(u64::from_le_bytes(unpacked))
67                    };
68                    let start = get_u64().ok_or(DebugInfoError {
69                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
70                    })?;
71                    let end = get_u64().ok_or(DebugInfoError {
72                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
73                    })?;
74                    last_span = Some((start, end));
75                    this_pc += 1;
76                }
77                debug::DITTO => {
78                    this_pc += debug_info.next().ok_or(DebugInfoError {
79                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
80                    })? as usize;
81                }
82                _ => {
83                    return Err(DebugInfoError {
84                        kind: DebugInfoErrorKind::UnknownDirective,
85                    });
86                }
87            }
88            if this_pc > absolute_program_counter {
89                return Ok(last_span.map(|(start, end)| (start as usize, end as usize)));
90            }
91        }
92        Err(DebugInfoError {
93            kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
94        })
95    }
96}
97
98impl std::fmt::Display for Error {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        self.kind.fmt(f)
101    }
102}
103
104impl std::fmt::Debug for Error {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        self.kind.fmt(f)
107    }
108}
109
110impl std::error::Error for Error {}
111
112impl Error {
113    pub fn type_error(value: &Value, ty: &ComplexType) -> Self {
114        ErrorKind::TypeError {
115            value: format!("{value:?}").into_boxed_str(),
116            ty: format!("{:?}", ty).into_boxed_str(),
117        }
118        .into()
119    }
120
121    pub fn index_not_found(container: &Value, index: &Value) -> Self {
122        ErrorKind::IndexNotFound {
123            index: format!("{index:?}").into_boxed_str(),
124            container: format!("{container:?}").into_boxed_str(),
125        }
126        .into()
127    }
128
129    pub fn expected_integers(l: &Value, r: &Value) -> Self {
130        ErrorKind::ExpectedIntegers(
131            format!("{l:?}").into_boxed_str(),
132            format!("{r:?}").into_boxed_str(),
133        )
134        .into()
135    }
136
137    pub fn expected_tuple(i: &Value) -> Self {
138        ErrorKind::ExpectedTuple(format!("{i:?}").into_boxed_str()).into()
139    }
140
141    pub fn expected_named_tuple(i: &Value) -> Self {
142        ErrorKind::ExpectedNamedTuple(format!("{i:?}").into_boxed_str()).into()
143    }
144
145    pub fn expected_function(function: &Value) -> Self {
146        ErrorKind::ExpectedFunction(format!("{function:?}").into_boxed_str()).into()
147    }
148
149    pub fn incomparable_values(l: &Value, r: &Value) -> Self {
150        ErrorKind::IncomparableValues(
151            format!("{l:?}").into_boxed_str(),
152            format!("{r:?}").into_boxed_str(),
153        )
154        .into()
155    }
156
157    pub fn expected_enum_variant(i: &Value) -> Error {
158        ErrorKind::ExpectedEnumVariant(format!("{i:?}").into_boxed_str()).into()
159    }
160
161    pub fn expected_enum_type(i: &Value) -> Error {
162        ErrorKind::ExpectedEnumType(format!("{i:?}").into_boxed_str()).into()
163    }
164
165    pub fn expected_option(i: &Value) -> Error {
166        ErrorKind::ExpectedOption(format!("{i:?}").into_boxed_str()).into()
167    }
168
169    pub fn expected_reference(i: &Value) -> Error {
170        ErrorKind::ExpectedReference(format!("{i:?}").into_boxed_str()).into()
171    }
172
173    pub fn other(e: impl Into<anyhow::Error>) -> Error {
174        ErrorKind::Other(e.into()).into()
175    }
176
177    pub fn custom(message: &'static str) -> Error {
178        ErrorKind::Other(anyhow::Error::msg(message)).into()
179    }
180}
181
182impl From<ErrorKind> for Error {
183    fn from(kind: ErrorKind) -> Self {
184        Self {
185            absolute_program_counter: None,
186            kind,
187        }
188    }
189}
190
191impl From<anyhow::Error> for Error {
192    fn from(e: anyhow::Error) -> Error {
193        Error::other(e)
194    }
195}
196
197#[derive(Debug)]
198#[non_exhaustive]
199#[must_use]
200pub enum ErrorKind {
201    ExpectedIntegers(Box<str>, Box<str>),
202    ExpectedFunction(Box<str>),
203    ExpectedEnumVariant(Box<str>),
204    ExpectedOption(Box<str>),
205    ExpectedEnumType(Box<str>),
206    ExpectedTuple(Box<str>),
207    ExpectedNamedTuple(Box<str>),
208    ExpectedReference(Box<str>),
209
210    IncomparableValues(Box<str>, Box<str>),
211    TypeError {
212        value: Box<str>,
213        ty: Box<str>,
214    },
215    CalledNeverFunction,
216    IndexNotFound {
217        index: Box<str>,
218        container: Box<str>,
219    },
220    OutOfScopeMut,
221    NotStatic,
222    InvalidBytecode(InvalidBytecode),
223
224    Other(anyhow::Error),
225}
226
227impl std::fmt::Display for ErrorKind {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        match self {
230            ErrorKind::ExpectedIntegers(l, r) => {
231                write!(f, "expected two integers, got {l} and {r}")
232            }
233            ErrorKind::ExpectedFunction(i) => write!(f, "expected a function, got {i}"),
234            ErrorKind::ExpectedEnumVariant(i) => write!(f, "expected an enum variant, got {i}"),
235            ErrorKind::ExpectedOption(i) => write!(f, "expected an option, got {i}"),
236            ErrorKind::ExpectedEnumType(i) => write!(f, "expected an enum, got {i}"),
237            ErrorKind::ExpectedTuple(i) => write!(f, "expected a tuple or named tuple, got {i}"),
238            ErrorKind::ExpectedNamedTuple(i) => write!(f, "expected a named tuple, got {i}"),
239            ErrorKind::ExpectedReference(i) => write!(f, "expected a reference type, got {i}"),
240            ErrorKind::IncomparableValues(l, r) => write!(f, "{l} cannot be compared to {r}"),
241            ErrorKind::TypeError { value, ty } => {
242                write!(f, "expected a value of type {ty}, got {value}")
243            }
244            ErrorKind::CalledNeverFunction => write!(f, "attempted to call a \"never\" function"),
245            ErrorKind::IndexNotFound { index, container } => {
246                write!(f, "{container} does not contain the field {index}")
247            }
248            ErrorKind::OutOfScopeMut => write!(
249                f,
250                "attempted to read or write a mutable value which has gone out of scope"
251            ),
252            ErrorKind::InvalidBytecode(invalid_bytecode) => {
253                write!(f, "invalid bytecode: {invalid_bytecode}")
254            }
255            ErrorKind::NotStatic => {
256                write!(f, "attempted to convert a mutable value to a static value")
257            }
258            ErrorKind::Other(error) => error.fmt(f),
259        }
260    }
261}
262
263/// Errors that may only be caused due to invalid bytecode.
264///
265/// If this error is returned by an espy program compiled by a compatible
266/// version of the compiler, it is considered an espy bug.
267#[derive(Debug)]
268pub enum InvalidBytecode {
269    /// Caused by an imbalance in stack operations.
270    ///
271    /// Well-behaved bytecode never has any reason to cause this.
272    StackUnderflow,
273    /// An instruction byte had an unexpected value.
274    InvalidInstruction,
275    /// A clone referred to a builtin value that does not exist.
276    InvalidBuiltin,
277    /// An instruction referred to a string id that did not exist.
278    InvalidStringId,
279    /// Occurs when the header is too short or
280    /// describes a program which is longer than the provided slice.
281    MalformedHeader,
282    /// Occurs when the program counter becomes greater than the length of the program.
283    ///
284    /// Note the "*greater*"; a pc of the program's length
285    /// (after the last byte) is considered an intentional return.
286    ProgramOutOfBounds,
287    /// Occurs when a stack access goes beyond the length of the stack.
288    StackOutOfBounds,
289    Utf8Error(std::str::Utf8Error),
290}
291
292impl From<InvalidBytecode> for Error {
293    fn from(e: InvalidBytecode) -> Error {
294        Error {
295            absolute_program_counter: None,
296            kind: ErrorKind::InvalidBytecode(e),
297        }
298    }
299}
300
301impl std::fmt::Display for InvalidBytecode {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        match self {
304            InvalidBytecode::StackUnderflow => write!(f, "stack underflow"),
305            InvalidBytecode::InvalidInstruction => write!(f, "invalid instruction"),
306            InvalidBytecode::InvalidBuiltin => write!(f, "invalid builtin"),
307            InvalidBytecode::InvalidStringId => write!(f, "unexpected string id"),
308            InvalidBytecode::MalformedHeader => write!(f, "malformed header"),
309            InvalidBytecode::ProgramOutOfBounds => write!(f, "program out of bounds"),
310            InvalidBytecode::StackOutOfBounds => write!(f, "stack out of bounds"),
311            InvalidBytecode::Utf8Error(utf8_error) => utf8_error.fmt(f),
312        }
313    }
314}
315
316#[derive(Debug)]
317pub struct DebugInfoError {
318    kind: DebugInfoErrorKind,
319}
320
321impl std::fmt::Display for DebugInfoError {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        use DebugInfoErrorKind as Kind;
324        match &self.kind {
325            Kind::UnexpectedEndOfSequence => write!(f, "unexpected end of sequence"),
326            Kind::UnknownDirective => write!(f, "unknown directive"),
327        }
328    }
329}
330
331#[derive(Debug)]
332pub enum DebugInfoErrorKind {
333    UnexpectedEndOfSequence,
334    UnknownDirective,
335}