Skip to main content

code_native/
lib.rs

1//! Safe(r) Rust bindings for writing a native module — `.so` or `.a` — for
2//! the [Code programming language](https://github.com/codelovesme/code).
3//!
4//! `code_abi.h`'s contract needs two things from a module: agreement on the
5//! `CodeValue` wire layout, and a `code_release` (plus friends) built from
6//! the *real* `runtime.c` rather than a reimplementation that merely looks
7//! compatible — getting refcounting subtly wrong is the kind of bug that
8//! corrupts memory rather than crashing where you'd notice. This crate's
9//! `build.rs` compiles the vendored `runtime.c` and links it into your
10//! `cdylib` directly, so every function below calls the same code the host
11//! runtime and every C module trust. (A `.a` module wants the opposite —
12//! the host already has the one runtime there — which is what the
13//! `static-module` feature turns off; see this crate's README.)
14//!
15//! # Quick start
16//!
17//! ```rust,ignore
18//! use code_native::*;
19//!
20//! #[no_mangle]
21//! pub extern "C" fn code_module_abi_version() -> u32 {
22//!     CODE_ABI_VERSION
23//! }
24//!
25//! #[no_mangle]
26//! pub unsafe extern "C" fn code_module_dispatch(out: *mut CodeValue, particle: *const CodeValue) {
27//!     let particle = &*particle;
28//!     match read_field_str(particle, "_class") {
29//!         Some("Double") => {
30//!             let value = read_field_number(particle, "value").unwrap_or(0.0);
31//!             make_result(&mut *out, "DoubleResult", |slot| code_number(slot, value * 2.0));
32//!         }
33//!         // A class this module does not handle answers null — see
34//!         // docs/todo/errors-as-particles.md.
35//!         _ => null(&mut *out),
36//!     }
37//! }
38//! ```
39//!
40//! Build with `crate-type = ["cdylib"]`, then `link "libmymodule.so" as m`
41//! from `.code` source. See this crate's README for the full walkthrough,
42//! including `.a` static modules and `code_module_vars`.
43//!
44//! To *speak first* rather than only answer — pushing particles into the
45//! program, which is what `Log`/`Exception`/`Tick`-shaped traffic needs —
46//! add [`declare_inbound!`] and call [`emit_inbound`]:
47//!
48//! ```rust,ignore
49//! code_native::declare_inbound!();
50//!
51//! fn report(message: &str) {
52//!     let mut p = CodeValue::zeroed();
53//!     // ... build a particle ...
54//!     emit_inbound(&p);
55//!     release(&mut p);
56//! }
57//! ```
58//!
59//! A pushed class the program has no handler for is dropped, so a module may
60//! report without every program that links it having to listen.
61//!
62//! `code_module_dispatch` and `code_module_abi_version` are the two required
63//! exports — there is no macro generating them here (unlike the *old*
64//! language's `code-native`): the new ABI dropped the descriptor-table
65//! design for one function a module dispatches through itself, so there is
66//! no boilerplate left to generate. `code_release` needs no Rust code at
67//! all — it comes from the linked `runtime.c` object automatically.
68
69use std::ffi::{c_char, c_int, c_void, CStr};
70use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
71
72// ===========================================================================
73// Wire layout — bit-for-bit `code_abi.h`. Only the pointer/int/float shapes
74// matter for ABI compatibility (not what they're named), but names are kept
75// identical to the header so the two are trivially diffable.
76// ===========================================================================
77
78/// Current ABI version. A module's `code_module_abi_version` must return
79/// this.
80pub const CODE_ABI_VERSION: u32 = 2;
81
82pub mod borrowed;
83
84/// Byte stride of an array/object element buffer: one [`CodeValue`]. Address
85/// a buffer through [`slot_at`] all the same.
86pub const CODE_VALUE_SLOT_SIZE: usize = 16;
87
88#[repr(C)]
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum CodeTag {
91    Number,
92    Str,
93    Bool,
94    Null,
95    Array,
96    Object,
97}
98
99/// A value: its kind, whether it holds a counted block, and one payload the
100/// kind decides — read it through the methods (`number()`, `len()`, …),
101/// which answer 0/null for a value of another kind. ABI version 2: 16 bytes,
102/// a list's count and keys in its block (`code_abi.h`'s `CodeList`).
103#[repr(C)]
104pub struct CodeValue {
105    pub tag: CodeTag,
106    pub heap: c_int,
107    payload: Payload,
108}
109
110#[repr(C)]
111#[derive(Clone, Copy)]
112union Payload {
113    number: f64,
114    str: *const c_char,
115    boolean: c_int,
116    list: *mut CodeList,
117}
118
119/// The head of a list's or object's block: its count and (an object's) its
120/// keys, followed by its [`CODE_VALUE_SLOT_SIZE`]-strided slots.
121#[repr(C)]
122pub struct CodeList {
123    pub len: i64,
124    pub keys: *mut *const c_char,
125}
126
127impl CodeValue {
128    /// A Number's value; 0 for anything else.
129    pub fn number(&self) -> f64 {
130        if self.tag == CodeTag::Number {
131            unsafe { self.payload.number }
132        } else {
133            0.0
134        }
135    }
136
137    /// A Str's characters; null for anything else.
138    pub fn str_ptr(&self) -> *const c_char {
139        if self.tag == CodeTag::Str {
140            unsafe { self.payload.str }
141        } else {
142            std::ptr::null()
143        }
144    }
145
146    /// A Bool's flag; 0 for anything else.
147    pub fn boolean(&self) -> c_int {
148        if self.tag == CodeTag::Bool {
149            unsafe { self.payload.boolean }
150        } else {
151            0
152        }
153    }
154
155    fn list(&self) -> *mut CodeList {
156        if matches!(self.tag, CodeTag::Array | CodeTag::Object) {
157            unsafe { self.payload.list }
158        } else {
159            std::ptr::null_mut()
160        }
161    }
162
163    /// An Array's or Object's element count; 0 for anything else.
164    pub fn len(&self) -> i64 {
165        let list = self.list();
166        if list.is_null() {
167            0
168        } else {
169            unsafe { (*list).len }
170        }
171    }
172
173    /// Whether [`len`](Self::len) is 0.
174    pub fn is_empty(&self) -> bool {
175        self.len() == 0
176    }
177
178    /// An Array's or Object's slots, for [`slot_at`]; null when it has none.
179    pub fn items(&self) -> *mut c_void {
180        let list = self.list();
181        if list.is_null() {
182            std::ptr::null_mut()
183        } else {
184            unsafe { list.add(1) as *mut c_void }
185        }
186    }
187
188    /// An Object's key pointers, parallel to [`items`](Self::items); null
189    /// for anything else.
190    pub fn keys(&self) -> *mut *const c_char {
191        let list = self.list();
192        if list.is_null() {
193            std::ptr::null_mut()
194        } else {
195            unsafe { (*list).keys }
196        }
197    }
198
199    /// An all-zero value — tag `Number`, `0.0`, not heap-owned. Bit-for-bit
200    /// what `CodeValue x = {0};` produces in C, and the required starting
201    /// state before passing `&mut` to any constructor below (each one calls
202    /// `code_release` on `out` first, exactly like the C ABI expects).
203    pub fn zeroed() -> Self {
204        // SAFETY: an all-zero-bytes CodeValue is a valid Number(0.0), which
205        // `code_release` (called by every constructor before overwriting
206        // `out`) already treats as a safe no-op — the same invariant `{0}`
207        // relies on in every C module.
208        unsafe { std::mem::zeroed() }
209    }
210}
211
212impl Default for CodeValue {
213    fn default() -> Self {
214        Self::zeroed()
215    }
216}
217
218#[repr(C)]
219pub struct CodeVarList {
220    pub count: i64,
221    pub names: *const *const c_char,
222    /// `CODE_VALUE_SLOT_SIZE` stride, `count` slots — see [`slot_at`].
223    pub values: *mut CodeValue,
224}
225
226// Both types carry raw pointers, so Rust doesn't derive Send/Sync for them
227// automatically — but `code_module_vars` (see README) is exactly the case
228// that needs a `static`/`OnceLock<CodeVarList>`, and the host only ever
229// reads this data (once, at `link` time), never mutates it concurrently.
230// Matches the old language's own `code-abi` crate, which needed the same
231// impls for the same reason.
232unsafe impl Send for CodeValue {}
233unsafe impl Sync for CodeValue {}
234unsafe impl Send for CodeVarList {}
235unsafe impl Sync for CodeVarList {}
236
237// ===========================================================================
238// Raw bindings to `runtime.c`'s exported (non-`static`) functions — the same
239// symbols `code_abi.h` declares for a C module. Calling into the actual
240// compiled `runtime.c`, not a port of it, is what keeps this crate free of
241// the layout-drift risk the *old* language's `code-native`/`code-abi` pair
242// needed a dedicated test to guard against.
243// ===========================================================================
244
245extern "C" {
246    fn code_number(out: *mut CodeValue, n: f64);
247    fn code_str(out: *mut CodeValue, s: *const c_char);
248    fn code_bool(out: *mut CodeValue, b: c_int);
249    fn code_null(out: *mut CodeValue);
250    fn code_array(out: *mut CodeValue, items: *mut c_void, len: i64);
251    fn code_object(out: *mut CodeValue, keys: *mut *const c_char, values: *mut c_void, len: i64);
252    fn code_copy(out: *mut CodeValue, src: *const CodeValue);
253    fn code_retain(v: *const CodeValue);
254    fn code_values_equal(a: *const CodeValue, b: *const CodeValue) -> c_int;
255    fn code_runtime_error(message: *const c_char) -> !;
256
257    // `build.rs` compiles `runtime.c` with `code_release` renamed to this at
258    // the preprocessor level (`-D`), and this crate re-exports it below under
259    // the real name from a function rustc actually treats as part of the
260    // crate (not an archive) — see that `#[no_mangle]` fn's own doc comment
261    // for why the rename is needed at all.
262    #[cfg(all(feature = "shared-module", not(feature = "static-module")))]
263    fn code_native_vendored_release(v: *mut CodeValue);
264}
265
266// A `.a` module links against the *host's* runtime, which already defines
267// `code_release` under its real name — so there is nothing to rename and
268// nothing to re-export, and `release` below calls it directly.
269#[cfg(any(not(feature = "shared-module"), feature = "static-module"))]
270extern "C" {
271    fn code_release(v: *mut CodeValue);
272}
273
274/// The ABI's required `code_release` export. Defined here, as a real Rust
275/// function, rather than left as whatever `runtime.c`'s own `code_release`
276/// would otherwise be: `cdylib` targets get `--exclude-libs=ALL` from
277/// rustc by default, which hides every symbol pulled in from a *linked
278/// static archive* (exactly what `build.rs`'s `cc::Build::compile` produces
279/// from `runtime.c`) out of the shared library's dynamic symbol table —
280/// even though this crate's own code calls it just fine internally. A
281/// symbol the crate defines directly (this function) isn't subject to that
282/// exclusion, so renaming the archive's copy and re-exporting it from here
283/// is what makes the host's `dlsym("code_release")` actually find it.
284///
285/// # Safety
286/// `v` must point to a valid, initialized `CodeValue` — the same
287/// requirement `runtime.c`'s own `code_release` has. The host only ever
288/// calls this on values it deep-copied out of your `code_module_dispatch`
289/// result, so you should never need to call it yourself except via
290/// [`release`].
291#[cfg(all(feature = "shared-module", not(feature = "static-module")))]
292#[no_mangle]
293pub unsafe extern "C" fn code_release(v: *mut CodeValue) {
294    code_native_vendored_release(v)
295}
296
297/// Addresses slot `index` of a [`CODE_VALUE_SLOT_SIZE`]-strided buffer —
298/// the Rust equivalent of `code_abi.h`'s `code_slot_at`. Pure pointer
299/// arithmetic, safe to reimplement independently (no allocator/refcount
300/// logic to drift from `runtime.c`).
301pub fn slot_at(base: *mut c_void, index: i64) -> *mut CodeValue {
302    (base as *mut u8).wrapping_offset(index as isize * CODE_VALUE_SLOT_SIZE as isize)
303        as *mut CodeValue
304}
305
306fn cstr(s: &str) -> std::ffi::CString {
307    std::ffi::CString::new(s).unwrap_or_else(|_| std::ffi::CString::new("<invalid-utf8>").unwrap())
308}
309
310// ===========================================================================
311// Safe scalar constructors — thin wrappers: `code_release`s `out` first
312// (matching every `runtime.c` constructor's own contract), then delegates.
313// ===========================================================================
314
315/// Write a Number into `out`.
316pub fn number(out: &mut CodeValue, n: f64) {
317    unsafe { code_number(out, n) }
318}
319
320/// Write a Str into `out`, borrowing `s` for `'static` (a string literal or
321/// otherwise permanently-alive buffer) rather than copying it — matching
322/// `code_str`'s own borrowing contract. Use [`owned_str`] for a value built
323/// at runtime that needs its own heap block.
324pub fn borrowed_str(out: &mut CodeValue, s: &'static CStr) {
325    unsafe { code_str(out, s.as_ptr()) }
326}
327
328/// Write a Str into `out` from a freshly-built Rust string. Leaks the
329/// `CString` — acceptable here because the value crosses into the host's
330/// own heap the moment your `code_module_dispatch` returns (the host
331/// deep-copies your result and then calls your module's `code_release` on
332/// it, which only ever frees what `runtime.c`'s own allocator built, never
333/// this leaked buffer).
334pub fn owned_str(out: &mut CodeValue, s: &str) {
335    let c = cstr(s);
336    unsafe { code_str(out, c.as_ptr()) }
337    std::mem::forget(c);
338}
339
340/// Write a Bool into `out`.
341pub fn boolean(out: &mut CodeValue, b: bool) {
342    unsafe { code_bool(out, b as c_int) }
343}
344
345/// Write Null into `out`.
346pub fn null(out: &mut CodeValue) {
347    unsafe { code_null(out) }
348}
349
350/// Release whatever `v` holds — call on every temporary [`CodeValue`] you
351/// built and no longer need (matching `runtime.c`'s own refcounting rule:
352/// every slot that ever named a heap block owns exactly one reference to
353/// it).
354pub fn release(v: &mut CodeValue) {
355    unsafe { code_release(v) }
356}
357
358/// Deep-copy `src` into `out` — `out` ends up owning its own references to
359/// everything `src` points at, and `src` is left untouched. This is how a
360/// handler passes a value it did not build itself along (e.g. an `Echo`
361/// returning its operand): the copy takes new references, so neither side's
362/// lifetime constrains the other.
363pub fn copy(out: &mut CodeValue, src: &CodeValue) {
364    unsafe { code_copy(out, src) }
365}
366
367/// Increment `v`'s refcount — needed only if you're holding onto a
368/// [`CodeValue`] you didn't just build yourself (e.g. a borrowed field from
369/// [`find_field`]) somewhere that will outlive the call it came from.
370/// Every retained value must be balanced by a [`release`].
371pub fn retain(v: &CodeValue) {
372    unsafe { code_retain(v) }
373}
374
375/// `obj.field` field access. **Total**: a missing field, or an `obj` that is
376/// not an Object at all, writes Null into `out`.
377///
378/// Total is where this differs from the language, and deliberately. `.code`
379/// source treats a non-Object operand as an *error* (`"abc".length` fails,
380/// which the README states as a rule), and `runtime.c` still has a
381/// `code_field` that does exactly that — for the compiler. It is not in
382/// `code_abi.h`, and this no longer calls it.
383///
384/// The reason is that a module cannot use a fallible accessor safely. Since
385/// phase 3 (2026-08-28) a runtime failure travels by a flag that only the
386/// *host's* generated code reads, and a `.so` carries its own copy of the
387/// runtime — so a failure raised inside a module sets the module's flag and
388/// is never seen. One function cannot be both fallible for the language and
389/// total for modules, so the ABI keeps the total one. A module that wants to
390/// refuse a wrong-typed operand says so itself, with [`exception`].
391pub fn field(out: &mut CodeValue, obj: &CodeValue, name: &str) {
392    match find_field(obj, name) {
393        Some(value) => copy(out, value),
394        None => null(out),
395    }
396}
397
398/// `arr[index]` element access, with the same totality as [`field`]: an
399/// out-of-bounds index, a non-Number index into an Array, a non-String key
400/// into an Object, or an `arr` that is neither, all write Null.
401///
402/// Matches `.code`'s own rules for everything except that last case, for the
403/// reason [`field`] gives.
404pub fn index(out: &mut CodeValue, arr: &CodeValue, i: &CodeValue) {
405    match arr.tag {
406        CodeTag::Array => {
407            // The language indexes arrays by Number, and only by a Number
408            // that is a whole one in range — `xs[1.5]` and `xs[99]` are both
409            // null, not errors.
410            let n = if i.tag == CodeTag::Number {
411                i.number()
412            } else {
413                f64::NAN
414            };
415            let whole = n as i64;
416            if whole as f64 == n && whole >= 0 && whole < arr.len() {
417                copy(out, unsafe { &*slot_at(arr.items(), whole) });
418            } else {
419                null(out);
420            }
421        }
422        // An Object is keyed by Str — the same split `loop` uses — so a
423        // computed key is just `find_field` under another name.
424        CodeTag::Object => match read_str(i).and_then(|key| find_field(arr, key)) {
425            Some(value) => copy(out, value),
426            None => null(out),
427        },
428        _ => null(out),
429    }
430}
431
432/// Structural equality, matching `.code` source's `=` operator.
433pub fn values_equal(a: &CodeValue, b: &CodeValue) -> bool {
434    unsafe { code_values_equal(a, b) != 0 }
435}
436
437// `bool_value` and `assert_value` used to live here, wrapping
438// `code_bool_value` and `code_assert`. Both are gone as of phase 3
439// (2026-08-28) along with their declarations in `code_abi.h`: they are the
440// compiler's own — one checks an `and`/`or` operand, the other is the
441// `assert` statement — and since phase 3 they report trouble through a flag
442// that only the host's generated code reads, so a module calling one would
443// have had its failure silently swallowed. A module that cannot do its work
444// returns [`exception`] instead; it may never end the application.
445
446/// Raise a fatal module error, taking the whole host process down.
447///
448/// **Deprecated as of 2026-08-28, and not for modules to call.** A module
449/// may never end the application — see
450/// `docs/todo/errors-as-particles.md`. Report a failure by returning an
451/// [`exception`] instead, which the program receives as an ordinary value
452/// and may examine or ignore.
453///
454/// Kept only because `runtime.c` itself still uses it internally for
455/// conditions with no frame to return to (out of memory). It will leave
456/// this crate's API entirely once the C runtime has an error channel.
457#[deprecated(
458    since = "1.1.0",
459    note = "a module may not end the application; return `exception(out, source, message)` instead"
460)]
461pub fn runtime_error(message: &str) -> ! {
462    let c = cstr(message);
463    unsafe { code_runtime_error(c.as_ptr()) }
464}
465
466// ===========================================================================
467// Slot buffers — for Array/Object construction, which `runtime.c` expects
468// as a `CODE_VALUE_SLOT_SIZE`-strided scratch buffer of already-built
469// elements (see `code_array`/`code_object`'s doc comments in `runtime.c`;
470// `tests/native_modules/test_math`'s `factors`/`meta` exported vars are
471// the C-side version of the same pattern).
472// ===========================================================================
473
474/// A scratch buffer of `count` [`CodeValue`] slots, zero-initialized (so
475/// each slot starts in the same safe state [`CodeValue::zeroed`] documents).
476/// Build each element in place with [`SlotBuffer::slot_mut`], then hand the
477/// buffer to [`array`] or [`object`] — matching `runtime.c`'s "elements are
478/// retained and copied out of this buffer, never adopted by reference"
479/// contract, after which every slot you wrote must still be [`release`]d
480/// (the copy took its own reference; yours is still live until you drop it).
481pub struct SlotBuffer {
482    buf: Vec<u8>,
483    len: i64,
484}
485
486impl SlotBuffer {
487    pub fn new(count: usize) -> Self {
488        Self {
489            buf: vec![0u8; count * CODE_VALUE_SLOT_SIZE],
490            len: count as i64,
491        }
492    }
493
494    /// Slot `index` — write a value into it with [`number`]/[`owned_str`]/etc.
495    pub fn slot_mut(&mut self, index: i64) -> &mut CodeValue {
496        debug_assert!(index >= 0 && index < self.len);
497        unsafe { &mut *slot_at(self.buf.as_mut_ptr() as *mut c_void, index) }
498    }
499
500    fn as_items_ptr(&mut self) -> *mut c_void {
501        self.buf.as_mut_ptr() as *mut c_void
502    }
503
504    /// Release every slot. Call after handing the buffer to [`array`] or
505    /// [`object`] — they copy elements out, they don't take ownership of
506    /// this buffer's own references.
507    pub fn release_all(&mut self) {
508        for i in 0..self.len {
509            unsafe { code_release(slot_at(self.buf.as_mut_ptr() as *mut c_void, i)) }
510        }
511    }
512}
513
514/// Write an Array into `out`, copying (and retaining) `elems`'s slots.
515/// `elems` still owns its own references afterwards — release it once
516/// you're done (see [`SlotBuffer::release_all`]).
517pub fn array(out: &mut CodeValue, elems: &mut SlotBuffer) {
518    unsafe { code_array(out, elems.as_items_ptr(), elems.len) }
519}
520
521/// Write an Object into `out` from parallel `keys` and `values` (a
522/// [`SlotBuffer`] built the same way [`array`] expects). `code_object` copies
523/// both the key *pointers* and the key *bytes* into the value's own storage
524/// (since 2026-08-29), so the `&'static CStr` bound is stricter than the ABI
525/// needs — it is the ergonomic path for the common case of literal field
526/// names (`c"status"`, `c"body"`). For names built at runtime — a parsed
527/// JSON object, a database row — use [`object_dyn`].
528pub fn object(out: &mut CodeValue, keys: &[&'static CStr], values: &mut SlotBuffer) {
529    debug_assert_eq!(keys.len() as i64, values.len);
530    let mut key_ptrs: Vec<*const c_char> = keys.iter().map(|k| k.as_ptr()).collect();
531    unsafe {
532        code_object(
533            out,
534            key_ptrs.as_mut_ptr(),
535            values.as_items_ptr(),
536            values.len,
537        )
538    }
539}
540
541/// [`object`], for keys that are not `'static` — built from a parsed
542/// document, a database row, an HTTP form. `code_object` copies each key's
543/// bytes into the new value's own block, so the `&str`s only have to live
544/// for the duration of this call.
545pub fn object_dyn(out: &mut CodeValue, keys: &[&str], values: &mut SlotBuffer) {
546    debug_assert_eq!(keys.len() as i64, values.len);
547    let c_keys: Vec<std::ffi::CString> = keys.iter().map(|k| cstr(k)).collect();
548    let mut key_ptrs: Vec<*const c_char> = c_keys.iter().map(|k| k.as_ptr()).collect();
549    unsafe {
550        code_object(
551            out,
552            key_ptrs.as_mut_ptr(),
553            values.as_items_ptr(),
554            values.len,
555        )
556    }
557    // `c_keys` drops here: safe, `code_object` copied the bytes it needed.
558}
559
560// ===========================================================================
561// Reading helpers — for use inside `code_module_dispatch`.
562// ===========================================================================
563
564/// Read a field by name off an Object value. `None` if `v` isn't an
565/// Object or the field doesn't exist — mirrors `code_field`'s own
566/// permissive-null behavior, but as an `Option` instead of writing Null.
567pub fn find_field<'a>(v: &'a CodeValue, name: &str) -> Option<&'a CodeValue> {
568    if v.tag != CodeTag::Object || v.keys().is_null() {
569        return None;
570    }
571    for i in 0..v.len() {
572        let key = unsafe { *v.keys().offset(i as isize) };
573        if key.is_null() {
574            continue;
575        }
576        let key_str = unsafe { CStr::from_ptr(key) };
577        if key_str.to_bytes() == name.as_bytes() {
578            return Some(unsafe { &*slot_at(v.items(), i) });
579        }
580    }
581    None
582}
583
584/// Read `v` as a `&str`, if it's a Str with a valid UTF-8 payload.
585pub fn read_str(v: &CodeValue) -> Option<&str> {
586    if v.tag != CodeTag::Str || v.str_ptr().is_null() {
587        return None;
588    }
589    unsafe { CStr::from_ptr(v.str_ptr()) }.to_str().ok()
590}
591
592/// Read `v` as an `f64`, if it's a Number.
593pub fn read_number(v: &CodeValue) -> Option<f64> {
594    (v.tag == CodeTag::Number).then_some(v.number())
595}
596
597/// Read `v` as a `bool`, if it's a Bool.
598pub fn read_bool(v: &CodeValue) -> Option<bool> {
599    (v.tag == CodeTag::Bool).then_some(v.boolean() != 0)
600}
601
602/// Convenience: [`find_field`] + [`read_str`].
603pub fn read_field_str<'a>(v: &'a CodeValue, name: &str) -> Option<&'a str> {
604    read_str(find_field(v, name)?)
605}
606
607/// Convenience: [`find_field`] + [`read_number`].
608pub fn read_field_number(v: &CodeValue, name: &str) -> Option<f64> {
609    read_number(find_field(v, name)?)
610}
611
612/// Convenience: [`find_field`] + [`read_bool`].
613pub fn read_field_bool(v: &CodeValue, name: &str) -> Option<bool> {
614    read_bool(find_field(v, name)?)
615}
616
617/// Iterate an Array's elements.
618pub fn array_elems(v: &CodeValue) -> impl Iterator<Item = &CodeValue> {
619    let (items, len) = if v.tag == CodeTag::Array {
620        (v.items(), v.len())
621    } else {
622        (std::ptr::null_mut(), 0)
623    };
624    (0..len).map(move |i| unsafe { &*slot_at(items, i) })
625}
626
627/// Iterate an Object's fields as `(key, value)` pairs, in stored order —
628/// the read counterpart to [`object`]/[`object_dyn`], for a handler that has
629/// to walk every field rather than name one ([`find_field`]). A field whose
630/// key is not valid UTF-8 is skipped. Not an Object: an empty iterator.
631pub fn object_entries(v: &CodeValue) -> impl Iterator<Item = (&str, &CodeValue)> {
632    let (keys, items, len) = if v.tag == CodeTag::Object && !v.keys().is_null() {
633        (v.keys(), v.items(), v.len())
634    } else {
635        (std::ptr::null_mut(), std::ptr::null_mut(), 0)
636    };
637    (0..len).filter_map(move |i| {
638        let key = unsafe { *keys.offset(i as isize) };
639        if key.is_null() {
640            return None;
641        }
642        let key = unsafe { CStr::from_ptr(key) }.to_str().ok()?;
643        Some((key, unsafe { &*slot_at(items, i) }))
644    })
645}
646
647/// Build a `{ _class = <class_name>, value = <fill's result> }` particle
648/// into `out` — the shape `emit ... to <alias> get x` expects a handler's
649/// result to have. Mirrors `runtime.c`'s own `code_make_result`, which a
650/// C module reaches via `#include "runtime.c"` but isn't exported for a
651/// separately-linked module to call directly, so this is a small
652/// reimplementation rather than an FFI binding.
653pub fn make_result(
654    out: &mut CodeValue,
655    class_name: &'static CStr,
656    fill: impl FnOnce(&mut CodeValue),
657) {
658    let mut value = CodeValue::zeroed();
659    fill(&mut value);
660    let mut buf = SlotBuffer::new(2);
661    borrowed_str(buf.slot_mut(0), class_name);
662    unsafe { code_copy(buf.slot_mut(1), &value) };
663    object(out, &[c"_class", c"value"], &mut buf);
664    buf.release_all();
665    release(&mut value);
666}
667
668// ===========================================================================
669// Inbound emissions — speaking first, rather than only answering.
670// ===========================================================================
671
672/// The host's pusher, handed over by `code_module_set_inbound`. `queue` is
673/// opaque — a module only ever passes it straight back. Mirrors
674/// `code_abi.h`'s `CodeEmitFn`.
675pub type CodeEmitFn = unsafe extern "C" fn(queue: *mut c_void, value: *const CodeValue);
676
677/// Where [`declare_inbound!`] parks what the host handed over. Two atomics
678/// rather than a `static mut`: the host sets these once at link time, and a
679/// module with a thread of its own would read them from that thread, so the
680/// access wants to be well-defined even though nothing does that yet.
681pub static INBOUND_QUEUE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
682/// The `CodeEmitFn` as a raw address — `AtomicPtr` cannot hold a `fn`
683/// pointer directly, and this is only ever written by [`store_inbound`] and
684/// read back by [`emit_inbound`].
685pub static INBOUND_EMIT: AtomicUsize = AtomicUsize::new(0);
686
687/// Record what the host handed over. Called by the export
688/// [`declare_inbound!`] generates; not useful on its own.
689pub fn store_inbound(queue: *mut c_void, emit: CodeEmitFn) {
690    INBOUND_QUEUE.store(queue, Ordering::Release);
691    INBOUND_EMIT.store(emit as usize, Ordering::Release);
692}
693
694/// Generate the optional `code_module_set_inbound` export.
695///
696/// A macro rather than a plain function in this crate, and that is
697/// load-bearing: `#[no_mangle]` symbols defined in a dependency are not
698/// reliably kept in the final `cdylib`, so the export has to be emitted in
699/// *your* crate. One invocation at the top level is all it takes:
700///
701/// ```rust,ignore
702/// code_native::declare_inbound!();
703/// ```
704///
705/// A module that never speaks first simply doesn't invoke it — the export is
706/// optional, and the host checks for it rather than requiring it.
707#[macro_export]
708macro_rules! declare_inbound {
709    // A `.a` static module spells its own export name, because every `.a`
710    // linked into one program shares a flat symbol table and the host finds
711    // these by prefix (`nm`, see loader.rs's `static_module_symbols`). It is
712    // spelled out rather than pasted together from a prefix because
713    // `macro_rules!` cannot concatenate identifiers — and spelling it matches
714    // how a static module already writes its other three exports.
715    ($name:ident) => {
716        /// Handed the host's queue and pusher once, at link time.
717        ///
718        /// # Safety
719        ///
720        /// Called by the host with its own queue pointer and pusher; both
721        /// stay valid for as long as the module is loaded.
722        #[no_mangle]
723        pub unsafe extern "C" fn $name(queue: *mut ::std::ffi::c_void, emit: $crate::CodeEmitFn) {
724            $crate::store_inbound(queue, emit);
725        }
726    };
727    () => {
728        /// Handed the host's queue and pusher once, at link time.
729        ///
730        /// # Safety
731        ///
732        /// Called by the host with its own queue pointer and pusher; both
733        /// stay valid for as long as the module is loaded.
734        #[no_mangle]
735        pub unsafe extern "C" fn code_module_set_inbound(
736            queue: *mut ::std::ffi::c_void,
737            emit: $crate::CodeEmitFn,
738        ) {
739            $crate::store_inbound(queue, emit);
740        }
741    };
742}
743
744/// Generate the optional `code_module_inbound_reply` export, which is how a
745/// module hears what the program answered to something it pushed.
746///
747/// Takes the function to hand it to — `fn(particle: &CodeValue, result:
748/// &CodeValue)`. `result` is a `CODE_NULL` value when no handler matched;
749/// both references are the host's and are only valid for the duration of the
750/// call, so read what you need and copy it out.
751///
752/// ```rust,ignore
753/// fn answered(particle: &CodeValue, result: &CodeValue) { /* ... */ }
754/// code_native::declare_inbound_reply!(answered);
755/// ```
756///
757/// A macro rather than a plain function for the same reason as
758/// [`declare_inbound!`]: a `#[no_mangle]` symbol defined in a dependency is
759/// not reliably kept in the final `cdylib`.
760#[macro_export]
761macro_rules! declare_inbound_reply {
762    // A `.a` static module spells its own export name — see
763    // [`declare_inbound!`] for why.
764    ($name:ident, $handler:path) => {
765        /// Called by the host after a particle this module pushed was
766        /// dispatched.
767        ///
768        /// # Safety
769        ///
770        /// Both pointers are the host's and valid for this call only.
771        #[no_mangle]
772        pub unsafe extern "C" fn $name(
773            particle: *const $crate::CodeValue,
774            result: *const $crate::CodeValue,
775        ) {
776            if particle.is_null() || result.is_null() {
777                return;
778            }
779            $handler(&*particle, &*result);
780        }
781    };
782    ($handler:path) => {
783        /// Called by the host after a particle this module pushed was
784        /// dispatched.
785        ///
786        /// # Safety
787        ///
788        /// Both pointers are the host's and valid for this call only.
789        #[no_mangle]
790        pub unsafe extern "C" fn code_module_inbound_reply(
791            particle: *const $crate::CodeValue,
792            result: *const $crate::CodeValue,
793        ) {
794            if particle.is_null() || result.is_null() {
795                return;
796            }
797            $handler(&*particle, &*result);
798        }
799    };
800}
801
802/// Push a particle into the program, to be dispatched to *its* handlers the
803/// next time the host drains (between top-level statements).
804///
805/// Returns `false` when the host never called `code_module_set_inbound` —
806/// which happens whenever the module was loaded by something that does not
807/// support inbound emissions. Pushing is therefore always best-effort from
808/// the module's side, and a module must stay correct when nobody is
809/// listening.
810///
811/// The particle is deep-copied into the host's heap by the host's own
812/// pusher, so `value` may be released as soon as this returns.
813///
814/// **A pushed class the program has no handler for is a runtime error**, not
815/// a silent drop (`tests/fail_inbound_unhandled.code` pins that). Push only
816/// what the program has agreed to receive.
817pub fn emit_inbound(value: &CodeValue) -> bool {
818    inbound_here().emit(value)
819}
820
821/// Where a push goes: a host's queue and the pusher it handed over with it.
822///
823/// What a module that works on a thread of its own captures *before* it
824/// starts the thread (`let inbound = inbound_here();`) and pushes through
825/// afterwards — so a module with an instance per link (`code_abi.h` item
826/// 13) answers into the queue of the link that asked, not into whichever
827/// link was configured last (ticket 178).
828#[derive(Clone, Copy)]
829pub struct Inbound {
830    queue: *mut c_void,
831    emit: usize,
832}
833
834// SAFETY: the host's queue and pusher are made to be used from a module's
835// own threads — that is what they are for (`code_abi.h`, "Inbound").
836unsafe impl Send for Inbound {}
837
838impl Inbound {
839    /// What the host handed over, kept by an instance of its own.
840    pub fn new(queue: *mut c_void, emit: CodeEmitFn) -> Inbound {
841        Inbound {
842            queue,
843            emit: emit as usize,
844        }
845    }
846
847    /// Nowhere: a push through this answers `false`.
848    pub const NONE: Inbound = Inbound {
849        queue: std::ptr::null_mut(),
850        emit: 0,
851    };
852
853    /// Push `value` there, as [`emit_inbound`] says. `false` when no queue
854    /// was ever handed over.
855    pub fn emit(&self, value: &CodeValue) -> bool {
856        if self.emit == 0 {
857            return false;
858        }
859        // SAFETY: non-zero only from a real `CodeEmitFn` (`new`, or
860        // `store_inbound`), paired with its own queue.
861        let emit: CodeEmitFn = unsafe { std::mem::transmute::<usize, CodeEmitFn>(self.emit) };
862        unsafe { emit(self.queue, value) };
863        true
864    }
865}
866
867thread_local! {
868    /// The queue of the instance whose dispatch is running on this thread,
869    /// set by [`with_inbound`] for the length of the call.
870    static CURRENT_INBOUND: std::cell::Cell<Option<Inbound>> = const { std::cell::Cell::new(None) };
871}
872
873/// Where a push made here goes: the queue of the instance being dispatched
874/// on this thread (see [`with_inbound`]), else the module's one queue
875/// (`code_module_set_inbound`).
876pub fn inbound_here() -> Inbound {
877    if let Some(inbound) = CURRENT_INBOUND.with(std::cell::Cell::get) {
878        return inbound;
879    }
880    Inbound {
881        queue: INBOUND_QUEUE.load(Ordering::Acquire),
882        emit: INBOUND_EMIT.load(Ordering::Acquire),
883    }
884}
885
886/// Makes `inbound` where pushes made on this thread go until it is dropped
887/// — the first line of a thread a module starts with what it captured from
888/// [`inbound_here`]:
889///
890/// ```rust,ignore
891/// let inbound = code_native::inbound_here();
892/// std::thread::spawn(move || {
893///     let _inbound = code_native::InboundScope::enter(inbound);
894///     // … emit_inbound(…) lands in the queue of the link that asked
895/// });
896/// ```
897pub struct InboundScope {
898    previous: Option<Inbound>,
899}
900
901impl InboundScope {
902    pub fn enter(inbound: Inbound) -> InboundScope {
903        InboundScope {
904            previous: CURRENT_INBOUND.with(|c| c.replace(Some(inbound))),
905        }
906    }
907}
908
909impl Drop for InboundScope {
910    fn drop(&mut self) {
911        let previous = self.previous;
912        CURRENT_INBOUND.with(|c| c.set(previous));
913    }
914}
915
916/// Run `f` with `inbound` as where pushes made on this thread go — what a
917/// module's `code_module_instance_dispatch` wraps its dispatch in, so the
918/// threads that call starts capture its link's queue.
919pub fn with_inbound<R>(inbound: Inbound, f: impl FnOnce() -> R) -> R {
920    let previous = CURRENT_INBOUND.with(|c| c.replace(Some(inbound)));
921    let result = f();
922    CURRENT_INBOUND.with(|c| c.set(previous));
923    result
924}
925
926// ===========================================================================
927// Failing without ending the program.
928// ===========================================================================
929
930/// Build `Exception { source, message, innerException }` into `out` — how a
931/// module reports that it could not do the work.
932///
933/// This is the *only* way a module should fail. A module may never end the
934/// application (`docs/todo/errors-as-particles.md`): the program receives
935/// this as an ordinary value through `get`, and may test it with
936/// `is Exception`, read `message`, or ignore it entirely.
937///
938/// `source` names the module, which a returned value cannot otherwise be
939/// asked — the caller knows what it emitted to, but an `Exception` stored,
940/// passed on, or wrapped as another's `innerException` has lost that.
941///
942/// `innerException` is null here; use [`exception_wrapping`] to carry the
943/// failure underneath this one.
944pub fn exception(out: &mut CodeValue, source: &str, message: &str) {
945    let mut inner = CodeValue::zeroed();
946    null(&mut inner);
947    exception_wrapping(out, source, message, &inner);
948    release(&mut inner);
949}
950
951/// [`exception`], carrying the failure that caused it as `innerException`.
952pub fn exception_wrapping(out: &mut CodeValue, source: &str, message: &str, inner: &CodeValue) {
953    let mut buf = SlotBuffer::new(4);
954    borrowed_str(buf.slot_mut(0), c"Exception");
955    owned_str(buf.slot_mut(1), source);
956    owned_str(buf.slot_mut(2), message);
957    copy(buf.slot_mut(3), inner);
958    object(
959        out,
960        &[c"_class", c"source", c"message", c"innerException"],
961        &mut buf,
962    );
963    buf.release_all();
964}
965
966/// Run a module's dispatch body so that a panic inside it becomes an
967/// [`exception`] rather than killing the host.
968///
969/// **Wrap every `code_module_dispatch` in this.** The guarantee it provides
970/// cannot be provided by the host: a panic escaping an `extern "C"` function
971/// aborts the process rather than unwinding, so the host's own
972/// `catch_unwind` never runs — the catch has to happen on this side of the
973/// FFI boundary, which is here.
974///
975/// What it covers is most of what "a badly written module" means in
976/// practice: `unwrap`/`expect` on `None` or `Err`, slice and index bounds,
977/// arithmetic overflow, explicit `panic!`/`assert!`, and panics raised
978/// inside dependencies. What it cannot cover is a deliberate `exit`, an
979/// infinite loop, or undefined behaviour reached through `unsafe`.
980///
981/// ```rust,ignore
982/// #[no_mangle]
983/// pub unsafe extern "C" fn code_module_dispatch(
984///     out: *mut CodeValue,
985///     particle: *const CodeValue,
986/// ) {
987///     guarded(&mut *out, "mymodule", |out| match read_field_str(&*particle, "_class") {
988///         Some("Double") => { /* ... */ }
989///         _ => null(out),
990///     })
991/// }
992/// ```
993pub fn guarded(out: &mut CodeValue, source: &str, body: impl FnOnce(&mut CodeValue)) {
994    let slot: *mut CodeValue = out;
995    // `AssertUnwindSafe` over the whole closure: `out` is a slot the host
996    // owns, there is no invariant of ours for a panic to leave half-broken,
997    // and whatever the body managed to write is released by the constructor
998    // `exception` runs next.
999    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1000        // SAFETY: `slot` came from the `&mut` above and outlives this call.
1001        body(unsafe { &mut *slot })
1002    }));
1003    let Err(payload) = result else {
1004        return;
1005    };
1006    // Rust's panic payload is a string for `panic!("...")` and `unwrap`
1007    // alike; anything else is reported without a message rather than
1008    // guessed at.
1009    let detail = payload
1010        .downcast_ref::<&str>()
1011        .map(|s| (*s).to_string())
1012        .or_else(|| payload.downcast_ref::<String>().cloned())
1013        .unwrap_or_else(|| "panicked".to_string());
1014    // SAFETY: as above — `catch_unwind` returning `Err` means the body
1015    // stopped early, not that the slot went away.
1016    exception(
1017        unsafe { &mut *slot },
1018        source,
1019        &format!("module panicked: {detail}"),
1020    );
1021}
1022
1023#[cfg(test)]
1024mod tests {
1025    use super::*;
1026
1027    /// `{ a = 1, s = "hi" }`.
1028    fn sample_object(out: &mut CodeValue) {
1029        let mut values = SlotBuffer::new(2);
1030        number(values.slot_mut(0), 1.0);
1031        owned_str(values.slot_mut(1), "hi");
1032        object(out, &[c"a", c"s"], &mut values);
1033        values.release_all();
1034    }
1035
1036    /// `[10, 20, 30]`.
1037    fn sample_array(out: &mut CodeValue) {
1038        let mut items = SlotBuffer::new(3);
1039        for (i, v) in [10.0, 20.0, 30.0].into_iter().enumerate() {
1040            number(items.slot_mut(i as i64), v);
1041        }
1042        array(out, &mut items);
1043        items.release_all();
1044    }
1045
1046    fn tag_of(f: impl FnOnce(&mut CodeValue)) -> CodeTag {
1047        let mut out = CodeValue::zeroed();
1048        f(&mut out);
1049        let tag = out.tag;
1050        release(&mut out);
1051        tag
1052    }
1053
1054    #[test]
1055    fn object_dyn_builds_with_runtime_keys() {
1056        // Keys owned by this scope, not `'static` — the case `object` can't
1057        // take. `code_object` copies the bytes, so the built value outlives
1058        // them.
1059        let mut values = SlotBuffer::new(2);
1060        number(values.slot_mut(0), 1.0);
1061        number(values.slot_mut(1), 2.0);
1062        let mut obj = CodeValue::zeroed();
1063        {
1064            let names: Vec<String> = vec!["one".into(), "two".into()];
1065            let key_refs: Vec<&str> = names.iter().map(String::as_str).collect();
1066            object_dyn(&mut obj, &key_refs, &mut values);
1067            // `names`/`key_refs` drop at the end of this block — the built
1068            // `obj` must not depend on them any more.
1069        }
1070        values.release_all();
1071
1072        assert_eq!(read_field_number(&obj, "one"), Some(1.0));
1073        assert_eq!(read_field_number(&obj, "two"), Some(2.0));
1074        release(&mut obj);
1075    }
1076
1077    #[test]
1078    fn object_entries_walks_every_field_in_order() {
1079        let mut obj = CodeValue::zeroed();
1080        sample_object(&mut obj); // { a = 1, s = "hi" }
1081        let seen: Vec<(String, CodeTag)> = object_entries(&obj)
1082            .map(|(k, v)| (k.to_owned(), v.tag))
1083            .collect();
1084        assert_eq!(
1085            seen,
1086            vec![
1087                ("a".to_owned(), CodeTag::Number),
1088                ("s".to_owned(), CodeTag::Str),
1089            ]
1090        );
1091        release(&mut obj);
1092
1093        // Not an Object: empty, not a panic.
1094        let mut n = CodeValue::zeroed();
1095        number(&mut n, 5.0);
1096        assert_eq!(object_entries(&n).count(), 0);
1097        release(&mut n);
1098    }
1099
1100    #[test]
1101    fn field_reads_a_present_member() {
1102        let mut obj = CodeValue::zeroed();
1103        sample_object(&mut obj);
1104        let mut out = CodeValue::zeroed();
1105        field(&mut out, &obj, "s");
1106        assert_eq!(read_str(&out), Some("hi"));
1107        release(&mut out);
1108        release(&mut obj);
1109    }
1110
1111    /// The half `field` shares with the language: an absent member is null,
1112    /// not a failure. The lookup was fine, it just found nothing.
1113    #[test]
1114    fn field_answers_null_for_an_absent_member() {
1115        let mut obj = CodeValue::zeroed();
1116        sample_object(&mut obj);
1117        assert_eq!(tag_of(|out| field(out, &obj, "nope")), CodeTag::Null);
1118        release(&mut obj);
1119    }
1120
1121    /// The half it does *not* share, and the reason this is Rust rather than
1122    /// a call into the ABI. `.code` source treats `"abc".length` as an error;
1123    /// a module cannot use a fallible accessor, because a failure raised
1124    /// inside a `.so` sets that copy's flag and nobody reads it. Total wins
1125    /// here, and the module says so itself with `exception` if it minds.
1126    #[test]
1127    fn field_answers_null_on_a_non_object() {
1128        let mut n = CodeValue::zeroed();
1129        number(&mut n, 42.0);
1130        assert_eq!(tag_of(|out| field(out, &n, "anything")), CodeTag::Null);
1131        release(&mut n);
1132    }
1133
1134    #[test]
1135    fn index_reads_an_array_element() {
1136        let mut arr = CodeValue::zeroed();
1137        sample_array(&mut arr);
1138        let mut i = CodeValue::zeroed();
1139        number(&mut i, 1.0);
1140        let mut out = CodeValue::zeroed();
1141        index(&mut out, &arr, &i);
1142        assert_eq!(read_number(&out), Some(20.0));
1143        release(&mut out);
1144        release(&mut arr);
1145    }
1146
1147    /// Every way of missing an array element is null, matching the
1148    /// language: out of range, negative, and a whole-number check that
1149    /// rejects `1.5` rather than truncating it.
1150    #[test]
1151    fn index_answers_null_for_every_kind_of_miss() {
1152        let mut arr = CodeValue::zeroed();
1153        sample_array(&mut arr);
1154        for probe in [99.0, -1.0, 1.5] {
1155            let mut i = CodeValue::zeroed();
1156            number(&mut i, probe);
1157            assert_eq!(
1158                tag_of(|out| index(out, &arr, &i)),
1159                CodeTag::Null,
1160                "index {probe} should be null"
1161            );
1162        }
1163        // A non-Number index into an Array is null too, not an error.
1164        let mut key = CodeValue::zeroed();
1165        owned_str(&mut key, "0");
1166        assert_eq!(tag_of(|out| index(out, &arr, &key)), CodeTag::Null);
1167        release(&mut key);
1168        release(&mut arr);
1169    }
1170
1171    /// An Array is keyed by Number and an Object by Str — the same split
1172    /// `loop` uses — so a computed key on an Object is `find_field` under
1173    /// another name.
1174    #[test]
1175    fn index_reads_an_object_by_string_key() {
1176        let mut obj = CodeValue::zeroed();
1177        sample_object(&mut obj);
1178        let mut key = CodeValue::zeroed();
1179        owned_str(&mut key, "a");
1180        let mut out = CodeValue::zeroed();
1181        index(&mut out, &obj, &key);
1182        assert_eq!(read_number(&out), Some(1.0));
1183        release(&mut out);
1184        release(&mut key);
1185        release(&mut obj);
1186    }
1187
1188    #[test]
1189    fn index_answers_null_on_a_non_container() {
1190        let mut n = CodeValue::zeroed();
1191        number(&mut n, 42.0);
1192        let mut i = CodeValue::zeroed();
1193        number(&mut i, 0.0);
1194        assert_eq!(tag_of(|out| index(out, &n, &i)), CodeTag::Null);
1195        release(&mut n);
1196    }
1197}