code-native 1.1.2

Write native .so modules for the Code programming language in Rust — safe CodeValue builders/readers over the real runtime.c, no reimplementation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
//! Safe(r) Rust bindings for writing a native module — `.so` or `.a` — for
//! the [Code programming language](https://github.com/codelovesme/code).
//!
//! `code_abi.h`'s contract needs two things from a module: agreement on the
//! `CodeValue` wire layout, and a `code_release` (plus friends) built from
//! the *real* `runtime.c` rather than a reimplementation that merely looks
//! compatible — getting refcounting subtly wrong is the kind of bug that
//! corrupts memory rather than crashing where you'd notice. This crate's
//! `build.rs` compiles the vendored `runtime.c` and links it into your
//! `cdylib` directly, so every function below calls the same code the host
//! runtime and every C module trust. (A `.a` module wants the opposite —
//! the host already has the one runtime there — which is what the
//! `static-module` feature turns off; see this crate's README.)
//!
//! # Quick start
//!
//! ```rust,ignore
//! use code_native::*;
//!
//! #[no_mangle]
//! pub extern "C" fn code_module_abi_version() -> u32 {
//!     CODE_ABI_VERSION
//! }
//!
//! #[no_mangle]
//! pub unsafe extern "C" fn code_module_dispatch(out: *mut CodeValue, particle: *const CodeValue) {
//!     let particle = &*particle;
//!     match read_field_str(particle, "_class") {
//!         Some("Double") => {
//!             let value = read_field_number(particle, "value").unwrap_or(0.0);
//!             make_result(&mut *out, "DoubleResult", |slot| code_number(slot, value * 2.0));
//!         }
//!         // A class this module does not handle answers null — see
//!         // docs/todo/errors-as-particles.md.
//!         _ => null(&mut *out),
//!     }
//! }
//! ```
//!
//! Build with `crate-type = ["cdylib"]`, then `link "libmymodule.so" as m`
//! from `.code` source. See this crate's README for the full walkthrough,
//! including `.a` static modules and `code_module_vars`.
//!
//! To *speak first* rather than only answer — pushing particles into the
//! program, which is what `Log`/`Exception`/`Tick`-shaped traffic needs —
//! add [`declare_inbound!`] and call [`emit_inbound`]:
//!
//! ```rust,ignore
//! code_native::declare_inbound!();
//!
//! fn report(message: &str) {
//!     let mut p = CodeValue::zeroed();
//!     // ... build a particle ...
//!     emit_inbound(&p);
//!     release(&mut p);
//! }
//! ```
//!
//! A pushed class the program has no handler for is dropped, so a module may
//! report without every program that links it having to listen.
//!
//! `code_module_dispatch` and `code_module_abi_version` are the two required
//! exports — there is no macro generating them here (unlike the *old*
//! language's `code-native`): the new ABI dropped the descriptor-table
//! design for one function a module dispatches through itself, so there is
//! no boilerplate left to generate. `code_release` needs no Rust code at
//! all — it comes from the linked `runtime.c` object automatically.

use std::ffi::{c_char, c_int, c_void, CStr};
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};

// ===========================================================================
// Wire layout — bit-for-bit `code_abi.h`. Only the pointer/int/float shapes
// matter for ABI compatibility (not what they're named), but names are kept
// identical to the header so the two are trivially diffable.
// ===========================================================================

/// Current ABI version. A module's `code_module_abi_version` must return
/// this.
pub const CODE_ABI_VERSION: u32 = 1;

/// Byte stride of an array/object element buffer — **not** `size_of::<CodeValue>()`.
/// This is a frozen ABI constant with headroom for `CodeValue` to grow
/// without breaking already-compiled modules; always address a buffer
/// through [`slot_at`], never by casting to `*mut CodeValue` and indexing.
pub const CODE_VALUE_SLOT_SIZE: usize = 80;

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeTag {
    Number,
    Str,
    Bool,
    Null,
    Array,
    Object,
}

#[repr(C)]
pub struct CodeValue {
    pub tag: CodeTag,
    pub heap: c_int,
    pub number: f64,
    pub str: *const c_char,
    pub boolean: c_int,
    /// `CODE_ARRAY`: element buffer; `CODE_OBJECT`: value buffer — both
    /// strided at [`CODE_VALUE_SLOT_SIZE`], addressed via [`slot_at`].
    pub items: *mut c_void,
    /// `CODE_OBJECT` only, parallel to `items`.
    pub keys: *mut *const c_char,
    pub len: i64,
}

