Skip to main content

sim_lib_control/
conditional.rs

1//! The `if` conditional special form.
2//!
3//! `if` is the first eval-policy organ of COOKBOOK_7 Category B: a special form,
4//! not an ordinary function. An ordinary function evaluates every argument before
5//! it runs, which would evaluate BOTH branches of a conditional. `IfForm` instead
6//! overrides [`Callable::call_exprs`] so it receives its arguments UNEVALUATED,
7//! evaluates only the test, and then evaluates only the taken branch. This is the
8//! SIM-native special-form mechanism -- a callable that drives evaluation of its
9//! own argument expressions -- and it needs no kernel change.
10
11use sim_kernel::{
12    Args, Callable, ClassRef, Cx, Error, Object, ObjectCompat, RawArgs, Result, Symbol, Value,
13};
14
15/// The `if` special form: `(if test then)` or `(if test then else)`.
16///
17/// Evaluates `test`; if it is truthy, evaluates and returns `then`, otherwise
18/// evaluates and returns `else` (or nil when omitted). The untaken branch is
19/// never evaluated.
20#[derive(Clone, Copy)]
21pub struct IfForm;
22
23impl IfForm {
24    /// The bare `if` symbol this form registers under.
25    pub fn symbol() -> Symbol {
26        Symbol::new("if")
27    }
28
29    /// Selects the branch expression for a truthy/falsy test, erroring on a bad
30    /// arity (`if` takes a test plus one or two branches).
31    fn arity_error(count: usize) -> Error {
32        Error::Eval(format!(
33            "if expects (if test then) or (if test then else), got {count} argument(s)"
34        ))
35    }
36}
37
38impl Object for IfForm {
39    fn display(&self, _cx: &mut Cx) -> Result<String> {
40        Ok("#<special-form if>".to_owned())
41    }
42
43    fn as_any(&self) -> &dyn std::any::Any {
44        self
45    }
46}
47
48impl ObjectCompat for IfForm {
49    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
50        cx.resolve_class(&Symbol::qualified("core", "Function"))
51    }
52
53    fn as_callable(&self) -> Option<&dyn Callable> {
54        Some(self)
55    }
56}
57
58impl Callable for IfForm {
59    /// Eager fallback when `if` is applied to already-evaluated values (e.g. via
60    /// `apply`): both branches are already values, so just select one. The
61    /// lazy-branch semantics live in [`Self::call_exprs`], which the evaluator
62    /// uses for a literal `(if ...)` form.
63    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
64        let values = args.into_vec();
65        if values.len() < 2 || values.len() > 3 {
66            return Err(Self::arity_error(values.len()));
67        }
68        if values[0].object().truth(cx)? {
69            Ok(values[1].clone())
70        } else if let Some(alt) = values.get(2) {
71            Ok(alt.clone())
72        } else {
73            cx.factory().nil()
74        }
75    }
76
77    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
78        let exprs = args.into_exprs();
79        if exprs.len() < 2 || exprs.len() > 3 {
80            return Err(Self::arity_error(exprs.len()));
81        }
82        let mut exprs = exprs.into_iter();
83        let test = exprs.next().expect("arity checked");
84        let then_branch = exprs.next().expect("arity checked");
85        let else_branch = exprs.next();
86
87        let taken = if cx.eval_expr(test)?.object().truth(cx)? {
88            then_branch
89        } else if let Some(else_branch) = else_branch {
90            else_branch
91        } else {
92            return cx.factory().nil();
93        };
94        cx.eval_expr(taken)
95    }
96}