Skip to main content

tatara_eval/
interpreter.rs

1//! Tree-walking interpreter — `Sexp` → `Value`.
2//!
3//! Minimal but complete enough to evaluate real Lisp programs that produce
4//! typed `Derivation` values. Special forms handled inline (to respect
5//! evaluation order / lazy arms); everything else dispatches via `apply` over
6//! evaluated arguments.
7//!
8//! Special forms:
9//!   - `quote`   — return the literal Sexp as Value
10//!   - `if`      — short-circuit branches
11//!   - `let`     — non-recursive bindings
12//!   - `letrec`  — recursive bindings via thunks
13//!   - `lambda`  — closure capture
14//!   - `define`  — bind in top-level env
15//!   - `begin`   — sequential evaluation
16//!   - `set!`    — mutate a top-level binding (restricted; see docs)
17
18use std::collections::BTreeMap;
19use std::sync::Arc;
20
21use tatara_lisp::{read, Atom, Sexp};
22
23use crate::builtins::builtin_table;
24use crate::env::Env;
25use crate::error::{EvalError, Result};
26use crate::value::{Lambda, Thunk, ThunkState, Value};
27
28pub struct Interpreter {
29    /// Root environment — seeded with builtins, mutated by `define`.
30    root: std::sync::RwLock<Env>,
31}
32
33impl Default for Interpreter {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl Interpreter {
40    pub fn new() -> Self {
41        let mut env = Env::new();
42        for (name, value) in builtin_table() {
43            env = env.extend(name, value);
44        }
45        Self {
46            root: std::sync::RwLock::new(env),
47        }
48    }
49
50    /// Interpreter with both pure builtins AND system-effects built in
51    /// (println/sleep/exit/env/read-file/write-file/shell). Call this
52    /// from host-side embedders like `tatara-init --eval`; leave sealed
53    /// terreiros using `new()` so they stay I/O-free.
54    pub fn new_with_system() -> Self {
55        let mut env = Env::new();
56        for (name, value) in builtin_table() {
57            env = env.extend(name, value);
58        }
59        for (name, value) in crate::system::system_builtin_table() {
60            env = env.extend(name, value);
61        }
62        Self {
63            root: std::sync::RwLock::new(env),
64        }
65    }
66
67    /// Evaluate a single source string. Returns the value of the last form.
68    pub fn eval_source(&self, src: &str) -> Result<Value> {
69        let forms = read(src)?;
70        self.eval_forms(&forms)
71    }
72
73    pub fn eval_forms(&self, forms: &[Sexp]) -> Result<Value> {
74        let mut last = Value::Nil;
75        for f in forms {
76            last = self.eval(f, &self.root_env())?;
77        }
78        Ok(last)
79    }
80
81    pub fn root_env(&self) -> Env {
82        self.root.read().unwrap().clone()
83    }
84
85    /// Install a binding in the top-level environment.
86    pub fn define(&self, name: impl Into<String>, value: Value) {
87        let mut root = self.root.write().unwrap();
88        *root = root.extend(name, value);
89    }
90
91    // ── evaluator ──────────────────────────────────────────────────────
92
93    pub fn eval(&self, s: &Sexp, env: &Env) -> Result<Value> {
94        match s {
95            Sexp::Nil => Ok(Value::Nil),
96            Sexp::Atom(a) => self.eval_atom(a, env),
97            Sexp::Quote(inner) => Ok(self.sexp_to_value(inner)),
98            Sexp::Quasiquote(inner) => self.eval_quasiquote(inner, env),
99            Sexp::Unquote(_) | Sexp::UnquoteSplice(_) => Err(EvalError::Malformed {
100                form: "unquote".into(),
101                reason: "unquote outside quasiquote".into(),
102            }),
103            Sexp::List(items) => {
104                if items.is_empty() {
105                    return Ok(Value::Nil);
106                }
107                // Check for special forms before evaluating the head.
108                if let Some(head) = items[0].as_symbol() {
109                    match head {
110                        "quote" => return self.sf_quote(items),
111                        "if" => return self.sf_if(items, env),
112                        "let" => return self.sf_let(items, env),
113                        "letrec" => return self.sf_letrec(items, env),
114                        "lambda" | "fn" => return self.sf_lambda(items, env),
115                        "define" => return self.sf_define(items, env),
116                        "begin" | "do" => return self.sf_begin(items, env),
117                        "and" => return self.sf_and(items, env),
118                        "or" => return self.sf_or(items, env),
119                        _ => {}
120                    }
121                }
122                // Function application: evaluate head + args, apply.
123                let head_val = self.eval(&items[0], env)?;
124                let mut args = Vec::with_capacity(items.len() - 1);
125                for a in &items[1..] {
126                    args.push(self.eval(a, env)?);
127                }
128                self.apply(&head_val, &args)
129            }
130        }
131    }
132
133    fn eval_atom(&self, a: &Atom, env: &Env) -> Result<Value> {
134        Ok(match a {
135            Atom::Int(n) => Value::Int(*n),
136            Atom::Float(n) => Value::Float(*n),
137            Atom::Str(s) => Value::Str(s.clone()),
138            Atom::Bool(b) => Value::Bool(*b),
139            Atom::Keyword(k) => Value::Keyword(k.clone()),
140            Atom::Symbol(name) => env
141                .lookup(name)
142                .ok_or_else(|| EvalError::Unbound(name.clone()))?,
143        })
144    }
145
146    fn sexp_to_value(&self, s: &Sexp) -> Value {
147        match s {
148            Sexp::Nil => Value::Nil,
149            Sexp::Atom(a) => match a {
150                Atom::Int(n) => Value::Int(*n),
151                Atom::Float(n) => Value::Float(*n),
152                Atom::Str(s) => Value::Str(s.clone()),
153                Atom::Bool(b) => Value::Bool(*b),
154                Atom::Keyword(k) => Value::Keyword(k.clone()),
155                Atom::Symbol(name) => Value::Symbol(name.clone()),
156            },
157            Sexp::List(xs) => {
158                Value::List(Arc::new(xs.iter().map(|x| self.sexp_to_value(x)).collect()))
159            }
160            other => Value::Quoted(Arc::new(other.clone())),
161        }
162    }
163
164    fn eval_quasiquote(&self, inner: &Sexp, env: &Env) -> Result<Value> {
165        match inner {
166            Sexp::Unquote(x) => self.eval(x, env),
167            Sexp::List(xs) => {
168                let mut out = Vec::with_capacity(xs.len());
169                for item in xs {
170                    match item {
171                        Sexp::UnquoteSplice(x) => {
172                            let v = self.eval(x, env)?;
173                            match v {
174                                Value::List(items) => {
175                                    out.extend(items.iter().cloned());
176                                }
177                                other => {
178                                    return Err(EvalError::Type {
179                                        expected: "list (for ,@)".into(),
180                                        found: other.type_name().into(),
181                                    })
182                                }
183                            }
184                        }
185                        other => out.push(self.eval_quasiquote(other, env)?),
186                    }
187                }
188                Ok(Value::List(Arc::new(out)))
189            }
190            Sexp::Atom(_) | Sexp::Nil => Ok(self.sexp_to_value(inner)),
191            Sexp::Quote(x) | Sexp::Quasiquote(x) => Ok(Value::Quoted(Arc::new((**x).clone()))),
192            Sexp::UnquoteSplice(_) => Err(EvalError::Malformed {
193                form: "quasiquote".into(),
194                reason: "bare ,@ outside of list".into(),
195            }),
196        }
197    }
198
199    // ── special forms ──────────────────────────────────────────────────
200
201    fn sf_quote(&self, items: &[Sexp]) -> Result<Value> {
202        if items.len() != 2 {
203            return Err(EvalError::Malformed {
204                form: "quote".into(),
205                reason: "expected (quote x)".into(),
206            });
207        }
208        Ok(self.sexp_to_value(&items[1]))
209    }
210
211    fn sf_if(&self, items: &[Sexp], env: &Env) -> Result<Value> {
212        if items.len() < 3 || items.len() > 4 {
213            return Err(EvalError::Malformed {
214                form: "if".into(),
215                reason: "expected (if c t [e])".into(),
216            });
217        }
218        let cond = self.eval(&items[1], env)?;
219        if cond.is_truthy() {
220            self.eval(&items[2], env)
221        } else if items.len() == 4 {
222            self.eval(&items[3], env)
223        } else {
224            Ok(Value::Nil)
225        }
226    }
227
228    fn sf_let(&self, items: &[Sexp], env: &Env) -> Result<Value> {
229        // (let ((a 1) (b 2)) body ...)
230        if items.len() < 3 {
231            return Err(EvalError::Malformed {
232                form: "let".into(),
233                reason: "expected (let ((name val)...) body...)".into(),
234            });
235        }
236        let bindings = items[1].as_list().ok_or_else(|| EvalError::Malformed {
237            form: "let".into(),
238            reason: "bindings must be a list".into(),
239        })?;
240        let mut new_env = env.clone();
241        for b in bindings {
242            let pair = b.as_list().ok_or_else(|| EvalError::Malformed {
243                form: "let".into(),
244                reason: "each binding must be (name val)".into(),
245            })?;
246            if pair.len() != 2 {
247                return Err(EvalError::Malformed {
248                    form: "let".into(),
249                    reason: "each binding must be (name val)".into(),
250                });
251            }
252            let name = pair[0].as_symbol().ok_or_else(|| EvalError::Malformed {
253                form: "let".into(),
254                reason: "binding name must be a symbol".into(),
255            })?;
256            let value = self.eval(&pair[1], env)?;
257            new_env = new_env.extend(name, value);
258        }
259        self.eval_body(&items[2..], &new_env)
260    }
261
262    fn sf_letrec(&self, items: &[Sexp], env: &Env) -> Result<Value> {
263        if items.len() < 3 {
264            return Err(EvalError::Malformed {
265                form: "letrec".into(),
266                reason: "expected (letrec ((name val)...) body...)".into(),
267            });
268        }
269        let bindings = items[1].as_list().ok_or_else(|| EvalError::Malformed {
270            form: "letrec".into(),
271            reason: "bindings must be a list".into(),
272        })?;
273        // Pre-allocate thunks so bindings can reference each other.
274        let mut names: Vec<String> = Vec::new();
275        let mut exprs: Vec<Sexp> = Vec::new();
276        for b in bindings {
277            let pair = b.as_list().ok_or_else(|| EvalError::Malformed {
278                form: "letrec".into(),
279                reason: "each binding must be (name val)".into(),
280            })?;
281            let name = pair[0]
282                .as_symbol()
283                .ok_or_else(|| EvalError::Malformed {
284                    form: "letrec".into(),
285                    reason: "binding name must be a symbol".into(),
286                })?
287                .to_string();
288            names.push(name);
289            exprs.push(pair[1].clone());
290        }
291        // Build an env whose bindings are thunks pointing at the same frame
292        // — naive approach: extend sequentially, force lazily in builtins.
293        let mut new_env = env.clone();
294        let thunks: Vec<Arc<Thunk>> = exprs
295            .iter()
296            .map(|e| Thunk::new(e.clone(), new_env.clone()))
297            .collect();
298        for (name, t) in names.iter().zip(&thunks) {
299            new_env = new_env.extend(name.clone(), Value::Thunk(t.clone()));
300        }
301        // Re-parent each thunk's env to the fully-bound env so each can see
302        // all its siblings.
303        for t in &thunks {
304            let mut state = t.cell.lock().unwrap();
305            if let ThunkState::Unevaluated { env: e, .. } = &mut *state {
306                *e = new_env.clone();
307            }
308        }
309        self.eval_body(&items[2..], &new_env)
310    }
311
312    fn sf_lambda(&self, items: &[Sexp], env: &Env) -> Result<Value> {
313        if items.len() < 3 {
314            return Err(EvalError::Malformed {
315                form: "lambda".into(),
316                reason: "expected (lambda (params...) body...)".into(),
317            });
318        }
319        let (params, rest) = parse_params(&items[1])?;
320        let body = items[2..].to_vec();
321        Ok(Value::Lambda(Arc::new(Lambda {
322            params,
323            rest,
324            body,
325            env: env.clone(),
326            name: None,
327        })))
328    }
329
330    fn sf_define(&self, items: &[Sexp], env: &Env) -> Result<Value> {
331        if items.len() != 3 {
332            return Err(EvalError::Malformed {
333                form: "define".into(),
334                reason: "expected (define name expr)".into(),
335            });
336        }
337        let name = items[1]
338            .as_symbol()
339            .ok_or_else(|| EvalError::Malformed {
340                form: "define".into(),
341                reason: "name must be a symbol".into(),
342            })?
343            .to_string();
344        let value = self.eval(&items[2], env)?;
345        self.define(name.clone(), value.clone());
346        Ok(value)
347    }
348
349    fn sf_begin(&self, items: &[Sexp], env: &Env) -> Result<Value> {
350        self.eval_body(&items[1..], env)
351    }
352
353    fn sf_and(&self, items: &[Sexp], env: &Env) -> Result<Value> {
354        let mut last = Value::Bool(true);
355        for e in &items[1..] {
356            last = self.eval(e, env)?;
357            if !last.is_truthy() {
358                return Ok(last);
359            }
360        }
361        Ok(last)
362    }
363
364    fn sf_or(&self, items: &[Sexp], env: &Env) -> Result<Value> {
365        for e in &items[1..] {
366            let v = self.eval(e, env)?;
367            if v.is_truthy() {
368                return Ok(v);
369            }
370        }
371        Ok(Value::Bool(false))
372    }
373
374    fn eval_body(&self, forms: &[Sexp], env: &Env) -> Result<Value> {
375        let mut last = Value::Nil;
376        for f in forms {
377            last = self.eval(f, env)?;
378        }
379        Ok(last)
380    }
381
382    // ── application ────────────────────────────────────────────────────
383
384    pub fn apply(&self, f: &Value, args: &[Value]) -> Result<Value> {
385        // Force any thunk arguments before use? Nix is lazy; we stay strict at
386        // call boundaries for now but force on use. Force here for ergonomics.
387        let forced_args: Vec<Value> = args
388            .iter()
389            .map(|v| self.force(v.clone()))
390            .collect::<Result<_>>()?;
391
392        match self.force(f.clone())? {
393            Value::Builtin(b) => {
394                if !b.arity.check(forced_args.len()) {
395                    return Err(EvalError::Arity {
396                        name: b.name.clone(),
397                        expected: b.arity.describe(),
398                        got: forced_args.len(),
399                    });
400                }
401                (b.func)(&forced_args)
402            }
403            Value::Lambda(l) => self.apply_lambda(&l, &forced_args),
404            other => Err(EvalError::Type {
405                expected: "callable".into(),
406                found: other.type_name().into(),
407            }),
408        }
409    }
410
411    fn apply_lambda(&self, l: &Lambda, args: &[Value]) -> Result<Value> {
412        let mut env = l.env.clone();
413        match &l.rest {
414            None => {
415                if args.len() != l.params.len() {
416                    return Err(EvalError::Arity {
417                        name: l.name.clone().unwrap_or_else(|| "<lambda>".into()),
418                        expected: format!("{}", l.params.len()),
419                        got: args.len(),
420                    });
421                }
422                for (p, a) in l.params.iter().zip(args.iter()) {
423                    env = env.extend(p.clone(), a.clone());
424                }
425            }
426            Some(rest_name) => {
427                if args.len() < l.params.len() {
428                    return Err(EvalError::Arity {
429                        name: l.name.clone().unwrap_or_else(|| "<lambda>".into()),
430                        expected: format!("at least {}", l.params.len()),
431                        got: args.len(),
432                    });
433                }
434                for (p, a) in l.params.iter().zip(args.iter()) {
435                    env = env.extend(p.clone(), a.clone());
436                }
437                let rest = args[l.params.len()..].to_vec();
438                env = env.extend(rest_name.clone(), Value::List(Arc::new(rest)));
439            }
440        }
441        self.eval_body(&l.body, &env)
442    }
443
444    /// Force a thunk, or return the value unchanged if it already is one.
445    pub fn force(&self, v: Value) -> Result<Value> {
446        let t = match v {
447            Value::Thunk(t) => t,
448            other => return Ok(other),
449        };
450        let mut state = t.cell.lock().unwrap();
451        match std::mem::replace(&mut *state, ThunkState::Evaluating) {
452            ThunkState::Forced(v) => {
453                *state = ThunkState::Forced(v.clone());
454                Ok(v)
455            }
456            ThunkState::Evaluating => Err(EvalError::Other(
457                "thunk cycle: forcing an in-progress thunk".into(),
458            )),
459            ThunkState::Unevaluated { body, env } => {
460                drop(state);
461                let v = self.eval(&body, &env)?;
462                let v = self.force(v)?;
463                *t.cell.lock().unwrap() = ThunkState::Forced(v.clone());
464                Ok(v)
465            }
466        }
467    }
468}
469
470fn parse_params(s: &Sexp) -> Result<(Vec<String>, Option<String>)> {
471    let items = s.as_list().ok_or_else(|| EvalError::Malformed {
472        form: "lambda".into(),
473        reason: "params must be a list".into(),
474    })?;
475    let mut params = Vec::new();
476    let mut rest = None;
477    let mut saw_rest = false;
478    for (i, p) in items.iter().enumerate() {
479        let name = p.as_symbol().ok_or_else(|| EvalError::Malformed {
480            form: "lambda".into(),
481            reason: "param must be a symbol".into(),
482        })?;
483        if name == "&rest" || name == "&" {
484            if i + 2 != items.len() {
485                return Err(EvalError::Malformed {
486                    form: "lambda".into(),
487                    reason: "&rest must be followed by exactly one name".into(),
488                });
489            }
490            saw_rest = true;
491            continue;
492        }
493        if saw_rest {
494            rest = Some(name.to_string());
495            break;
496        }
497        params.push(name.to_string());
498    }
499    let _ = BTreeMap::<String, Value>::new(); // quell "unused" in future tweaks
500    Ok((params, rest))
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn evaluates_literals() {
509        let i = Interpreter::new();
510        assert!(matches!(i.eval_source("42").unwrap(), Value::Int(42)));
511        assert!(matches!(i.eval_source("#t").unwrap(), Value::Bool(true)));
512        match i.eval_source(r#""hi""#).unwrap() {
513            Value::Str(s) => assert_eq!(s, "hi"),
514            _ => panic!(),
515        }
516    }
517
518    #[test]
519    fn arithmetic() {
520        let i = Interpreter::new();
521        assert!(matches!(i.eval_source("(+ 1 2 3)").unwrap(), Value::Int(6)));
522        assert!(matches!(
523            i.eval_source("(* 2 3 4)").unwrap(),
524            Value::Int(24)
525        ));
526        assert!(matches!(i.eval_source("(- 10 3)").unwrap(), Value::Int(7)));
527        assert!(matches!(i.eval_source("(- 5)").unwrap(), Value::Int(-5)));
528    }
529
530    #[test]
531    fn if_and_booleans() {
532        let i = Interpreter::new();
533        assert!(matches!(
534            i.eval_source("(if (< 1 2) 'yes 'no)").unwrap(),
535            Value::Symbol(s) if s == "yes"
536        ));
537    }
538
539    #[test]
540    fn let_binds() {
541        let i = Interpreter::new();
542        let v = i.eval_source("(let ((x 10) (y 20)) (+ x y))").unwrap();
543        assert!(matches!(v, Value::Int(30)));
544    }
545
546    #[test]
547    fn lambda_and_apply() {
548        let i = Interpreter::new();
549        let v = i.eval_source("((lambda (x y) (+ x y)) 3 4)").unwrap();
550        assert!(matches!(v, Value::Int(7)));
551    }
552
553    #[test]
554    fn closures_capture_env() {
555        let i = Interpreter::new();
556        let v = i
557            .eval_source(
558                "(let ((make-add (lambda (x) (lambda (y) (+ x y))))) \
559                 ((make-add 3) 4))",
560            )
561            .unwrap();
562        assert!(matches!(v, Value::Int(7)));
563    }
564
565    #[test]
566    fn letrec_enables_mutual_reference() {
567        let i = Interpreter::new();
568        let v = i
569            .eval_source(
570                "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) \
571                          (odd?  (lambda (n) (if (= n 0) #f (even? (- n 1)))))) \
572                   (even? 10))",
573            )
574            .unwrap();
575        assert!(matches!(v, Value::Bool(true)));
576    }
577
578    #[test]
579    fn quasiquote_and_unquote() {
580        let i = Interpreter::new();
581        let v = i.eval_source("(let ((x 5)) `(1 ,x 3))").unwrap();
582        match v {
583            Value::List(xs) => {
584                assert_eq!(xs.len(), 3);
585                assert!(matches!(xs[1], Value::Int(5)));
586            }
587            _ => panic!(),
588        }
589    }
590
591    #[test]
592    fn string_append_and_tostring() {
593        let i = Interpreter::new();
594        let v = i
595            .eval_source(r#"(string-append "hello-" (toString 42))"#)
596            .unwrap();
597        assert!(matches!(v, Value::Str(s) if s == "hello-42"));
598    }
599
600    #[test]
601    fn derivation_builtin_produces_typed_value() {
602        let i = Interpreter::new();
603        let v = i
604            .eval_source(
605                r#"(derivation
606                     (attrs
607                       "name"    "hello"
608                       "version" "1.0"))"#,
609            )
610            .unwrap();
611        match v {
612            Value::Derivation(d) => {
613                assert_eq!(d.name, "hello");
614                assert_eq!(d.version.as_deref(), Some("1.0"));
615            }
616            _ => panic!("expected Derivation, got {v:?}"),
617        }
618    }
619
620    #[test]
621    fn derivation_store_path_is_deterministic() {
622        let i = Interpreter::new();
623        let v1 = i
624            .eval_source(r#"(derivation (attrs "name" "x" "version" "1"))"#)
625            .unwrap();
626        let v2 = i
627            .eval_source(r#"(derivation (attrs "name" "x" "version" "1"))"#)
628            .unwrap();
629        let (Value::Derivation(a), Value::Derivation(b)) = (v1, v2) else {
630            panic!("expected derivations");
631        };
632        assert_eq!(a.store_path(), b.store_path());
633    }
634}