impl CodeValue {
    /// An all-zero value — tag `Number`, `0.0`, not heap-owned. Bit-for-bit
    /// what `CodeValue x = {0};` produces in C, and the required starting
    /// state before passing `&mut` to any constructor below (each one calls
    /// `code_release` on `out` first, exactly like the C ABI expects).
    pub fn zeroed() -> Self {
        // SAFETY: an all-zero-bytes CodeValue is a valid Number(0.0), which
        // `code_release` (called by every constructor before overwriting
        // `out`) already treats as a safe no-op — the same invariant `{0}`
        // relies on in every C module.
        unsafe { std::mem::zeroed() }
    }
}

impl Default for CodeValue {
    fn default() -> Self {
        Self::zeroed()
    }
}

#[repr(C)]
pub struct CodeVarList {
    pub count: i64,
    pub names: *const *const c_char,
    /// `CODE_VALUE_SLOT_SIZE` stride, `count` slots — see [`slot_at`].
    pub values: *mut CodeValue,
}

// Both types carry raw pointers, so Rust doesn't derive Send/Sync for them
// automatically — but `code_module_vars` (see README) is exactly the case
// that needs a `static`/`OnceLock<CodeVarList>`, and the host only ever
// reads this data (once, at `link` time), never mutates it concurrently.
// Matches the old language's own `code-abi` crate, which needed the same
// impls for the same reason.
unsafe impl Send for CodeValue {}
unsafe impl Sync for CodeValue {}
unsafe impl Send for CodeVarList {}
unsafe impl Sync for CodeVarList {}

// ===========================================================================
// Raw bindings to `runtime.c`'s exported (non-`static`) functions — the same
// symbols `code_abi.h` declares for a C module. Calling into the actual
// compiled `runtime.c`, not a port of it, is what keeps this crate free of
// the layout-drift risk the *old* language's `code-native`/`code-abi` pair
// needed a dedicated test to guard against.
// ===========================================================================

extern "C" {
    fn code_number(out: *mut CodeValue, n: f64);
    fn code_str(out: *mut CodeValue, s: *const c_char);
    fn code_bool(out: *mut CodeValue, b: c_int);
    fn code_null(out: *mut CodeValue);
    fn code_array(out: *mut CodeValue, items: *mut c_void, len: i64);
    fn code_object(out: *mut CodeValue, keys: *mut *const c_char, values: *mut c_void, len: i64);
    fn code_copy(out: *mut CodeValue, src: *const CodeValue);
    fn code_retain(v: *const CodeValue);
    fn code_values_equal(a: *const CodeValue, b: *const CodeValue) -> c_int;
    fn code_runtime_error(message: *const c_char) -> !;

    // `build.rs` compiles `runtime.c` with `code_release` renamed to this at
    // the preprocessor level (`-D`), and this crate re-exports it below under
    // the real name from a function rustc actually treats as part of the
    // crate (not an archive) — see that `#[no_mangle]` fn's own doc comment
    // for why the rename is needed at all.
    #[cfg(feature = "shared-module")]
    fn code_native_vendored_release(v: *mut CodeValue);
}

// A `.a` module links against the *host's* runtime, which already defines
// `code_release` under its real name — so there is nothing to rename and
// nothing to re-export, and `release` below calls it directly.
#[cfg(not(feature = "shared-module"))]
extern "C" {
    fn code_release(v: *mut CodeValue);
}

/// The ABI's required `code_release` export. Defined here, as a real Rust
/// function, rather than left as whatever `runtime.c`'s own `code_release`
/// would otherwise be: `cdylib` targets get `--exclude-libs=ALL` from
/// rustc by default, which hides every symbol pulled in from a *linked
/// static archive* (exactly what `build.rs`'s `cc::Build::compile` produces
/// from `runtime.c`) out of the shared library's dynamic symbol table —
/// even though this crate's own code calls it just fine internally. A
/// symbol the crate defines directly (this function) isn't subject to that
/// exclusion, so renaming the archive's copy and re-exporting it from here
/// is what makes the host's `dlsym("code_release")` actually find it.
///
/// # Safety
/// `v` must point to a valid, initialized `CodeValue` — the same
/// requirement `runtime.c`'s own `code_release` has. The host only ever
/// calls this on values it deep-copied out of your `code_module_dispatch`
/// result, so you should never need to call it yourself except via
/// [`release`].
#[cfg(feature = "shared-module")]
#[no_mangle]
pub unsafe extern "C" fn code_release(v: *mut CodeValue) {
    code_native_vendored_release(v)
}

/// Addresses slot `index` of a [`CODE_VALUE_SLOT_SIZE`]-strided buffer —
/// the Rust equivalent of `code_abi.h`'s `code_slot_at`. Pure pointer
/// arithmetic, safe to reimplement independently (no allocator/refcount
/// logic to drift from `runtime.c`).
pub fn slot_at(base: *mut c_void, index: i64) -> *mut CodeValue {
    (base as *mut u8).wrapping_offset(index as isize * CODE_VALUE_SLOT_SIZE as isize)
        as *mut CodeValue
}

