use std::ffi::{c_char, c_int, c_void, CStr};
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
pub const CODE_ABI_VERSION: u32 = 2;
pub mod borrowed;
pub const CODE_VALUE_SLOT_SIZE: usize = 16;
#[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,
payload: Payload,
}
#[repr(C)]
#[derive(Clone, Copy)]
union Payload {
number: f64,
str: *const c_char,
boolean: c_int,
list: *mut CodeList,
}
#[repr(C)]
pub struct CodeList {
pub len: i64,
pub keys: *mut *const c_char,
}
impl CodeValue {
pub fn number(&self) -> f64 {
if self.tag == CodeTag::Number {
unsafe { self.payload.number }
} else {
0.0
}
}
pub fn str_ptr(&self) -> *const c_char {
if self.tag == CodeTag::Str {
unsafe { self.payload.str }
} else {
std::ptr::null()
}
}
pub fn boolean(&self) -> c_int {
if self.tag == CodeTag::Bool {
unsafe { self.payload.boolean }
} else {
0
}
}
fn list(&self) -> *mut CodeList {
if matches!(self.tag, CodeTag::Array | CodeTag::Object) {
unsafe { self.payload.list }
} else {
std::ptr::null_mut()
}
}
pub fn len(&self) -> i64 {
let list = self.list();
if list.is_null() {
0
} else {
unsafe { (*list).len }
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn items(&self) -> *mut c_void {
let list = self.list();
if list.is_null() {
std::ptr::null_mut()
} else {
unsafe { list.add(1) as *mut c_void }
}
}
pub fn keys(&self) -> *mut *const c_char {
let list = self.list();
if list.is_null() {
std::ptr::null_mut()
} else {
unsafe { (*list).keys }
}
}
pub fn zeroed() -> Self {
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,
pub values: *mut CodeValue,
}
unsafe impl Send for CodeValue {}
unsafe impl Sync for CodeValue {}
unsafe impl Send for CodeVarList {}
unsafe impl Sync for CodeVarList {}
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) -> !;
#[cfg(all(feature = "shared-module", not(feature = "static-module")))]
fn code_native_vendored_release(v: *mut CodeValue);
}
#[cfg(any(not(feature = "shared-module"), feature = "static-module"))]
extern "C" {
fn code_release(v: *mut CodeValue);
}
#[cfg(all(feature = "shared-module", not(feature = "static-module")))]
#[no_mangle]
pub unsafe extern "C" fn code_release(v: *mut CodeValue) {
code_native_vendored_release(v)
}
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())
}
pub fn number(out: &mut CodeValue, n: f64) {
unsafe { code_number(out, n) }
}
pub fn borrowed_str(out: &mut CodeValue, s: &'static CStr) {
unsafe { code_str(out, s.as_ptr()) }
}
pub fn owned_str(out: &mut CodeValue, s: &str) {
let c = cstr(s);
unsafe { code_str(out, c.as_ptr()) }
std::mem::forget(c);
}
pub fn boolean(out: &mut CodeValue, b: bool) {
unsafe { code_bool(out, b as c_int) }
}
pub fn null(out: &mut CodeValue) {
unsafe { code_null(out) }
}
pub fn release(v: &mut CodeValue) {
unsafe { code_release(v) }
}
pub fn copy(out: &mut CodeValue, src: &CodeValue) {
unsafe { code_copy(out, src) }
}
pub fn retain(v: &CodeValue) {
unsafe { code_retain(v) }
}
pub fn field(out: &mut CodeValue, obj: &CodeValue, name: &str) {
match find_field(obj, name) {
Some(value) => copy(out, value),
None => null(out),
}
}
pub fn index(out: &mut CodeValue, arr: &CodeValue, i: &CodeValue) {
match arr.tag {
CodeTag::Array => {
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);
}
}
CodeTag::Object => match read_str(i).and_then(|key| find_field(arr, key)) {
Some(value) => copy(out, value),
None => null(out),
},
_ => null(out),
}
}
pub fn values_equal(a: &CodeValue, b: &CodeValue) -> bool {
unsafe { code_values_equal(a, b) != 0 }
}
#[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()) }
}
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,
}
}
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
}
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)) }
}
}
}
pub fn array(out: &mut CodeValue, elems: &mut SlotBuffer) {
unsafe { code_array(out, elems.as_items_ptr(), elems.len) }
}
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,
)
}
}
pub fn object_dyn(out: &mut CodeValue, keys: &[&str], values: &mut SlotBuffer) {
debug_assert_eq!(keys.len() as i64, values.len);
let c_keys: Vec<std::ffi::CString> = keys.iter().map(|k| cstr(k)).collect();
let mut key_ptrs: Vec<*const c_char> = c_keys.iter().map(|k| k.as_ptr()).collect();
unsafe {
code_object(
out,
key_ptrs.as_mut_ptr(),
values.as_items_ptr(),
values.len,
)
}
}
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
}
pub fn read_str(v: &CodeValue) -> Option<&str> {
if v.tag != CodeTag::Str || v.str_ptr().is_null() {
return None;
}
unsafe { CStr::from_ptr(v.str_ptr()) }.to_str().ok()
}
pub fn read_number(v: &CodeValue) -> Option<f64> {
(v.tag == CodeTag::Number).then_some(v.number())
}
pub fn read_bool(v: &CodeValue) -> Option<bool> {
(v.tag == CodeTag::Bool).then_some(v.boolean() != 0)
}
pub fn read_field_str<'a>(v: &'a CodeValue, name: &str) -> Option<&'a str> {
read_str(find_field(v, name)?)
}
pub fn read_field_number(v: &CodeValue, name: &str) -> Option<f64> {
read_number(find_field(v, name)?)
}
pub fn read_field_bool(v: &CodeValue, name: &str) -> Option<bool> {
read_bool(find_field(v, name)?)
}
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) })
}
pub fn object_entries(v: &CodeValue) -> impl Iterator<Item = (&str, &CodeValue)> {
let (keys, items, len) = if v.tag == CodeTag::Object && !v.keys().is_null() {
(v.keys(), v.items(), v.len())
} else {
(std::ptr::null_mut(), std::ptr::null_mut(), 0)
};
(0..len).filter_map(move |i| {
let key = unsafe { *keys.offset(i as isize) };
if key.is_null() {
return None;
}
let key = unsafe { CStr::from_ptr(key) }.to_str().ok()?;
Some((key, unsafe { &*slot_at(items, i) }))
})
}
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);
}
pub type CodeEmitFn = unsafe extern "C" fn(queue: *mut c_void, value: *const CodeValue);
pub static INBOUND_QUEUE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
pub static INBOUND_EMIT: AtomicUsize = AtomicUsize::new(0);
pub fn store_inbound(queue: *mut c_void, emit: CodeEmitFn) {
INBOUND_QUEUE.store(queue, Ordering::Release);
INBOUND_EMIT.store(emit as usize, Ordering::Release);
}
#[macro_export]
macro_rules! declare_inbound {
($name:ident) => {
#[no_mangle]
pub unsafe extern "C" fn $name(queue: *mut ::std::ffi::c_void, emit: $crate::CodeEmitFn) {
$crate::store_inbound(queue, emit);
}
};
() => {
#[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);
}
};
}
#[macro_export]
macro_rules! declare_inbound_reply {
($name:ident, $handler:path) => {
#[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) => {
#[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);
}
};
}
pub fn emit_inbound(value: &CodeValue) -> bool {
inbound_here().emit(value)
}
#[derive(Clone, Copy)]
pub struct Inbound {
queue: *mut c_void,
emit: usize,
}
unsafe impl Send for Inbound {}
impl Inbound {
pub fn new(queue: *mut c_void, emit: CodeEmitFn) -> Inbound {
Inbound {
queue,
emit: emit as usize,
}
}
pub const NONE: Inbound = Inbound {
queue: std::ptr::null_mut(),
emit: 0,
};
pub fn emit(&self, value: &CodeValue) -> bool {
if self.emit == 0 {
return false;
}
let emit: CodeEmitFn = unsafe { std::mem::transmute::<usize, CodeEmitFn>(self.emit) };
unsafe { emit(self.queue, value) };
true
}
}
thread_local! {
static CURRENT_INBOUND: std::cell::Cell<Option<Inbound>> = const { std::cell::Cell::new(None) };
}
pub fn inbound_here() -> Inbound {
if let Some(inbound) = CURRENT_INBOUND.with(std::cell::Cell::get) {
return inbound;
}
Inbound {
queue: INBOUND_QUEUE.load(Ordering::Acquire),
emit: INBOUND_EMIT.load(Ordering::Acquire),
}
}
pub struct InboundScope {
previous: Option<Inbound>,
}
impl InboundScope {
pub fn enter(inbound: Inbound) -> InboundScope {
InboundScope {
previous: CURRENT_INBOUND.with(|c| c.replace(Some(inbound))),
}
}
}
impl Drop for InboundScope {
fn drop(&mut self) {
let previous = self.previous;
CURRENT_INBOUND.with(|c| c.set(previous));
}
}
pub fn with_inbound<R>(inbound: Inbound, f: impl FnOnce() -> R) -> R {
let previous = CURRENT_INBOUND.with(|c| c.replace(Some(inbound)));
let result = f();
CURRENT_INBOUND.with(|c| c.set(previous));
result
}
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);
}
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();
}
pub fn guarded(out: &mut CodeValue, source: &str, body: impl FnOnce(&mut CodeValue)) {
let slot: *mut CodeValue = out;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
body(unsafe { &mut *slot })
}));
let Err(payload) = result else {
return;
};
let detail = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "panicked".to_string());
exception(
unsafe { &mut *slot },
source,
&format!("module panicked: {detail}"),
);
}
#[cfg(test)]
mod tests {
use super::*;
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();
}
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 object_dyn_builds_with_runtime_keys() {
let mut values = SlotBuffer::new(2);
number(values.slot_mut(0), 1.0);
number(values.slot_mut(1), 2.0);
let mut obj = CodeValue::zeroed();
{
let names: Vec<String> = vec!["one".into(), "two".into()];
let key_refs: Vec<&str> = names.iter().map(String::as_str).collect();
object_dyn(&mut obj, &key_refs, &mut values);
}
values.release_all();
assert_eq!(read_field_number(&obj, "one"), Some(1.0));
assert_eq!(read_field_number(&obj, "two"), Some(2.0));
release(&mut obj);
}
#[test]
fn object_entries_walks_every_field_in_order() {
let mut obj = CodeValue::zeroed();
sample_object(&mut obj); let seen: Vec<(String, CodeTag)> = object_entries(&obj)
.map(|(k, v)| (k.to_owned(), v.tag))
.collect();
assert_eq!(
seen,
vec![
("a".to_owned(), CodeTag::Number),
("s".to_owned(), CodeTag::Str),
]
);
release(&mut obj);
let mut n = CodeValue::zeroed();
number(&mut n, 5.0);
assert_eq!(object_entries(&n).count(), 0);
release(&mut n);
}
#[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);
}
#[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);
}
#[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);
}
#[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"
);
}
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);
}
#[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);
}
}