Skip to main content

lambda_throw_cat/
syntax.rs

1//! Abstract syntax and source-position newtypes.
2//!
3//! Spike 4 extends the spike-3 grammar with two non-local control-flow forms:
4//! [`Expr::Throw`] raises an exception carrying any runtime value, and
5//! [`Expr::TryCatch`] catches a thrown value into a let-style binder around
6//! the handler.  Every other reduction form is unchanged; the propagation of
7//! a thrown value is purely a property of the evaluator's `Outcome` return
8//! type, not of the syntax tree.
9
10/// A byte offset into the source string.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct Position(usize);
13
14impl Position {
15    /// The underlying byte offset.
16    #[must_use]
17    pub fn value(&self) -> usize {
18        self.0
19    }
20}
21
22impl From<usize> for Position {
23    fn from(value: usize) -> Self {
24        Self(value)
25    }
26}
27
28impl std::fmt::Display for Position {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}", self.0)
31    }
32}
33
34/// A variable or property identifier.
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct VarName(String);
37
38impl VarName {
39    /// View the name as a string slice.
40    #[must_use]
41    pub fn as_str(&self) -> &str {
42        &self.0
43    }
44}
45
46impl From<String> for VarName {
47    fn from(value: String) -> Self {
48        Self(value)
49    }
50}
51
52impl From<&str> for VarName {
53    fn from(value: &str) -> Self {
54        Self(value.to_owned())
55    }
56}
57
58impl std::fmt::Display for VarName {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.write_str(&self.0)
61    }
62}
63
64/// The abstract syntax tree.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum Expr {
67    /// Variable reference.
68    Var(VarName),
69    /// Lambda abstraction.
70    Lam {
71        /// Bound parameter name.
72        param: VarName,
73        /// Function body.
74        body: Box<Expr>,
75    },
76    /// Function application (left-associative at the surface).
77    App {
78        /// The function being applied.
79        func: Box<Expr>,
80        /// The argument.
81        arg: Box<Expr>,
82    },
83    /// Let-binding.
84    Let {
85        /// The bound name.
86        name: VarName,
87        /// The value being bound.
88        value: Box<Expr>,
89        /// The body in which the binding is in scope.
90        body: Box<Expr>,
91    },
92    /// Fixed-point binding.
93    Fix {
94        /// The name bound to the fixed point inside `body`.
95        name: VarName,
96        /// The expression being closed over its own name.
97        body: Box<Expr>,
98    },
99    /// Allocate a fresh cell initialised with the value of `inner`.
100    Ref {
101        /// The expression whose value will populate the new cell.
102        inner: Box<Expr>,
103    },
104    /// Dereference the cell pointed to by `inner`.
105    Deref {
106        /// The expression that must evaluate to a [`Value::Ref`].
107        ///
108        /// [`Value::Ref`]: crate::value::Value::Ref
109        inner: Box<Expr>,
110    },
111    /// Assign `value` into the cell or property pointed to by `target`.
112    /// When `target` is a [`Expr::Field`] the assignment is to an own
113    /// property of the referenced object; otherwise it is to a cell.
114    Assign {
115        /// The expression denoting the location to be written.
116        target: Box<Expr>,
117        /// The value to store.
118        value: Box<Expr>,
119    },
120    /// Sequence: evaluate `first` (discarding its value), then evaluate
121    /// `second` and return its value.
122    Seq {
123        /// The expression evaluated for effect only.
124        first: Box<Expr>,
125        /// The expression whose value is the result of the sequence.
126        second: Box<Expr>,
127    },
128    /// Object literal with optional prototype.  Evaluating an [`Expr::Object`]
129    /// allocates a fresh object on the heap and returns a reference to it.
130    Object {
131        /// Property entries, in source order.  Duplicates resolve by the
132        /// last write.
133        entries: Vec<(VarName, Expr)>,
134        /// Optional prototype expression.  When `Some`, the value must
135        /// evaluate to a reference to another object at run time.
136        prototype: Option<Box<Expr>>,
137    },
138    /// Field access on an object.  Walks the prototype chain on miss.
139    Field {
140        /// The object expression.
141        object: Box<Expr>,
142        /// The property name to look up.
143        name: VarName,
144    },
145    /// Throw an exception carrying the value of `inner`.  Unwinds the
146    /// current evaluation until caught by an enclosing [`Expr::TryCatch`].
147    Throw {
148        /// The value to throw.
149        inner: Box<Expr>,
150    },
151    /// Catch an exception thrown by `body`, binding the thrown value to
152    /// `catch_param` while evaluating `handler`.
153    TryCatch {
154        /// The expression evaluated under exception protection.
155        body: Box<Expr>,
156        /// The name to which a caught thrown value is bound.
157        catch_param: VarName,
158        /// The handler expression evaluated when `body` throws.
159        handler: Box<Expr>,
160    },
161}
162
163impl Expr {
164    /// Build a variable reference.
165    #[must_use]
166    pub fn var(name: impl Into<VarName>) -> Self {
167        Self::Var(name.into())
168    }
169
170    /// Build a lambda abstraction.
171    #[must_use]
172    pub fn lam(param: impl Into<VarName>, body: Self) -> Self {
173        Self::Lam {
174            param: param.into(),
175            body: Box::new(body),
176        }
177    }
178
179    /// Build an application node.
180    #[must_use]
181    pub fn app(func: Self, arg: Self) -> Self {
182        Self::App {
183            func: Box::new(func),
184            arg: Box::new(arg),
185        }
186    }
187
188    /// Build a let-binding.  Named `bind` because `let` is a keyword.
189    #[must_use]
190    pub fn bind(name: impl Into<VarName>, value: Self, body: Self) -> Self {
191        Self::Let {
192            name: name.into(),
193            value: Box::new(value),
194            body: Box::new(body),
195        }
196    }
197
198    /// Build a fixed-point binding.
199    #[must_use]
200    pub fn fix(name: impl Into<VarName>, body: Self) -> Self {
201        Self::Fix {
202            name: name.into(),
203            body: Box::new(body),
204        }
205    }
206
207    /// Build a `ref` allocation node.
208    #[must_use]
209    pub fn alloc(inner: Self) -> Self {
210        Self::Ref {
211            inner: Box::new(inner),
212        }
213    }
214
215    /// Build a dereference node.
216    #[must_use]
217    pub fn deref(inner: Self) -> Self {
218        Self::Deref {
219            inner: Box::new(inner),
220        }
221    }
222
223    /// Build an assignment node.
224    #[must_use]
225    pub fn assign(target: Self, value: Self) -> Self {
226        Self::Assign {
227            target: Box::new(target),
228            value: Box::new(value),
229        }
230    }
231
232    /// Build a sequence node.
233    #[must_use]
234    pub fn seq(first: Self, second: Self) -> Self {
235        Self::Seq {
236            first: Box::new(first),
237            second: Box::new(second),
238        }
239    }
240
241    /// Build an object literal with no prototype.
242    #[must_use]
243    pub fn object(entries: Vec<(VarName, Self)>) -> Self {
244        Self::Object {
245            entries,
246            prototype: None,
247        }
248    }
249
250    /// Build an object literal with a prototype expression.
251    #[must_use]
252    pub fn object_with_proto(entries: Vec<(VarName, Self)>, prototype: Self) -> Self {
253        Self::Object {
254            entries,
255            prototype: Some(Box::new(prototype)),
256        }
257    }
258
259    /// Build a field access node.
260    #[must_use]
261    pub fn field(object: Self, name: impl Into<VarName>) -> Self {
262        Self::Field {
263            object: Box::new(object),
264            name: name.into(),
265        }
266    }
267
268    /// Build a throw node.
269    #[must_use]
270    pub fn throw(inner: Self) -> Self {
271        Self::Throw {
272            inner: Box::new(inner),
273        }
274    }
275
276    /// Build a try-catch node.
277    #[must_use]
278    pub fn try_catch(body: Self, catch_param: impl Into<VarName>, handler: Self) -> Self {
279        Self::TryCatch {
280            body: Box::new(body),
281            catch_param: catch_param.into(),
282            handler: Box::new(handler),
283        }
284    }
285}
286
287impl std::fmt::Display for Expr {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        match self {
290            Self::Var(name) => write!(f, "{name}"),
291            Self::Lam { param, body } => write!(f, "(\\{param}. {body})"),
292            Self::App { func, arg } => write!(f, "({func} {arg})"),
293            Self::Let { name, value, body } => {
294                write!(f, "(let {name} = {value} in {body})")
295            }
296            Self::Fix { name, body } => write!(f, "(fix {name}. {body})"),
297            Self::Ref { inner } => write!(f, "(ref {inner})"),
298            Self::Deref { inner } => write!(f, "(!{inner})"),
299            Self::Assign { target, value } => write!(f, "({target} := {value})"),
300            Self::Seq { first, second } => write!(f, "({first} ; {second})"),
301            Self::Object { entries, prototype } => write_object(f, entries, prototype.as_deref()),
302            Self::Field { object, name } => write!(f, "({object}.{name})"),
303            Self::Throw { inner } => write!(f, "(throw {inner})"),
304            Self::TryCatch {
305                body,
306                catch_param,
307                handler,
308            } => write!(f, "(try {body} catch {catch_param}. {handler})"),
309        }
310    }
311}
312
313fn write_object(
314    f: &mut std::fmt::Formatter<'_>,
315    entries: &[(VarName, Expr)],
316    prototype: Option<&Expr>,
317) -> std::fmt::Result {
318    let body = entries
319        .iter()
320        .map(|(k, v)| format!("{k} = {v}"))
321        .collect::<Vec<_>>()
322        .join(", ");
323    let formatted = prototype.map_or_else(
324        || format!("{{{body}}}"),
325        |proto| format!("(extend {proto} {{{body}}})"),
326    );
327    f.write_str(&formatted)
328}