fn cstr(s: &str) -> std::ffi::CString {
    std::ffi::CString::new(s).unwrap_or_else(|_| std::ffi::CString::new("<invalid-utf8>").unwrap())
}

// ===========================================================================
// Safe scalar constructors — thin wrappers: `code_release`s `out` first
// (matching every `runtime.c` constructor's own contract), then delegates.
// ===========================================================================

/// Write a Number into `out`.
pub fn number(out: &mut CodeValue, n: f64) {
    unsafe { code_number(out, n) }
}

/// Write a Str into `out`, borrowing `s` for `'static` (a string literal or
/// otherwise permanently-alive buffer) rather than copying it — matching
/// `code_str`'s own borrowing contract. Use [`owned_str`] for a value built
/// at runtime that needs its own heap block.
pub fn borrowed_str(out: &mut CodeValue, s: &'static CStr) {
    unsafe { code_str(out, s.as_ptr()) }
}

/// Write a Str into `out` from a freshly-built Rust string. Leaks the
/// `CString` — acceptable here because the value crosses into the host's
/// own heap the moment your `code_module_dispatch` returns (the host
/// deep-copies your result and then calls your module's `code_release` on
/// it, which only ever frees what `runtime.c`'s own allocator built, never
/// this leaked buffer).
pub fn owned_str(out: &mut CodeValue, s: &str) {
    let c = cstr(s);
    unsafe { code_str(out, c.as_ptr()) }
    std::mem::forget(c);
}

/// Write a Bool into `out`.
pub fn boolean(out: &mut CodeValue, b: bool) {
    unsafe { code_bool(out, b as c_int) }
}

/// Write Null into `out`.
pub fn null(out: &mut CodeValue) {
    unsafe { code_null(out) }
}

/// Release whatever `v` holds — call on every temporary [`CodeValue`] you
/// built and no longer need (matching `runtime.c`'s own refcounting rule:
/// every slot that ever named a heap block owns exactly one reference to
/// it).
pub fn release(v: &mut CodeValue) {
    unsafe { code_release(v) }
}

/// Deep-copy `src` into `out` — `out` ends up owning its own references to
/// everything `src` points at, and `src` is left untouched. This is how a
/// handler passes a value it did not build itself along (e.g. an `Echo`
/// returning its operand): the copy takes new references, so neither side's
/// lifetime constrains the other.
pub fn copy(out: &mut CodeValue, src: &CodeValue) {
    unsafe { code_copy(out, src) }
}

/// Increment `v`'s refcount — needed only if you're holding onto a
/// [`CodeValue`] you didn't just build yourself (e.g. a borrowed field from
/// [`find_field`]) somewhere that will outlive the call it came from.
/// Every retained value must be balanced by a [`release`].
pub fn retain(v: &CodeValue) {
    unsafe { code_retain(v) }
}

/// `obj.field` field access. **Total**: a missing field, or an `obj` that is
/// not an Object at all, writes Null into `out`.
///
/// Total is where this differs from the language, and deliberately. `.code`
/// source treats a non-Object operand as an *error* (`"abc".length` fails,
/// which the README states as a rule), and `runtime.c` still has a
/// `code_field` that does exactly that — for the compiler. It is not in
/// `code_abi.h`, and this no longer calls it.
///
/// The reason is that a module cannot use a fallible accessor safely. Since
/// phase 3 (2026-08-28) a runtime failure travels by a flag that only the
/// *host's* generated code reads, and a `.so` carries its own copy of the
/// runtime — so a failure raised inside a module sets the module's flag and
/// is never seen. One function cannot be both fallible for the language and
/// total for modules, so the ABI keeps the total one. A module that wants to
/// refuse a wrong-typed operand says so itself, with [`exception`].
pub fn field(out: &mut CodeValue, obj: &CodeValue, name: &str) {
    match find_field(obj, name) {
        Some(value) => copy(out, value),
        None => null(out),
    }
}

