Skip to main content

lambda_throw_cat/
error.rs

1//! Project-wide error type.
2//!
3//! [`Error::Thrown`] is unusual: it represents a value-carrying control-flow
4//! signal rather than an interpreter-internal failure.  The evaluator
5//! propagates it like any other error via `?`, and an enclosing
6//! [`Expr::TryCatch`] is the only site that pattern-matches it back into a
7//! normal evaluation.  An [`Error::Thrown`] that reaches the top boundary
8//! is converted to [`Error::UncaughtException`] for the user.  The payload
9//! is boxed because it carries the full heap state at the moment of the
10//! throw and would otherwise inflate every fallible `Result` size.
11//!
12//! [`Expr::TryCatch`]: crate::syntax::Expr::TryCatch
13
14use crate::eval::Fuel;
15use crate::heap::{Address, Heap};
16use crate::syntax::{Position, VarName};
17use crate::value::Value;
18
19/// Payload of a propagating throw: the thrown value plus the heap and fuel
20/// at the moment the throw was raised.  Carried inside [`Error::Thrown`]
21/// behind a [`Box`] so the [`Result`] discriminant stays small.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ThrownPayload {
24    value: Value,
25    heap: Heap,
26    fuel: Fuel,
27}
28
29impl ThrownPayload {
30    /// Build a payload from its three components.
31    #[must_use]
32    pub fn new(value: Value, heap: Heap, fuel: Fuel) -> Self {
33        Self { value, heap, fuel }
34    }
35
36    /// The thrown value.
37    #[must_use]
38    pub fn value(&self) -> &Value {
39        &self.value
40    }
41
42    /// Decompose into owned parts so the catch handler can resume with the
43    /// thrown value and the heap and fuel as they were at the throw site.
44    #[must_use]
45    pub fn into_parts(self) -> (Value, Heap, Fuel) {
46        (self.value, self.heap, self.fuel)
47    }
48}
49
50/// All errors in lambda-throw-cat.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum Error {
53    /// The lexer encountered a character that does not begin any token.
54    UnexpectedChar {
55        /// Byte offset.
56        at: Position,
57        /// The offending character.
58        ch: char,
59    },
60    /// Input ended while the parser still required more tokens.
61    UnexpectedEnd {
62        /// What was expected.
63        expected: &'static str,
64    },
65    /// The parser found a token that does not satisfy the current production.
66    UnexpectedToken {
67        /// Byte offset.
68        at: Position,
69        /// What was expected.
70        expected: &'static str,
71        /// Token that was actually found.
72        found: String,
73    },
74    /// Evaluation referenced a variable not bound in the current environment.
75    UnboundVariable {
76        /// The unbound name.
77        name: VarName,
78    },
79    /// Evaluation exceeded its step budget.
80    FuelExhausted {
81        /// The configured step limit.
82        limit: u64,
83    },
84    /// A dereference, store, field access, or field assignment targeted an
85    /// address not present in the heap.
86    DanglingReference {
87        /// The address that could not be resolved.
88        address: Address,
89    },
90    /// Dereference or assignment was attempted on a value that is not a cell
91    /// reference.
92    NotAReference {
93        /// Rendering of the offending non-reference value.
94        found: String,
95    },
96    /// Application was attempted with a non-function value.
97    NotAFunction {
98        /// Rendering of the offending non-function value.
99        found: String,
100    },
101    /// Field access or extend targeted a value that is not (or does not
102    /// resolve to) an object.
103    NotAnObject {
104        /// Rendering of the offending non-object value.
105        found: String,
106    },
107    /// Field access walked the prototype chain to its end without finding the
108    /// requested property.
109    PropertyNotFound {
110        /// The missing property name.
111        name: VarName,
112    },
113    /// A `throw` is propagating up the evaluation stack.  Carries a boxed
114    /// [`ThrownPayload`] with the value, heap, and fuel at the throw site
115    /// so an enclosing try-catch can resume from there.  Boxed to keep the
116    /// [`Result`] discriminant small.
117    Thrown(Box<ThrownPayload>),
118    /// A `throw` escaped the top-level evaluation without being caught.
119    UncaughtException {
120        /// The value that was thrown.
121        value: Value,
122    },
123}
124
125impl std::fmt::Display for Error {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Self::UnexpectedChar { at, ch } => {
129                write!(f, "unexpected character {ch:?} at byte {}", at.value())
130            }
131            Self::UnexpectedEnd { expected } => {
132                write!(f, "unexpected end of input; expected {expected}")
133            }
134            Self::UnexpectedToken {
135                at,
136                expected,
137                found,
138            } => {
139                write!(
140                    f,
141                    "unexpected token {found:?} at byte {}; expected {expected}",
142                    at.value()
143                )
144            }
145            Self::UnboundVariable { name } => {
146                write!(f, "unbound variable {:?}", name.as_str())
147            }
148            Self::FuelExhausted { limit } => {
149                write!(f, "evaluation exceeded step limit of {limit}")
150            }
151            Self::DanglingReference { address } => {
152                write!(f, "dangling reference to address {address}")
153            }
154            Self::NotAReference { found } => {
155                write!(f, "expected a reference, found {found}")
156            }
157            Self::NotAFunction { found } => {
158                write!(f, "expected a function, found {found}")
159            }
160            Self::NotAnObject { found } => {
161                write!(f, "expected an object, found {found}")
162            }
163            Self::PropertyNotFound { name } => {
164                write!(
165                    f,
166                    "property {:?} not found on object or its prototype chain",
167                    name.as_str()
168                )
169            }
170            Self::Thrown(payload) => {
171                write!(f, "propagating throw of {}", payload.value())
172            }
173            Self::UncaughtException { value } => {
174                write!(f, "uncaught exception: {value}")
175            }
176        }
177    }
178}
179
180impl std::error::Error for Error {}