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 {}