/// `arr[index]` element access, with the same totality as [`field`]: an
/// out-of-bounds index, a non-Number index into an Array, a non-String key
/// into an Object, or an `arr` that is neither, all write Null.
///
/// Matches `.code`'s own rules for everything except that last case, for the
/// reason [`field`] gives.
pub fn index(out: &mut CodeValue, arr: &CodeValue, i: &CodeValue) {
    match arr.tag {
        CodeTag::Array => {
            // The language indexes arrays by Number, and only by a Number
            // that is a whole one in range — `xs[1.5]` and `xs[99]` are both
            // null, not errors.
            let n = if i.tag == CodeTag::Number {
                i.number
            } else {
                f64::NAN
            };
            let whole = n as i64;
            if whole as f64 == n && whole >= 0 && whole < arr.len {
                copy(out, unsafe { &*slot_at(arr.items, whole) });
            } else {
                null(out);
            }
        }
        // An Object is keyed by Str — the same split `loop` uses — so a
        // computed key is just `find_field` under another name.
        CodeTag::Object => match read_str(i).and_then(|key| find_field(arr, key)) {
            Some(value) => copy(out, value),
            None => null(out),
        },
        _ => null(out),
    }
}

/// Structural equality, matching `.code` source's `=` operator.
pub fn values_equal(a: &CodeValue, b: &CodeValue) -> bool {
    unsafe { code_values_equal(a, b) != 0 }
}

// `bool_value` and `assert_value` used to live here, wrapping
// `code_bool_value` and `code_assert`. Both are gone as of phase 3
// (2026-08-28) along with their declarations in `code_abi.h`: they are the
// compiler's own — one checks an `and`/`or` operand, the other is the
// `assert` statement — and since phase 3 they report trouble through a flag
// that only the host's generated code reads, so a module calling one would
// have had its failure silently swallowed. A module that cannot do its work
// returns [`exception`] instead; it may never end the application.

/// Raise a fatal module error, taking the whole host process down.
///
/// **Deprecated as of 2026-08-28, and not for modules to call.** A module
/// may never end the application — see
/// `docs/todo/errors-as-particles.md`. Report a failure by returning an
/// [`exception`] instead, which the program receives as an ordinary value
/// and may examine or ignore.
///
/// Kept only because `runtime.c` itself still uses it internally for
/// conditions with no frame to return to (out of memory). It will leave
/// this crate's API entirely once the C runtime has an error channel.
#[deprecated(
    since = "1.1.0",
    note = "a module may not end the application; return `exception(out, source, message)` instead"
)]
pub fn runtime_error(message: &str) -> ! {
    let c = cstr(message);
    unsafe { code_runtime_error(c.as_ptr()) }
}

// ===========================================================================
// Slot buffers — for Array/Object construction, which `runtime.c` expects
// as a `CODE_VALUE_SLOT_SIZE`-strided scratch buffer of already-built
// elements (see `code_array`/`code_object`'s doc comments in `runtime.c`;
// `tests/native_modules/test_math`'s `factors`/`meta` exported vars are
// the C-side version of the same pattern).
// ===========================================================================

/// A scratch buffer of `count` [`CodeValue`] slots, zero-initialized (so
/// each slot starts in the same safe state [`CodeValue::zeroed`] documents).
/// Build each element in place with [`SlotBuffer::slot_mut`], then hand the
/// buffer to [`array`] or [`object`] — matching `runtime.c`'s "elements are
/// retained and copied out of this buffer, never adopted by reference"
/// contract, after which every slot you wrote must still be [`release`]d
/// (the copy took its own reference; yours is still live until you drop it).
pub struct SlotBuffer {
    buf: Vec<u8>,
    len: i64,
}

impl SlotBuffer {
    pub fn new(count: usize) -> Self {
        Self {
            buf: vec![0u8; count * CODE_VALUE_SLOT_SIZE],
            len: count as i64,
        }
    }

    /// Slot `index` — write a value into it with [`number`]/[`owned_str`]/etc.
    pub fn slot_mut(&mut self, index: i64) -> &mut CodeValue {
        debug_assert!(index >= 0 && index < self.len);
        unsafe { &mut *slot_at(self.buf.as_mut_ptr() as *mut c_void, index) }
    }

    fn as_items_ptr(&mut self) -> *mut c_void {
        self.buf.as_mut_ptr() as *mut c_void
    }

    /// Release every slot. Call after handing the buffer to [`array`] or
    /// [`object`] — they copy elements out, they don't take ownership of
    /// this buffer's own references.
    pub fn release_all(&mut self) {
        for i in 0..self.len {
            unsafe { code_release(slot_at(self.buf.as_mut_ptr() as *mut c_void, i)) }
        }
    }
}

/// Write an Array into `out`, copying (and retaining) `elems`'s slots.
/// `elems` still owns its own references afterwards — release it once
/// you're done (see [`SlotBuffer::release_all`]).
pub fn array(out: &mut CodeValue, elems: &mut SlotBuffer) {
    unsafe { code_array(out, elems.as_items_ptr(), elems.len) }
}

