Skip to main content

sim_lib_binding/
let_form.rs

1//! The `let` binding special form.
2//!
3//! `let` is a COOKBOOK_7 Category B eval-policy organ: a special form that
4//! introduces lexical bindings. Like [`crate`]'s other binding machinery it is
5//! lexical and parallel -- every initializer is evaluated in the OUTER scope, so
6//! no binding can observe another, and the body then runs in a fresh child scope.
7//!
8//! It is implemented with the SIM-native special-form mechanism: a [`Callable`]
9//! that overrides [`Callable::call_exprs`] to take its argument expressions
10//! UNEVALUATED, so it can decide what to evaluate and in which scope. The
11//! bindings are installed into the kernel [`Env`] (via [`Cx::with_env`]) that the
12//! evaluator consults for symbol lookup, so a bound name resolves in the body and
13//! is gone again once the form returns.
14
15use std::sync::Arc;
16
17use sim_kernel::{
18    Args, Callable, ClassRef, Cx, Env, Error, Expr, Object, ObjectCompat, RawArgs, Result, Symbol,
19    Value,
20};
21
22/// The `let` special form: `(let ((name init)...) body...)`.
23#[derive(Clone, Copy)]
24pub struct LetForm;
25
26impl LetForm {
27    /// The bare `let` symbol this form registers under.
28    pub fn symbol() -> Symbol {
29        Symbol::new("let")
30    }
31}
32
33/// Parse one binding clause into `(name, init)`.
34///
35/// A clause decodes either as a call `(name init)` (operator + one argument) or
36/// as a two-element data list `[name init]`, depending on the codec's lowering;
37/// both are accepted so the form works from a lisp surface and from a data codec.
38fn parse_clause(clause: Expr) -> Result<(Symbol, Expr)> {
39    let bad = || Error::Eval("let binding must be (name init)".to_owned());
40    match clause {
41        Expr::Call { operator, args } => {
42            let Expr::Symbol(name) = *operator else {
43                return Err(bad());
44            };
45            let mut args = args.into_iter();
46            let (Some(init), None) = (args.next(), args.next()) else {
47                return Err(bad());
48            };
49            Ok((name, init))
50        }
51        Expr::List(items) | Expr::Vector(items) => {
52            let mut items = items.into_iter();
53            match (items.next(), items.next(), items.next()) {
54                (Some(Expr::Symbol(name)), Some(init), None) => Ok((name, init)),
55                _ => Err(bad()),
56            }
57        }
58        _ => Err(bad()),
59    }
60}
61
62/// Parse the bindings container (a list/vector of clauses) into `(name, init)`s.
63fn parse_bindings(bindings: Expr) -> Result<Vec<(Symbol, Expr)>> {
64    let clauses = match bindings {
65        Expr::List(items) | Expr::Vector(items) => items,
66        Expr::Nil => Vec::new(),
67        _ => return Err(Error::Eval("let bindings must be a list".to_owned())),
68    };
69    clauses.into_iter().map(parse_clause).collect()
70}
71
72impl Object for LetForm {
73    fn display(&self, _cx: &mut Cx) -> Result<String> {
74        Ok("#<special-form let>".to_owned())
75    }
76
77    fn as_any(&self) -> &dyn std::any::Any {
78        self
79    }
80}
81
82impl ObjectCompat for LetForm {
83    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
84        cx.resolve_class(&Symbol::qualified("core", "Function"))
85    }
86
87    fn as_callable(&self) -> Option<&dyn Callable> {
88        Some(self)
89    }
90}
91
92impl Callable for LetForm {
93    /// `let` cannot run on pre-evaluated arguments: its first argument is the
94    /// binding form, which must stay unevaluated. Calling it eagerly is a usage
95    /// error; the real semantics live in [`Self::call_exprs`].
96    fn call(&self, _cx: &mut Cx, _args: Args) -> Result<Value> {
97        Err(Error::Eval(
98            "let is a special form and cannot be applied to evaluated arguments".to_owned(),
99        ))
100    }
101
102    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
103        let mut exprs = args.into_exprs().into_iter();
104        let Some(bindings) = exprs.next() else {
105            return Err(Error::Eval(
106                "let expects (let (bindings...) body...)".to_owned(),
107            ));
108        };
109        let body: Vec<Expr> = exprs.collect();
110
111        // Parallel bindings: evaluate every initializer in the OUTER scope first.
112        let clauses = parse_bindings(bindings)?;
113        let mut bound = Vec::with_capacity(clauses.len());
114        for (name, init) in clauses {
115            let value = cx.eval_expr(init)?;
116            bound.push((name, value));
117        }
118
119        // Install the bindings in a fresh child scope and run the body there.
120        let child = Env::child(Arc::new(cx.env().clone()));
121        cx.with_env(child, |cx| {
122            for (name, value) in bound {
123                cx.env_mut().define(name, value);
124            }
125            let mut last = cx.factory().nil()?;
126            for expr in body {
127                last = cx.eval_expr(expr)?;
128            }
129            Ok(last)
130        })
131    }
132}