Skip to main content

tatara_lisp_eval/
ffi.rs

1//! FFI — register Rust functions as callable Lisp procedures.
2//!
3//! Two registration modes:
4//!
5//!   - **Raw** (`Interpreter::register_fn`): you receive `&[Value]` and
6//!     pick values out yourself. Most flexible, no marshalling overhead.
7//!     Appropriate for primitives that need to inspect arg kinds
8//!     directly or handle variadic arguments.
9//!
10//!   - **Typed** (`Interpreter::register_typed{0,1,2,3,4}`): you declare
11//!     Rust arg + return types; the runtime marshals `Value` ↔ Rust
12//!     types via the `FromValue` and `IntoValue` traits. Arity is
13//!     inferred from the Rust signature. This is the common-case API
14//!     for embedder code.
15//!
16//! Values that need to cross the FFI boundary unchanged (e.g., opaque
17//! host handles) can be wrapped in `Value::Foreign(Arc<dyn Any>)` and
18//! downcast in the native fn body.
19
20use std::sync::Arc;
21
22use tatara_lisp::Span;
23
24use crate::error::{EvalError, Result};
25use crate::value::Value;
26
27/// How many arguments a registered function accepts.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum Arity {
30    Exact(usize),
31    AtLeast(usize),
32    Range(usize, usize),
33    Any,
34}
35
36impl Arity {
37    /// Check `got` against this arity; returns `Ok(())` or a reason string.
38    pub fn check(&self, got: usize) -> std::result::Result<(), String> {
39        match *self {
40            Self::Exact(n) if got == n => Ok(()),
41            Self::Exact(n) => Err(format!("expected exactly {n}, got {got}")),
42            Self::AtLeast(n) if got >= n => Ok(()),
43            Self::AtLeast(n) => Err(format!("expected at least {n}, got {got}")),
44            Self::Range(lo, hi) if got >= lo && got <= hi => Ok(()),
45            Self::Range(lo, hi) => Err(format!("expected {lo}..={hi}, got {got}")),
46            Self::Any => Ok(()),
47        }
48    }
49}
50
51/// A native Rust function the host has registered. Parameterized over the
52/// host context type `H` so the callable can read/write host state.
53///
54/// The simple flavor — no access to the function registry. Use this for
55/// primitives that operate purely on `Value` arguments. For higher-order
56/// primitives (`map`, `filter`, `fold`, ...) that need to invoke a
57/// callable `Value`, register via `Interpreter::register_higher_order_fn`
58/// instead — the host then receives a `Caller` it can use to call back
59/// into the eval loop.
60pub trait NativeCallable<H>: Send + Sync + 'static {
61    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value>;
62}
63
64impl<H, F> NativeCallable<H> for F
65where
66    F: Fn(&[Value], &mut H, Span) -> Result<Value> + Send + Sync + 'static,
67{
68    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value> {
69        (self)(args, host, call_span)
70    }
71}
72
73/// A higher-order Rust primitive — receives a `Caller` so it can invoke
74/// `Value::Closure` / `Value::NativeFn` arguments back into the eval loop.
75/// Used by `map`, `filter`, `fold`, `for-each`, and friends.
76pub trait HigherOrderCallable<H>: Send + Sync + 'static {
77    fn call(
78        &self,
79        args: &[Value],
80        host: &mut H,
81        caller: &Caller<H>,
82        call_span: Span,
83    ) -> Result<Value>;
84}
85
86impl<H, F> HigherOrderCallable<H> for F
87where
88    F: Fn(&[Value], &mut H, &Caller<H>, Span) -> Result<Value> + Send + Sync + 'static,
89{
90    fn call(
91        &self,
92        args: &[Value],
93        host: &mut H,
94        caller: &Caller<H>,
95        call_span: Span,
96    ) -> Result<Value> {
97        (self)(args, host, caller, call_span)
98    }
99}
100
101/// Handle that a higher-order primitive uses to invoke a callable `Value`
102/// back into the eval loop. Holds borrows of the eval-time read-only
103/// state — the function registry and the macro expander. `apply_value`
104/// dispatches through whichever `Value` kind the callee is (`Closure`,
105/// `NativeFn`, `HigherOrderFn`).
106///
107/// Construction is private — `Caller` only ever appears via
108/// `HigherOrderCallable::call`, so primitives can only obtain one for the
109/// duration of the call they're servicing.
110pub struct Caller<'a, H> {
111    pub(crate) registry: &'a FnRegistry<H>,
112    pub(crate) expander: &'a tatara_lisp::SpannedExpander,
113}
114
115impl<'a, H: 'static> Caller<'a, H> {
116    /// Apply a callable `Value` to `args` against this caller's registry.
117    /// Mirrors the eval loop's `apply` precisely — closures get a fresh
118    /// frame; native fns dispatch through the registry; higher-order
119    /// fns receive a fresh `Caller` of their own.
120    pub fn apply_value(
121        &self,
122        callee: &Value,
123        args: Vec<Value>,
124        host: &mut H,
125        call_span: Span,
126    ) -> Result<Value> {
127        crate::eval::apply_external(callee, args, call_span, self.registry, self.expander, host)
128    }
129
130    /// Borrow the macro expander — primitives like `macroexpand-1`
131    /// look up registered macros through this handle.
132    pub fn expander(&self) -> &tatara_lisp::SpannedExpander {
133        self.expander
134    }
135
136    /// Convenience: call a unary callable with one arg. Errors with a
137    /// canonical message if the callee is not a procedure.
138    pub fn call1(&self, f: &Value, x: Value, host: &mut H, span: Span) -> Result<Value> {
139        self.apply_value(f, vec![x], host, span)
140    }
141
142    /// Convenience: call a binary callable with two args.
143    pub fn call2(&self, f: &Value, a: Value, b: Value, host: &mut H, span: Span) -> Result<Value> {
144        self.apply_value(f, vec![a, b], host, span)
145    }
146}
147
148/// One registered callable. Internal storage; primitives don't see this.
149/// `Arc` (not `Box`) so the apply path can clone the callable out of the
150/// registry borrow before invoking it — letting `apply()` hold `&mut
151/// Interpreter` while a higher-order primitive runs (which lets that
152/// primitive re-enter the dispatch path with the same Interpreter).
153/// A primitive that may have to wait, split so that **waiting cannot
154/// consume**.
155///
156/// ## Why two phases
157///
158/// A one-phase parking primitive — one that inspects the host, decides it
159/// cannot proceed, and returns [`crate::vm::Vm::park`] — carries a contract
160/// the VM cannot check: it must not have consumed anything, because the
161/// parked call is *re-executed from the top*. Take-then-park loses whatever
162/// was taken.
163///
164/// That contract is exactly the kind that holds until the first interesting
165/// case. A selective `receive` is the interesting case: it takes a message,
166/// finds it does not match the pattern, and must wait — and the natural
167/// implementation of that loses the message on every non-match.
168///
169/// Splitting the primitive in two removes the possibility rather than
170/// warning about it. [`AwaitableCallable::ready`] gets `&H` — an immutable
171/// borrow — so **it cannot mutate the host at all**; the compiler rejects
172/// the attempt. [`AwaitableCallable::call`] gets `&mut H` and is invoked
173/// only once `ready` has said yes, so it never has a reason to park.
174///
175/// Take-then-park is not discouraged here. It does not typecheck.
176pub trait AwaitableCallable<H>: Send + Sync + 'static {
177    /// May this call proceed? Answered against an **immutable** host.
178    ///
179    /// Return `false` to park the calling process. The VM restores the stack
180    /// and retries the call later.
181    fn ready(&self, args: &[Value], host: &H) -> bool;
182
183    /// Do the work. Called only when [`Self::ready`] returned `true`.
184    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value>;
185}
186
187/// The ergonomic form: a pair of closures.
188pub struct Awaitable<R, C> {
189    pub ready: R,
190    pub call: C,
191}
192
193impl<H, R, C> AwaitableCallable<H> for Awaitable<R, C>
194where
195    R: Fn(&[Value], &H) -> bool + Send + Sync + 'static,
196    C: Fn(&[Value], &mut H, Span) -> Result<Value> + Send + Sync + 'static,
197{
198    fn ready(&self, args: &[Value], host: &H) -> bool {
199        (self.ready)(args, host)
200    }
201    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value> {
202        (self.call)(args, host, call_span)
203    }
204}
205
206pub(crate) enum FnImpl<H> {
207    Native(Arc<dyn NativeCallable<H>>),
208    Higher(Arc<dyn HigherOrderCallable<H>>),
209    Awaitable(Arc<dyn AwaitableCallable<H>>),
210}
211
212impl<H> Clone for FnImpl<H> {
213    fn clone(&self) -> Self {
214        match self {
215            Self::Native(f) => Self::Native(Arc::clone(f)),
216            Self::Higher(f) => Self::Higher(Arc::clone(f)),
217            Self::Awaitable(f) => Self::Awaitable(Arc::clone(f)),
218        }
219    }
220}
221
222/// Registry of registered native functions for an `Interpreter<H>`.
223pub(crate) struct FnRegistry<H> {
224    entries: Vec<FnEntry<H>>,
225}
226
227pub(crate) struct FnEntry<H> {
228    pub name: Arc<str>,
229    /// Kept for future registry introspection — arity checking at call
230    /// time uses the copy on `Value::NativeFn` for a quicker path.
231    #[allow(dead_code)]
232    pub arity: Arity,
233    pub callable: FnImpl<H>,
234}
235
236// Hand-written rather than derived: `#[derive(Clone)]` on a generic struct
237// adds a `H: Clone` bound, and `H` is the embedder's host type, which has no
238// reason to be cloneable. Nothing here actually holds an `H` — the callables
239// are `Arc<dyn …>` — so the bound would be a derive artefact that blocks every
240// real embedder from forking an interpreter.
241impl<H> Clone for FnEntry<H> {
242    fn clone(&self) -> Self {
243        Self {
244            name: Arc::clone(&self.name),
245            arity: self.arity,
246            callable: self.callable.clone(),
247        }
248    }
249}
250
251impl<H> Clone for FnRegistry<H> {
252    fn clone(&self) -> Self {
253        Self {
254            entries: self.entries.clone(),
255        }
256    }
257}
258
259impl<H> Default for FnRegistry<H> {
260    fn default() -> Self {
261        Self {
262            entries: Vec::new(),
263        }
264    }
265}
266
267impl<H> FnRegistry<H> {
268    pub(crate) fn new() -> Self {
269        Self::default()
270    }
271
272    pub(crate) fn insert(&mut self, entry: FnEntry<H>) {
273        // Shadow any earlier registration with the same name — last wins.
274        if let Some(slot) = self.entries.iter_mut().find(|e| e.name == entry.name) {
275            *slot = entry;
276        } else {
277            self.entries.push(entry);
278        }
279    }
280
281    pub(crate) fn lookup(&self, name: &str) -> Option<&FnEntry<H>> {
282        self.entries.iter().find(|e| &*e.name == name)
283    }
284}
285
286// ── Typed marshalling ──────────────────────────────────────────────────
287
288/// Convert from a Lisp `Value` into a Rust value. Implemented for the
289/// common primitive types and for `Value` itself (identity). Used by the
290/// `register_typed{N}` helpers to destructure args.
291pub trait FromValue: Sized {
292    fn from_value(v: &Value, at: Span) -> Result<Self>;
293}
294
295impl FromValue for Value {
296    fn from_value(v: &Value, _at: Span) -> Result<Self> {
297        Ok(v.clone())
298    }
299}
300
301impl FromValue for i64 {
302    fn from_value(v: &Value, at: Span) -> Result<Self> {
303        match v {
304            Value::Int(n) => Ok(*n),
305            other => Err(EvalError::type_mismatch("integer", other.type_name(), at)),
306        }
307    }
308}
309
310impl FromValue for f64 {
311    fn from_value(v: &Value, at: Span) -> Result<Self> {
312        match v {
313            Value::Int(n) => Ok(*n as f64),
314            Value::Float(n) => Ok(*n),
315            other => Err(EvalError::type_mismatch("number", other.type_name(), at)),
316        }
317    }
318}
319
320impl FromValue for bool {
321    fn from_value(v: &Value, at: Span) -> Result<Self> {
322        match v {
323            Value::Bool(b) => Ok(*b),
324            other => Err(EvalError::type_mismatch("bool", other.type_name(), at)),
325        }
326    }
327}
328
329impl FromValue for String {
330    fn from_value(v: &Value, at: Span) -> Result<Self> {
331        match v {
332            Value::Str(s) => Ok(s.to_string()),
333            other => Err(EvalError::type_mismatch("string", other.type_name(), at)),
334        }
335    }
336}
337
338impl FromValue for Arc<str> {
339    fn from_value(v: &Value, at: Span) -> Result<Self> {
340        match v {
341            Value::Str(s) => Ok(s.clone()),
342            Value::Symbol(s) => Ok(s.clone()),
343            Value::Keyword(s) => Ok(s.clone()),
344            other => Err(EvalError::type_mismatch(
345                "string/symbol",
346                other.type_name(),
347                at,
348            )),
349        }
350    }
351}
352
353impl FromValue for Vec<Value> {
354    fn from_value(v: &Value, at: Span) -> Result<Self> {
355        match v {
356            Value::Nil => Ok(Vec::new()),
357            Value::List(xs) => Ok(xs.as_ref().clone()),
358            other => Err(EvalError::type_mismatch("list", other.type_name(), at)),
359        }
360    }
361}
362
363impl<T: FromValue> FromValue for Option<T> {
364    fn from_value(v: &Value, at: Span) -> Result<Self> {
365        match v {
366            Value::Nil => Ok(None),
367            other => T::from_value(other, at).map(Some),
368        }
369    }
370}
371
372/// Convert a Rust value into a `Value` for Lisp. Implemented for the
373/// primitive types. The blanket `From<T> for Value` impls cover most
374/// cases; this trait is the named interface used by typed-helper
375/// registration.
376pub trait IntoValue {
377    fn into_value(self) -> Value;
378}
379
380impl IntoValue for Value {
381    fn into_value(self) -> Value {
382        self
383    }
384}
385
386impl IntoValue for () {
387    fn into_value(self) -> Value {
388        Value::Nil
389    }
390}
391
392impl IntoValue for bool {
393    fn into_value(self) -> Value {
394        Value::Bool(self)
395    }
396}
397
398impl IntoValue for i64 {
399    fn into_value(self) -> Value {
400        Value::Int(self)
401    }
402}
403
404impl IntoValue for f64 {
405    fn into_value(self) -> Value {
406        Value::Float(self)
407    }
408}
409
410impl IntoValue for String {
411    fn into_value(self) -> Value {
412        Value::Str(Arc::from(self))
413    }
414}
415
416impl IntoValue for &str {
417    fn into_value(self) -> Value {
418        Value::Str(Arc::from(self))
419    }
420}
421
422impl IntoValue for Arc<str> {
423    fn into_value(self) -> Value {
424        Value::Str(self)
425    }
426}
427
428impl<T: IntoValue> IntoValue for Option<T> {
429    fn into_value(self) -> Value {
430        match self {
431            None => Value::Nil,
432            Some(x) => x.into_value(),
433        }
434    }
435}
436
437impl<T: IntoValue> IntoValue for Vec<T> {
438    fn into_value(self) -> Value {
439        Value::list(self.into_iter().map(IntoValue::into_value))
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn arity_check() {
449        assert!(Arity::Exact(2).check(2).is_ok());
450        assert!(Arity::Exact(2).check(3).is_err());
451        assert!(Arity::AtLeast(1).check(5).is_ok());
452        assert!(Arity::AtLeast(1).check(0).is_err());
453        assert!(Arity::Range(1, 3).check(2).is_ok());
454        assert!(Arity::Range(1, 3).check(4).is_err());
455        assert!(Arity::Any.check(0).is_ok());
456        assert!(Arity::Any.check(1000).is_ok());
457    }
458
459    #[test]
460    fn from_value_round_trips_primitives() {
461        let sp = Span::synthetic();
462        assert_eq!(i64::from_value(&Value::Int(42), sp).unwrap(), 42);
463        assert_eq!(f64::from_value(&Value::Float(1.5), sp).unwrap(), 1.5);
464        assert!(bool::from_value(&Value::Bool(true), sp).unwrap());
465        assert_eq!(
466            String::from_value(&Value::Str(Arc::from("hi")), sp).unwrap(),
467            "hi"
468        );
469    }
470
471    #[test]
472    fn from_value_int_to_float_coerces() {
473        let sp = Span::synthetic();
474        assert_eq!(f64::from_value(&Value::Int(3), sp).unwrap(), 3.0);
475    }
476
477    #[test]
478    fn from_value_option_nil_is_none() {
479        let sp = Span::synthetic();
480        assert_eq!(
481            <Option<i64> as FromValue>::from_value(&Value::Nil, sp).unwrap(),
482            None
483        );
484        assert_eq!(
485            <Option<i64> as FromValue>::from_value(&Value::Int(7), sp).unwrap(),
486            Some(7)
487        );
488    }
489
490    #[test]
491    fn from_value_type_mismatch_reports_expected_kind() {
492        let sp = Span::synthetic();
493        let err = i64::from_value(&Value::Str(Arc::from("x")), sp).unwrap_err();
494        assert!(matches!(
495            err,
496            EvalError::TypeMismatch {
497                expected: "integer",
498                ..
499            }
500        ));
501    }
502
503    #[test]
504    fn into_value_round_trips() {
505        assert!(matches!(42i64.into_value(), Value::Int(42)));
506        assert!(matches!(true.into_value(), Value::Bool(true)));
507        assert!(matches!(().into_value(), Value::Nil));
508        match String::from("hello").into_value() {
509            Value::Str(s) => assert_eq!(&*s, "hello"),
510            other => panic!("{other:?}"),
511        }
512    }
513
514    #[test]
515    fn into_value_vec_produces_list() {
516        let v: Vec<i64> = vec![1, 2, 3];
517        match v.into_value() {
518            Value::List(xs) => {
519                assert_eq!(xs.len(), 3);
520                assert!(matches!(&xs[0], Value::Int(1)));
521            }
522            other => panic!("{other:?}"),
523        }
524    }
525}