/// Write an Object into `out` from parallel `keys` and `values` (a
/// [`SlotBuffer`] built the same way [`array`] expects). `keys` must
/// outlive nothing in particular — `code_object` copies the pointers, and
/// C-string field names are expected to be `'static` (string literals),
/// matching `code_abi.h`'s own "key pointers are read-only data" note.
pub fn object(out: &mut CodeValue, keys: &[&'static CStr], values: &mut SlotBuffer) {
    debug_assert_eq!(keys.len() as i64, values.len);
    let mut key_ptrs: Vec<*const c_char> = keys.iter().map(|k| k.as_ptr()).collect();
    unsafe {
        code_object(
            out,
            key_ptrs.as_mut_ptr(),
            values.as_items_ptr(),
            values.len,
        )
    }
}

// ===========================================================================
// Reading helpers — for use inside `code_module_dispatch`.
// ===========================================================================

/// Read a field by name off an Object value. `None` if `v` isn't an
/// Object or the field doesn't exist — mirrors `code_field`'s own
/// permissive-null behavior, but as an `Option` instead of writing Null.
pub fn find_field<'a>(v: &'a CodeValue, name: &str) -> Option<&'a CodeValue> {
    if v.tag != CodeTag::Object || v.keys.is_null() {
        return None;
    }
    for i in 0..v.len {
        let key = unsafe { *v.keys.offset(i as isize) };
        if key.is_null() {
            continue;
        }
        let key_str = unsafe { CStr::from_ptr(key) };
        if key_str.to_bytes() == name.as_bytes() {
            return Some(unsafe { &*slot_at(v.items, i) });
        }
    }
    None
}

/// Read `v` as a `&str`, if it's a Str with a valid UTF-8 payload.
pub fn read_str(v: &CodeValue) -> Option<&str> {
    if v.tag != CodeTag::Str || v.str.is_null() {
        return None;
    }
    unsafe { CStr::from_ptr(v.str) }.to_str().ok()
}

/// Read `v` as an `f64`, if it's a Number.
pub fn read_number(v: &CodeValue) -> Option<f64> {
    (v.tag == CodeTag::Number).then_some(v.number)
}

/// Read `v` as a `bool`, if it's a Bool.
pub fn read_bool(v: &CodeValue) -> Option<bool> {
    (v.tag == CodeTag::Bool).then_some(v.boolean != 0)
}

/// Convenience: [`find_field`] + [`read_str`].
pub fn read_field_str<'a>(v: &'a CodeValue, name: &str) -> Option<&'a str> {
    read_str(find_field(v, name)?)
}

/// Convenience: [`find_field`] + [`read_number`].
pub fn read_field_number(v: &CodeValue, name: &str) -> Option<f64> {
    read_number(find_field(v, name)?)
}

/// Convenience: [`find_field`] + [`read_bool`].
pub fn read_field_bool(v: &CodeValue, name: &str) -> Option<bool> {
    read_bool(find_field(v, name)?)
}

/// Iterate an Array's elements.
pub fn array_elems(v: &CodeValue) -> impl Iterator<Item = &CodeValue> {
    let (items, len) = if v.tag == CodeTag::Array {
        (v.items, v.len)
    } else {
        (std::ptr::null_mut(), 0)
    };
    (0..len).map(move |i| unsafe { &*slot_at(items, i) })
}

/// Build a `{ _class = <class_name>, value = <fill's result> }` particle
/// into `out` — the shape `emit ... to <alias> get x` expects a handler's
/// result to have. Mirrors `runtime.c`'s own `code_make_result`, which a
/// C module reaches via `#include "runtime.c"` but isn't exported for a
/// separately-linked module to call directly, so this is a small
/// reimplementation rather than an FFI binding.
pub fn make_result(
    out: &mut CodeValue,
    class_name: &'static CStr,
    fill: impl FnOnce(&mut CodeValue),
) {
    let mut value = CodeValue::zeroed();
    fill(&mut value);
    let mut buf = SlotBuffer::new(2);
    borrowed_str(buf.slot_mut(0), class_name);
    unsafe { code_copy(buf.slot_mut(1), &value) };
    object(out, &[c"_class", c"value"], &mut buf);
    buf.release_all();
    release(&mut value);
}

// ===========================================================================
// Inbound emissions — speaking first, rather than only answering.
// ===========================================================================

/// The host's pusher, handed over by `code_module_set_inbound`. `queue` is
/// opaque — a module only ever passes it straight back. Mirrors
/// `code_abi.h`'s `CodeEmitFn`.
pub type CodeEmitFn = unsafe extern "C" fn(queue: *mut c_void, value: *const CodeValue);

