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 native Rust primitive that receives its arguments **by value**.
74///
75/// ## Why a second shape rather than widening `NativeCallable`
76///
77/// [`NativeCallable`] hands out a borrowed `&[Value]`, and that one
78/// signature decides the runtime's whole memory story. A borrowed slice is
79/// a live second reference to every `Arc` payload it names, so
80/// `Arc::get_mut` / `Arc::try_unwrap` **cannot succeed inside a primitive**
81/// — not "usually fails", cannot. Every copy-on-write update is therefore
82/// an unconditional full copy, whether or not anyone else can still observe
83/// the old value. `hash-map-set` was the measured instance: an O(n)
84/// `HashMap` clone per insert at every size.
85///
86/// Taking `Vec<Value>` **moves** the arguments in. A primitive whose
87/// argument happens to be the only reference to its payload can mutate that
88/// payload in place; one whose argument is shared still copies. The
89/// difference is read off the refcount at run time, so nothing has to be
90/// proved statically and no program changes meaning.
91///
92/// ## This is a parallel path, deliberately
93///
94/// 243 `register_fn` call sites across this workspace keep compiling
95/// untouched. Reach for `register_owned_fn` when a primitive **builds a new
96/// heap value out of an argument's heap value** — that is the whole class
97/// that can profit. A primitive that only reads its arguments gains nothing
98/// and should stay on [`NativeCallable`], where the borrow documents that it
99/// consumes nothing.
100///
101/// ## An owned primitive must not park
102///
103/// [`Park`](crate::vm::run::Park)'s contract is that a parking primitive has
104/// consumed nothing, because the parked call is re-executed from the top. This
105/// signature consumes its arguments by taking them — so the contract is
106/// unsatisfiable here, not merely hard to honour. That is what lets the VM
107/// skip preserving an owned call's arguments for a retry (`Vm::do_call`), and
108/// skipping that copy is what makes the uniqueness visible at all. A parking
109/// primitive belongs on `register_awaitable_fn`, whose two phases exist for
110/// exactly this reason; one that calls [`crate::vm::Vm::park`] from here gets
111/// [`crate::vm::run::VmError::ParkContract`] rather than a silently corrupted
112/// retry.
113pub trait OwnedCallable<H>: Send + Sync + 'static {
114    fn call(&self, args: Vec<Value>, host: &mut H, call_span: Span) -> Result<Value>;
115}
116
117impl<H, F> OwnedCallable<H> for F
118where
119    F: Fn(Vec<Value>, &mut H, Span) -> Result<Value> + Send + Sync + 'static,
120{
121    fn call(&self, args: Vec<Value>, host: &mut H, call_span: Span) -> Result<Value> {
122        (self)(args, host, call_span)
123    }
124}
125
126/// A higher-order Rust primitive — receives a `Caller` so it can invoke
127/// `Value::Closure` / `Value::NativeFn` arguments back into the eval loop.
128/// Used by `map`, `filter`, `fold`, `for-each`, and friends.
129pub trait HigherOrderCallable<H>: Send + Sync + 'static {
130    fn call(
131        &self,
132        args: &[Value],
133        host: &mut H,
134        caller: &Caller<H>,
135        call_span: Span,
136    ) -> Result<Value>;
137}
138
139impl<H, F> HigherOrderCallable<H> for F
140where
141    F: Fn(&[Value], &mut H, &Caller<H>, Span) -> Result<Value> + Send + Sync + 'static,
142{
143    fn call(
144        &self,
145        args: &[Value],
146        host: &mut H,
147        caller: &Caller<H>,
148        call_span: Span,
149    ) -> Result<Value> {
150        (self)(args, host, caller, call_span)
151    }
152}
153
154/// Handle that a higher-order primitive uses to invoke a callable `Value`
155/// back into the eval loop. Holds borrows of the eval-time read-only
156/// state — the function registry and the macro expander. `apply_value`
157/// dispatches through whichever `Value` kind the callee is (`Closure`,
158/// `NativeFn`, `HigherOrderFn`).
159///
160/// Construction is private — `Caller` only ever appears via
161/// `HigherOrderCallable::call`, so primitives can only obtain one for the
162/// duration of the call they're servicing.
163pub struct Caller<'a, H> {
164    pub(crate) registry: &'a FnRegistry<H>,
165    pub(crate) expander: &'a tatara_lisp::SpannedExpander,
166}
167
168impl<'a, H: 'static> Caller<'a, H> {
169    /// Apply a callable `Value` to `args` against this caller's registry.
170    /// Mirrors the eval loop's `apply` precisely — closures get a fresh
171    /// frame; native fns dispatch through the registry; higher-order
172    /// fns receive a fresh `Caller` of their own.
173    pub fn apply_value(
174        &self,
175        callee: &Value,
176        args: Vec<Value>,
177        host: &mut H,
178        call_span: Span,
179    ) -> Result<Value> {
180        crate::eval::apply_external(callee, args, call_span, self.registry, self.expander, host)
181    }
182
183    /// Borrow the macro expander — primitives like `macroexpand-1`
184    /// look up registered macros through this handle.
185    pub fn expander(&self) -> &tatara_lisp::SpannedExpander {
186        self.expander
187    }
188
189    /// Convenience: call a unary callable with one arg. Errors with a
190    /// canonical message if the callee is not a procedure.
191    pub fn call1(&self, f: &Value, x: Value, host: &mut H, span: Span) -> Result<Value> {
192        self.apply_value(f, vec![x], host, span)
193    }
194
195    /// Convenience: call a binary callable with two args.
196    pub fn call2(&self, f: &Value, a: Value, b: Value, host: &mut H, span: Span) -> Result<Value> {
197        self.apply_value(f, vec![a, b], host, span)
198    }
199}
200
201/// One registered callable. Internal storage; primitives don't see this.
202/// `Arc` (not `Box`) so the apply path can clone the callable out of the
203/// registry borrow before invoking it — letting `apply()` hold `&mut
204/// Interpreter` while a higher-order primitive runs (which lets that
205/// primitive re-enter the dispatch path with the same Interpreter).
206/// A primitive that may have to wait, split so that **waiting cannot
207/// consume**.
208///
209/// ## Why two phases
210///
211/// A one-phase parking primitive — one that inspects the host, decides it
212/// cannot proceed, and returns [`crate::vm::Vm::park`] — carries a contract
213/// the VM cannot check: it must not have consumed anything, because the
214/// parked call is *re-executed from the top*. Take-then-park loses whatever
215/// was taken.
216///
217/// That contract is exactly the kind that holds until the first interesting
218/// case. A selective `receive` is the interesting case: it takes a message,
219/// finds it does not match the pattern, and must wait — and the natural
220/// implementation of that loses the message on every non-match.
221///
222/// Splitting the primitive in two removes the possibility rather than
223/// warning about it. [`AwaitableCallable::ready`] gets `&H` — an immutable
224/// borrow — so **it cannot mutate the host at all**; the compiler rejects
225/// the attempt. [`AwaitableCallable::call`] gets `&mut H` and is invoked
226/// only once `ready` has said yes, so it never has a reason to park.
227///
228/// Take-then-park is not discouraged here. It does not typecheck.
229pub trait AwaitableCallable<H>: Send + Sync + 'static {
230    /// May this call proceed? Answered against an **immutable** host.
231    ///
232    /// Return `false` to park the calling process. The VM restores the stack
233    /// and retries the call later.
234    fn ready(&self, args: &[Value], host: &H) -> bool;
235
236    /// Do the work. Called only when [`Self::ready`] returned `true`.
237    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value>;
238}
239
240/// The ergonomic form: a pair of closures.
241pub struct Awaitable<R, C> {
242    pub ready: R,
243    pub call: C,
244}
245
246impl<H, R, C> AwaitableCallable<H> for Awaitable<R, C>
247where
248    R: Fn(&[Value], &H) -> bool + Send + Sync + 'static,
249    C: Fn(&[Value], &mut H, Span) -> Result<Value> + Send + Sync + 'static,
250{
251    fn ready(&self, args: &[Value], host: &H) -> bool {
252        (self.ready)(args, host)
253    }
254    fn call(&self, args: &[Value], host: &mut H, call_span: Span) -> Result<Value> {
255        (self.call)(args, host, call_span)
256    }
257}
258
259pub(crate) enum FnImpl<H> {
260    Native(Arc<dyn NativeCallable<H>>),
261    Owned(Arc<dyn OwnedCallable<H>>),
262    Higher(Arc<dyn HigherOrderCallable<H>>),
263    Awaitable(Arc<dyn AwaitableCallable<H>>),
264}
265
266impl<H> Clone for FnImpl<H> {
267    fn clone(&self) -> Self {
268        match self {
269            Self::Native(f) => Self::Native(Arc::clone(f)),
270            Self::Owned(f) => Self::Owned(Arc::clone(f)),
271            Self::Higher(f) => Self::Higher(Arc::clone(f)),
272            Self::Awaitable(f) => Self::Awaitable(Arc::clone(f)),
273        }
274    }
275}
276
277impl<H> FnImpl<H> {
278    /// May a call into this callable return the park sentinel
279    /// ([`crate::vm::Vm::park`])?
280    ///
281    /// The VM preserves a copy of a call's arguments so it can rebuild the
282    /// operand stack if the call parks and has to be retried. That copy is
283    /// also a live second reference to every argument for the whole call —
284    /// which is precisely what stops an owned primitive from ever seeing an
285    /// unaliased argument. So the VM asks this first and only pays when the
286    /// answer is yes.
287    ///
288    /// **Only `Owned` answers `false`, and the reason is the signature, not
289    /// the body.** [`Park`](crate::vm::run::Park)'s contract is that a parking
290    /// primitive *must have consumed nothing*, because the call is re-executed
291    /// from the top. An [`OwnedCallable`] receives `Vec<Value>` **by value**:
292    /// it has consumed its arguments before its first line runs. So a parking
293    /// owned primitive is not merely discouraged, it cannot satisfy the
294    /// contract, and skipping its retry copy costs nothing that was reachable.
295    ///
296    /// The three other shapes all answer `true`, `Native` included — an
297    /// earlier version of this reasoned from "no [`Caller`], so it cannot
298    /// reach the eval loop" and got `Native` wrong. [`crate::vm::Vm::park`] is
299    /// public, and `vm::run::tests::install_parking_prim` registers a parking
300    /// primitive through `register_fn` today. Reachability of the *eval loop*
301    /// was never the question; reachability of `Vm::park` is, and only the
302    /// owned signature settles it.
303    pub(crate) fn may_park(&self) -> bool {
304        match self {
305            Self::Owned(_) => false,
306            Self::Native(_) | Self::Higher(_) | Self::Awaitable(_) => true,
307        }
308    }
309}
310
311/// Registry of registered native functions for an `Interpreter<H>`.
312pub(crate) struct FnRegistry<H> {
313    entries: Vec<FnEntry<H>>,
314}
315
316pub(crate) struct FnEntry<H> {
317    pub name: Arc<str>,
318    /// Kept for future registry introspection — arity checking at call
319    /// time uses the copy on `Value::NativeFn` for a quicker path.
320    #[allow(dead_code)]
321    pub arity: Arity,
322    pub callable: FnImpl<H>,
323}
324
325// Hand-written rather than derived: `#[derive(Clone)]` on a generic struct
326// adds a `H: Clone` bound, and `H` is the embedder's host type, which has no
327// reason to be cloneable. Nothing here actually holds an `H` — the callables
328// are `Arc<dyn …>` — so the bound would be a derive artefact that blocks every
329// real embedder from forking an interpreter.
330impl<H> Clone for FnEntry<H> {
331    fn clone(&self) -> Self {
332        Self {
333            name: Arc::clone(&self.name),
334            arity: self.arity,
335            callable: self.callable.clone(),
336        }
337    }
338}
339
340impl<H> Clone for FnRegistry<H> {
341    fn clone(&self) -> Self {
342        Self {
343            entries: self.entries.clone(),
344        }
345    }
346}
347
348impl<H> Default for FnRegistry<H> {
349    fn default() -> Self {
350        Self {
351            entries: Vec::new(),
352        }
353    }
354}
355
356impl<H> FnRegistry<H> {
357    pub(crate) fn new() -> Self {
358        Self::default()
359    }
360
361    pub(crate) fn insert(&mut self, entry: FnEntry<H>) {
362        // Shadow any earlier registration with the same name — last wins.
363        if let Some(slot) = self.entries.iter_mut().find(|e| e.name == entry.name) {
364            *slot = entry;
365        } else {
366            self.entries.push(entry);
367        }
368    }
369
370    pub(crate) fn lookup(&self, name: &str) -> Option<&FnEntry<H>> {
371        self.entries.iter().find(|e| &*e.name == name)
372    }
373}
374
375// ── Typed marshalling ──────────────────────────────────────────────────
376
377/// Convert from a Lisp `Value` into a Rust value. Implemented for the
378/// common primitive types and for `Value` itself (identity). Used by the
379/// `register_typed{N}` helpers to destructure args.
380pub trait FromValue: Sized {
381    fn from_value(v: &Value, at: Span) -> Result<Self>;
382}
383
384impl FromValue for Value {
385    fn from_value(v: &Value, _at: Span) -> Result<Self> {
386        Ok(v.clone())
387    }
388}
389
390impl FromValue for i64 {
391    fn from_value(v: &Value, at: Span) -> Result<Self> {
392        match v {
393            Value::Int(n) => Ok(*n),
394            other => Err(EvalError::type_mismatch("integer", other.type_name(), at)),
395        }
396    }
397}
398
399impl FromValue for f64 {
400    fn from_value(v: &Value, at: Span) -> Result<Self> {
401        match v {
402            Value::Int(n) => Ok(*n as f64),
403            Value::Float(n) => Ok(*n),
404            other => Err(EvalError::type_mismatch("number", other.type_name(), at)),
405        }
406    }
407}
408
409impl FromValue for bool {
410    fn from_value(v: &Value, at: Span) -> Result<Self> {
411        match v {
412            Value::Bool(b) => Ok(*b),
413            other => Err(EvalError::type_mismatch("bool", other.type_name(), at)),
414        }
415    }
416}
417
418impl FromValue for String {
419    fn from_value(v: &Value, at: Span) -> Result<Self> {
420        match v {
421            Value::Str(s) => Ok(s.to_string()),
422            other => Err(EvalError::type_mismatch("string", other.type_name(), at)),
423        }
424    }
425}
426
427impl FromValue for Arc<str> {
428    fn from_value(v: &Value, at: Span) -> Result<Self> {
429        match v {
430            Value::Str(s) => Ok(s.clone()),
431            Value::Symbol(s) => Ok(s.clone()),
432            Value::Keyword(s) => Ok(s.clone()),
433            other => Err(EvalError::type_mismatch(
434                "string/symbol",
435                other.type_name(),
436                at,
437            )),
438        }
439    }
440}
441
442impl FromValue for Vec<Value> {
443    fn from_value(v: &Value, at: Span) -> Result<Self> {
444        match v {
445            Value::Nil => Ok(Vec::new()),
446            Value::List(xs) => Ok(xs.as_ref().clone()),
447            other => Err(EvalError::type_mismatch("list", other.type_name(), at)),
448        }
449    }
450}
451
452impl<T: FromValue> FromValue for Option<T> {
453    fn from_value(v: &Value, at: Span) -> Result<Self> {
454        match v {
455            Value::Nil => Ok(None),
456            other => T::from_value(other, at).map(Some),
457        }
458    }
459}
460
461/// Convert a Rust value into a `Value` for Lisp. Implemented for the
462/// primitive types. The blanket `From<T> for Value` impls cover most
463/// cases; this trait is the named interface used by typed-helper
464/// registration.
465pub trait IntoValue {
466    fn into_value(self) -> Value;
467}
468
469impl IntoValue for Value {
470    fn into_value(self) -> Value {
471        self
472    }
473}
474
475impl IntoValue for () {
476    fn into_value(self) -> Value {
477        Value::Nil
478    }
479}
480
481impl IntoValue for bool {
482    fn into_value(self) -> Value {
483        Value::Bool(self)
484    }
485}
486
487impl IntoValue for i64 {
488    fn into_value(self) -> Value {
489        Value::Int(self)
490    }
491}
492
493impl IntoValue for f64 {
494    fn into_value(self) -> Value {
495        Value::Float(self)
496    }
497}
498
499impl IntoValue for String {
500    fn into_value(self) -> Value {
501        Value::Str(Arc::from(self))
502    }
503}
504
505impl IntoValue for &str {
506    fn into_value(self) -> Value {
507        Value::Str(Arc::from(self))
508    }
509}
510
511impl IntoValue for Arc<str> {
512    fn into_value(self) -> Value {
513        Value::Str(self)
514    }
515}
516
517impl<T: IntoValue> IntoValue for Option<T> {
518    fn into_value(self) -> Value {
519        match self {
520            None => Value::Nil,
521            Some(x) => x.into_value(),
522        }
523    }
524}
525
526impl<T: IntoValue> IntoValue for Vec<T> {
527    fn into_value(self) -> Value {
528        Value::list(self.into_iter().map(IntoValue::into_value))
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn arity_check() {
538        assert!(Arity::Exact(2).check(2).is_ok());
539        assert!(Arity::Exact(2).check(3).is_err());
540        assert!(Arity::AtLeast(1).check(5).is_ok());
541        assert!(Arity::AtLeast(1).check(0).is_err());
542        assert!(Arity::Range(1, 3).check(2).is_ok());
543        assert!(Arity::Range(1, 3).check(4).is_err());
544        assert!(Arity::Any.check(0).is_ok());
545        assert!(Arity::Any.check(1000).is_ok());
546    }
547
548    #[test]
549    fn from_value_round_trips_primitives() {
550        let sp = Span::synthetic();
551        assert_eq!(i64::from_value(&Value::Int(42), sp).unwrap(), 42);
552        assert_eq!(f64::from_value(&Value::Float(1.5), sp).unwrap(), 1.5);
553        assert!(bool::from_value(&Value::Bool(true), sp).unwrap());
554        assert_eq!(
555            String::from_value(&Value::Str(Arc::from("hi")), sp).unwrap(),
556            "hi"
557        );
558    }
559
560    #[test]
561    fn from_value_int_to_float_coerces() {
562        let sp = Span::synthetic();
563        assert_eq!(f64::from_value(&Value::Int(3), sp).unwrap(), 3.0);
564    }
565
566    #[test]
567    fn from_value_option_nil_is_none() {
568        let sp = Span::synthetic();
569        assert_eq!(
570            <Option<i64> as FromValue>::from_value(&Value::Nil, sp).unwrap(),
571            None
572        );
573        assert_eq!(
574            <Option<i64> as FromValue>::from_value(&Value::Int(7), sp).unwrap(),
575            Some(7)
576        );
577    }
578
579    #[test]
580    fn from_value_type_mismatch_reports_expected_kind() {
581        let sp = Span::synthetic();
582        let err = i64::from_value(&Value::Str(Arc::from("x")), sp).unwrap_err();
583        assert!(matches!(
584            err,
585            EvalError::TypeMismatch {
586                expected: "integer",
587                ..
588            }
589        ));
590    }
591
592    #[test]
593    fn into_value_round_trips() {
594        assert!(matches!(42i64.into_value(), Value::Int(42)));
595        assert!(matches!(true.into_value(), Value::Bool(true)));
596        assert!(matches!(().into_value(), Value::Nil));
597        match String::from("hello").into_value() {
598            Value::Str(s) => assert_eq!(&*s, "hello"),
599            other => panic!("{other:?}"),
600        }
601    }
602
603    #[test]
604    fn into_value_vec_produces_list() {
605        let v: Vec<i64> = vec![1, 2, 3];
606        match v.into_value() {
607            Value::List(xs) => {
608                assert_eq!(xs.len(), 3);
609                assert!(matches!(&xs[0], Value::Int(1)));
610            }
611            other => panic!("{other:?}"),
612        }
613    }
614}