Skip to main content

tatara_eval/
value.rs

1//! `Value` — runtime values produced by the interpreter.
2//!
3//! Layered carefully so a future arena-backed representation can replace the
4//! `Arc<...>` inner pointers without changing the public API. Today: ergonomic
5//! reference-counted variants; tomorrow: `&'arena Value` allocated from
6//! `bumpalo::Bump` held by `tatara-terreiro`.
7
8use std::collections::BTreeMap;
9use std::fmt;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13use tatara_lisp::Sexp;
14use tatara_nix::derivation::Derivation;
15use tatara_nix::store::StorePath;
16
17use crate::env::Env;
18
19/// Every runtime value flows through this enum. Variants match a Nix-ish
20/// ontology (atoms, lists, attrsets, functions, thunks, paths, derivations) so
21/// the Lisp surface can carry Nix's academic semantics verbatim.
22#[derive(Clone)]
23pub enum Value {
24    Nil,
25    Bool(bool),
26    Int(i64),
27    Float(f64),
28    Str(String),
29    Symbol(String),
30    Keyword(String),
31    Path(PathBuf),
32    List(Arc<Vec<Value>>),
33    Attrs(Arc<BTreeMap<String, Value>>),
34    Lambda(Arc<Lambda>),
35    Builtin(Arc<Builtin>),
36    Thunk(Arc<Thunk>),
37    Derivation(Arc<Derivation>),
38    StorePath(StorePath),
39    /// A quoted Sexp — lets Lisp programs manipulate code-as-data.
40    Quoted(Arc<Sexp>),
41}
42
43impl fmt::Debug for Value {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::Nil => write!(f, "nil"),
47            Self::Bool(b) => write!(f, "{b}"),
48            Self::Int(n) => write!(f, "{n}"),
49            Self::Float(n) => write!(f, "{n}"),
50            Self::Str(s) => write!(f, "{s:?}"),
51            Self::Symbol(s) => write!(f, "sym:{s}"),
52            Self::Keyword(s) => write!(f, ":{s}"),
53            Self::Path(p) => write!(f, "path:{}", p.display()),
54            Self::List(xs) => {
55                write!(f, "(")?;
56                for (i, x) in xs.iter().enumerate() {
57                    if i > 0 {
58                        write!(f, " ")?;
59                    }
60                    write!(f, "{x:?}")?;
61                }
62                write!(f, ")")
63            }
64            Self::Attrs(m) => {
65                write!(f, "{{")?;
66                for (i, (k, v)) in m.iter().enumerate() {
67                    if i > 0 {
68                        write!(f, " ")?;
69                    }
70                    write!(f, "{k} = {v:?}")?;
71                }
72                write!(f, "}}")
73            }
74            Self::Lambda(l) => write!(f, "<lambda/{}>", l.params.len()),
75            Self::Builtin(b) => write!(f, "<builtin {}>", b.name),
76            Self::Thunk(_) => write!(f, "<thunk>"),
77            Self::Derivation(d) => write!(f, "<deriv {}>", d.name),
78            Self::StorePath(p) => write!(f, "<store-path {p}>"),
79            Self::Quoted(s) => write!(f, "'{s}"),
80        }
81    }
82}
83
84impl fmt::Display for Value {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            Self::Nil => write!(f, ""),
88            Self::Str(s) => write!(f, "{s}"),
89            Self::Int(n) => write!(f, "{n}"),
90            Self::Float(n) => write!(f, "{n}"),
91            Self::Bool(b) => write!(f, "{b}"),
92            Self::Symbol(s) => write!(f, "{s}"),
93            Self::Keyword(s) => write!(f, ":{s}"),
94            Self::Path(p) => write!(f, "{}", p.display()),
95            Self::List(xs) => {
96                write!(f, "(")?;
97                for (i, x) in xs.iter().enumerate() {
98                    if i > 0 {
99                        write!(f, " ")?;
100                    }
101                    write!(f, "{x}")?;
102                }
103                write!(f, ")")
104            }
105            other => write!(f, "{other:?}"),
106        }
107    }
108}
109
110impl Value {
111    pub fn type_name(&self) -> &'static str {
112        match self {
113            Self::Nil => "nil",
114            Self::Bool(_) => "bool",
115            Self::Int(_) => "int",
116            Self::Float(_) => "float",
117            Self::Str(_) => "string",
118            Self::Symbol(_) => "symbol",
119            Self::Keyword(_) => "keyword",
120            Self::Path(_) => "path",
121            Self::List(_) => "list",
122            Self::Attrs(_) => "attrs",
123            Self::Lambda(_) => "lambda",
124            Self::Builtin(_) => "builtin",
125            Self::Thunk(_) => "thunk",
126            Self::Derivation(_) => "derivation",
127            Self::StorePath(_) => "store-path",
128            Self::Quoted(_) => "quoted",
129        }
130    }
131
132    pub fn is_truthy(&self) -> bool {
133        !matches!(self, Self::Nil | Self::Bool(false))
134    }
135
136    pub fn as_int(&self) -> Option<i64> {
137        match self {
138            Self::Int(n) => Some(*n),
139            _ => None,
140        }
141    }
142
143    pub fn as_str(&self) -> Option<&str> {
144        match self {
145            Self::Str(s) | Self::Symbol(s) | Self::Keyword(s) => Some(s),
146            _ => None,
147        }
148    }
149
150    pub fn as_list(&self) -> Option<&[Value]> {
151        match self {
152            Self::List(xs) => Some(xs),
153            _ => None,
154        }
155    }
156
157    pub fn as_attrs(&self) -> Option<&BTreeMap<String, Value>> {
158        match self {
159            Self::Attrs(m) => Some(m),
160            _ => None,
161        }
162    }
163
164    pub fn as_derivation(&self) -> Option<&Derivation> {
165        match self {
166            Self::Derivation(d) => Some(d),
167            _ => None,
168        }
169    }
170
171    pub fn as_path(&self) -> Option<&PathBuf> {
172        match self {
173            Self::Path(p) => Some(p),
174            _ => None,
175        }
176    }
177
178    /// Coerce this value to a plain `String` if that's a meaningful operation.
179    /// Matches Nix's `toString` semantics: strings/numbers/paths/derivations
180    /// have a canonical string form; lists/attrs/lambdas do not.
181    pub fn coerce_to_string(&self) -> Option<String> {
182        match self {
183            Self::Str(s) => Some(s.clone()),
184            Self::Symbol(s) | Self::Keyword(s) => Some(s.clone()),
185            Self::Int(n) => Some(n.to_string()),
186            Self::Float(n) => Some(n.to_string()),
187            Self::Bool(b) => Some(b.to_string()),
188            Self::Path(p) => Some(p.to_string_lossy().into_owned()),
189            Self::StorePath(p) => Some(p.render()),
190            Self::Derivation(d) => Some(d.store_path().render()),
191            _ => None,
192        }
193    }
194}
195
196// ── function types ───────────────────────────────────────────────────────
197
198/// A user-defined closure — captures its lexical environment.
199pub struct Lambda {
200    pub params: Vec<String>,
201    /// If present, extra args bind to this single name as a list (rest-args).
202    pub rest: Option<String>,
203    pub body: Vec<Sexp>,
204    pub env: Env,
205    pub name: Option<String>,
206}
207
208/// A native builtin. Takes already-evaluated args + current env + interpreter
209/// handle so it can evaluate Sexp if it needs to (for short-circuit forms, the
210/// interpreter uses special-form dispatch instead).
211pub type BuiltinFn = dyn Fn(&[Value]) -> crate::error::Result<Value> + Send + Sync;
212
213pub struct Builtin {
214    pub name: String,
215    pub arity: Arity,
216    pub func: Arc<BuiltinFn>,
217}
218
219#[derive(Clone, Copy, Debug)]
220pub enum Arity {
221    Exact(usize),
222    AtLeast(usize),
223    Any,
224}
225
226impl Arity {
227    pub fn check(&self, got: usize) -> bool {
228        match *self {
229            Self::Exact(n) => got == n,
230            Self::AtLeast(n) => got >= n,
231            Self::Any => true,
232        }
233    }
234
235    pub fn describe(&self) -> String {
236        match *self {
237            Self::Exact(n) => format!("{n}"),
238            Self::AtLeast(n) => format!("at least {n}"),
239            Self::Any => "any".into(),
240        }
241    }
242}
243
244// ── laziness ─────────────────────────────────────────────────────────────
245
246/// A deferred computation. Forced once; result cached. Enables Nix-style
247/// non-strict evaluation: `(let ((x big-compute)) small)` skips big-compute
248/// unless something forces `x`.
249pub struct Thunk {
250    pub cell: std::sync::Mutex<ThunkState>,
251}
252
253pub enum ThunkState {
254    Unevaluated { body: Sexp, env: Env },
255    Evaluating, // detects cycles
256    Forced(Value),
257}
258
259impl Thunk {
260    pub fn new(body: Sexp, env: Env) -> Arc<Self> {
261        Arc::new(Self {
262            cell: std::sync::Mutex::new(ThunkState::Unevaluated { body, env }),
263        })
264    }
265}