/// Where [`declare_inbound!`] parks what the host handed over. Two atomics
/// rather than a `static mut`: the host sets these once at link time, and a
/// module with a thread of its own would read them from that thread, so the
/// access wants to be well-defined even though nothing does that yet.
pub static INBOUND_QUEUE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
/// The `CodeEmitFn` as a raw address — `AtomicPtr` cannot hold a `fn`
/// pointer directly, and this is only ever written by [`store_inbound`] and
/// read back by [`emit_inbound`].
pub static INBOUND_EMIT: AtomicUsize = AtomicUsize::new(0);

/// Record what the host handed over. Called by the export
/// [`declare_inbound!`] generates; not useful on its own.
pub fn store_inbound(queue: *mut c_void, emit: CodeEmitFn) {
    INBOUND_QUEUE.store(queue, Ordering::Release);
    INBOUND_EMIT.store(emit as usize, Ordering::Release);
}

/// Generate the optional `code_module_set_inbound` export.
///
/// A macro rather than a plain function in this crate, and that is
/// load-bearing: `#[no_mangle]` symbols defined in a dependency are not
/// reliably kept in the final `cdylib`, so the export has to be emitted in
/// *your* crate. One invocation at the top level is all it takes:
///
/// ```rust,ignore
/// code_native::declare_inbound!();
/// ```
///
/// A module that never speaks first simply doesn't invoke it — the export is
/// optional, and the host checks for it rather than requiring it.
#[macro_export]
macro_rules! declare_inbound {
    // A `.a` static module spells its own export name, because every `.a`
    // linked into one program shares a flat symbol table and the host finds
    // these by prefix (`nm`, see loader.rs's `static_module_symbols`). It is
    // spelled out rather than pasted together from a prefix because
    // `macro_rules!` cannot concatenate identifiers — and spelling it matches
    // how a static module already writes its other three exports.
    ($name:ident) => {
        /// Handed the host's queue and pusher once, at link time.
        ///
        /// # Safety
        ///
        /// Called by the host with its own queue pointer and pusher; both
        /// stay valid for as long as the module is loaded.
        #[no_mangle]
        pub unsafe extern "C" fn $name(queue: *mut ::std::ffi::c_void, emit: $crate::CodeEmitFn) {
            $crate::store_inbound(queue, emit);
        }
    };
    () => {
        /// Handed the host's queue and pusher once, at link time.
        ///
        /// # Safety
        ///
        /// Called by the host with its own queue pointer and pusher; both
        /// stay valid for as long as the module is loaded.
        #[no_mangle]
        pub unsafe extern "C" fn code_module_set_inbound(
            queue: *mut ::std::ffi::c_void,
            emit: $crate::CodeEmitFn,
        ) {
            $crate::store_inbound(queue, emit);
        }
    };
}

/// Generate the optional `code_module_inbound_reply` export, which is how a
/// module hears what the program answered to something it pushed.
///
/// Takes the function to hand it to — `fn(particle: &CodeValue, result:
/// &CodeValue)`. `result` is a `CODE_NULL` value when no handler matched;
/// both references are the host's and are only valid for the duration of the
/// call, so read what you need and copy it out.
///
/// ```rust,ignore
/// fn answered(particle: &CodeValue, result: &CodeValue) { /* ... */ }
/// code_native::declare_inbound_reply!(answered);
/// ```
///
/// A macro rather than a plain function for the same reason as
/// [`declare_inbound!`]: a `#[no_mangle]` symbol defined in a dependency is
/// not reliably kept in the final `cdylib`.
#[macro_export]
macro_rules! declare_inbound_reply {
    // A `.a` static module spells its own export name — see
    // [`declare_inbound!`] for why.
    ($name:ident, $handler:path) => {
        /// Called by the host after a particle this module pushed was
        /// dispatched.
        ///
        /// # Safety
        ///
        /// Both pointers are the host's and valid for this call only.
        #[no_mangle]
        pub unsafe extern "C" fn $name(
            particle: *const $crate::CodeValue,
            result: *const $crate::CodeValue,
        ) {
            if particle.is_null() || result.is_null() {
                return;
            }
            $handler(&*particle, &*result);
        }
    };
    ($handler:path) => {
        /// Called by the host after a particle this module pushed was
        /// dispatched.
        ///
        /// # Safety
        ///
        /// Both pointers are the host's and valid for this call only.
        #[no_mangle]
        pub unsafe extern "C" fn code_module_inbound_reply(
            particle: *const $crate::CodeValue,
            result: *const $crate::CodeValue,
        ) {
            if particle.is_null() || result.is_null() {
                return;
            }
            $handler(&*particle, &*result);
        }
    };
}

/// Push a particle into the program, to be dispatched to *its* handlers the
/// next time the host drains (between top-level statements).
///
/// Returns `false` when the host never called `code_module_set_inbound` —
/// which happens whenever the module was loaded by something that does not
/// support inbound emissions. Pushing is therefore always best-effort from
/// the module's side, and a module must stay correct when nobody is
/// listening.
///
/// The particle is deep-copied into the host's heap by the host's own
/// pusher, so `value` may be released as soon as this returns.
///
/// **A pushed class the program has no handler for is a runtime error**, not
/// a silent drop (`tests/fail_inbound_unhandled.code` pins that). Push only
/// what the program has agreed to receive.
pub fn emit_inbound(value: &CodeValue) -> bool {
    let emit = INBOUND_EMIT.load(Ordering::Acquire);
    if emit == 0 {
        return false;
    }
    let queue = INBOUND_QUEUE.load(Ordering::Acquire);
    // SAFETY: `emit` is non-zero only because `store_inbound` wrote a real
    // `CodeEmitFn` there, and `queue` is whatever the host paired with it.
    let emit: CodeEmitFn = unsafe { std::mem::transmute::<usize, CodeEmitFn>(emit) };
    unsafe { emit(queue, value) };
    true
}

// ===========================================================================
// Failing without ending the program.
// ===========================================================================

/// Build `Exception { source, message, innerException }` into `out` — how a
/// module reports that it could not do the work.
///
/// This is the *only* way a module should fail. A module may never end the
/// application (`docs/todo/errors-as-particles.md`): the program receives
/// this as an ordinary value through `get`, and may test it with
/// `is Exception`, read `message`, or ignore it entirely.
///
/// `source` names the module, which a returned value cannot otherwise be
/// asked — the caller knows what it emitted to, but an `Exception` stored,
/// passed on, or wrapped as another's `innerException` has lost that.
///
/// `innerException` is null here; use [`exception_wrapping`] to carry the
/// failure underneath this one.
pub fn exception(out: &mut CodeValue, source: &str, message: &str) {
    let mut inner = CodeValue::zeroed();
    null(&mut inner);
    exception_wrapping(out, source, message, &inner);
    release(&mut inner);
}

/// [`exception`], carrying the failure that caused it as `innerException`.
pub fn exception_wrapping(out: &mut CodeValue, source: &str, message: &str, inner: &CodeValue) {
    let mut buf = SlotBuffer::new(4);
    borrowed_str(buf.slot_mut(0), c"Exception");
    owned_str(buf.slot_mut(1), source);
    owned_str(buf.slot_mut(2), message);
    copy(buf.slot_mut(3), inner);
    object(
        out,
        &[c"_class", c"source", c"message", c"innerException"],
        &mut buf,
    );
    buf.release_all();
}

/// Run a module's dispatch body so that a panic inside it becomes an
/// [`exception`] rather than killing the host.
///
/// **Wrap every `code_module_dispatch` in this.** The guarantee it provides
/// cannot be provided by the host: a panic escaping an `extern "C"` function
/// aborts the process rather than unwinding, so the host's own
/// `catch_unwind` never runs — the catch has to happen on this side of the
/// FFI boundary, which is here.
///
/// What it covers is most of what "a badly written module" means in
/// practice: `unwrap`/`expect` on `None` or `Err`, slice and index bounds,
/// arithmetic overflow, explicit `panic!`/`assert!`, and panics raised
/// inside dependencies. What it cannot cover is a deliberate `exit`, an
/// infinite loop, or undefined behaviour reached through `unsafe`.
///
/// ```rust,ignore
/// #[no_mangle]
/// pub unsafe extern "C" fn code_module_dispatch(
///     out: *mut CodeValue,
///     particle: *const CodeValue,
/// ) {
///     guarded(&mut *out, "mymodule", |out| match read_field_str(&*particle, "_class") {
///         Some("Double") => { /* ... */ }
///         _ => null(out),
///     })
/// }
/// ```
pub fn guarded(out: &mut CodeValue, source: &str, body: impl FnOnce(&mut CodeValue)) {
    let slot: *mut CodeValue = out;
    // `AssertUnwindSafe` over the whole closure: `out` is a slot the host
    // owns, there is no invariant of ours for a panic to leave half-broken,
    // and whatever the body managed to write is released by the constructor
    // `exception` runs next.
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        // SAFETY: `slot` came from the `&mut` above and outlives this call.
        body(unsafe { &mut *slot })
    }));
    let Err(payload) = result else {
        return;
    };
    // Rust's panic payload is a string for `panic!("...")` and `unwrap`
    // alike; anything else is reported without a message rather than
    // guessed at.
    let detail = payload
        .downcast_ref::<&str>()
        .map(|s| (*s).to_string())
        .or_else(|| payload.downcast_ref::<String>().cloned())
        .unwrap_or_else(|| "panicked".to_string());
    // SAFETY: as above — `catch_unwind` returning `Err` means the body
    // stopped early, not that the slot went away.
    exception(
        unsafe { &mut *slot },
        source,
        &format!("module panicked: {detail}"),
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    /// `{ a = 1, s = "hi" }`.
    fn sample_object(out: &mut CodeValue) {
        let mut values = SlotBuffer::new(2);
        number(values.slot_mut(0), 1.0);
        owned_str(values.slot_mut(1), "hi");
        object(out, &[c"a", c"s"], &mut values);
        values.release_all();
    }

    /// `[10, 20, 30]`.
    fn sample_array(out: &mut CodeValue) {
        let mut items = SlotBuffer::new(3);
        for (i, v) in [10.0, 20.0, 30.0].into_iter().enumerate() {
            number(items.slot_mut(i as i64), v);
        }
        array(out, &mut items);
        items.release_all();
    }

    fn tag_of(f: impl FnOnce(&mut CodeValue)) -> CodeTag {
        let mut out = CodeValue::zeroed();
        f(&mut out);
        let tag = out.tag;
        release(&mut out);
        tag
    }

    #[test]
    fn field_reads_a_present_member() {
        let mut obj = CodeValue::zeroed();
        sample_object(&mut obj);
        let mut out = CodeValue::zeroed();
        field(&mut out, &obj, "s");
        assert_eq!(read_str(&out), Some("hi"));
        release(&mut out);
        release(&mut obj);
    }

    /// The half `field` shares with the language: an absent member is null,
    /// not a failure. The lookup was fine, it just found nothing.
    #[test]
    fn field_answers_null_for_an_absent_member() {
        let mut obj = CodeValue::zeroed();
        sample_object(&mut obj);
        assert_eq!(tag_of(|out| field(out, &obj, "nope")), CodeTag::Null);
        release(&mut obj);
    }

    /// The half it does *not* share, and the reason this is Rust rather than
    /// a call into the ABI. `.code` source treats `"abc".length` as an error;
    /// a module cannot use a fallible accessor, because a failure raised
    /// inside a `.so` sets that copy's flag and nobody reads it. Total wins
    /// here, and the module says so itself with `exception` if it minds.
    #[test]
    fn field_answers_null_on_a_non_object() {
        let mut n = CodeValue::zeroed();
        number(&mut n, 42.0);
        assert_eq!(tag_of(|out| field(out, &n, "anything")), CodeTag::Null);
        release(&mut n);
    }

    #[test]
    fn index_reads_an_array_element() {
        let mut arr = CodeValue::zeroed();
        sample_array(&mut arr);
        let mut i = CodeValue::zeroed();
        number(&mut i, 1.0);
        let mut out = CodeValue::zeroed();
        index(&mut out, &arr, &i);
        assert_eq!(read_number(&out), Some(20.0));
        release(&mut out);
        release(&mut arr);
    }

    /// Every way of missing an array element is null, matching the
    /// language: out of range, negative, and a whole-number check that
    /// rejects `1.5` rather than truncating it.
    #[test]
    fn index_answers_null_for_every_kind_of_miss() {
        let mut arr = CodeValue::zeroed();
        sample_array(&mut arr);
        for probe in [99.0, -1.0, 1.5] {
            let mut i = CodeValue::zeroed();
            number(&mut i, probe);
            assert_eq!(
                tag_of(|out| index(out, &arr, &i)),
                CodeTag::Null,
                "index {probe} should be null"
            );
        }
        // A non-Number index into an Array is null too, not an error.
        let mut key = CodeValue::zeroed();
        owned_str(&mut key, "0");
        assert_eq!(tag_of(|out| index(out, &arr, &key)), CodeTag::Null);
        release(&mut key);
        release(&mut arr);
    }

    /// An Array is keyed by Number and an Object by Str — the same split
    /// `loop` uses — so a computed key on an Object is `find_field` under
    /// another name.
    #[test]
    fn index_reads_an_object_by_string_key() {
        let mut obj = CodeValue::zeroed();
        sample_object(&mut obj);
        let mut key = CodeValue::zeroed();
        owned_str(&mut key, "a");
        let mut out = CodeValue::zeroed();
        index(&mut out, &obj, &key);
        assert_eq!(read_number(&out), Some(1.0));
        release(&mut out);
        release(&mut key);
        release(&mut obj);
    }

    #[test]
    fn index_answers_null_on_a_non_container() {
        let mut n = CodeValue::zeroed();
        number(&mut n, 42.0);
        let mut i = CodeValue::zeroed();
        number(&mut i, 0.0);
        assert_eq!(tag_of(|out| index(out, &n, &i)), CodeTag::Null);
        release(&mut n);
    }
}