#![allow(clippy::not_unsafe_ptr_arg_deref)]
use cljrs_gc::GcPtr;
use cljrs_value::keyword::Keyword;
use cljrs_value::value::{MapValue, PrintValue, SetValue};
use cljrs_value::{
CljxCons, PersistentHashSet, PersistentList, PersistentVector, Symbol, TypeInstance, Value,
};
use std::sync::Arc;
#[inline]
unsafe fn val_ref<'a>(ptr: *const Value) -> &'a Value {
unsafe { &*ptr }
}
#[inline]
unsafe fn collect_args(elems: *const *const Value, n: i64) -> Vec<Value> {
let mut args = Vec::with_capacity(n as usize);
for i in 0..n as usize {
let ptr = unsafe { *elems.add(i) };
args.push(unsafe { val_ref(ptr) }.clone());
}
args
}
#[inline]
fn box_val(v: Value) -> *const Value {
let ptr = GcPtr::new(v);
ptr.get() as *const Value
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_safepoint() {
cljrs_gc::safepoint();
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_nil() -> *const Value {
box_val(Value::Nil)
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_true() -> *const Value {
box_val(Value::Bool(true))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_false() -> *const Value {
box_val(Value::Bool(false))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_long(n: i64) -> *const Value {
box_val(Value::Long(n))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_double(n: f64) -> *const Value {
box_val(Value::Double(n))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_const_char(c: u32) -> *const Value {
box_val(Value::Char(char::from_u32(c).unwrap_or('\u{FFFD}')))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_const_string(ptr: *const u8, len: u64) -> *const Value {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as *const () as usize) };
let s = std::str::from_utf8(bytes).unwrap_or("<invalid utf8>");
box_val(Value::Str(GcPtr::new(s.to_string())))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_const_keyword(ptr: *const u8, len: u64) -> *const Value {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as *const () as usize) };
let name = std::str::from_utf8(bytes).unwrap_or("??");
box_val(Value::Keyword(GcPtr::new(Keyword::simple(name))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_const_symbol(ptr: *const u8, len: u64) -> *const Value {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as *const () as usize) };
let name = std::str::from_utf8(bytes).unwrap_or("??");
box_val(Value::Symbol(GcPtr::new(Symbol {
namespace: None,
name: Arc::from(name),
})))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_truthiness(v: *const Value) -> u8 {
let v = unsafe { val_ref(v) };
match v {
Value::Nil | Value::Bool(false) => 0,
_ => 1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_add(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
match (a, b) {
(Value::Long(x), Value::Long(y)) => box_val(Value::Long(x.wrapping_add(*y))),
(Value::Double(x), Value::Double(y)) => box_val(Value::Double(x + y)),
(Value::Long(x), Value::Double(y)) => box_val(Value::Double(*x as f64 + y)),
(Value::Double(x), Value::Long(y)) => box_val(Value::Double(x + *y as f64)),
_ => rt_const_nil(), }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_sub(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
match (a, b) {
(Value::Long(x), Value::Long(y)) => box_val(Value::Long(x.wrapping_sub(*y))),
(Value::Double(x), Value::Double(y)) => box_val(Value::Double(x - y)),
(Value::Long(x), Value::Double(y)) => box_val(Value::Double(*x as f64 - y)),
(Value::Double(x), Value::Long(y)) => box_val(Value::Double(x - *y as f64)),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_mul(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
match (a, b) {
(Value::Long(x), Value::Long(y)) => box_val(Value::Long(x.wrapping_mul(*y))),
(Value::Double(x), Value::Double(y)) => box_val(Value::Double(x * y)),
(Value::Long(x), Value::Double(y)) => box_val(Value::Double(*x as f64 * y)),
(Value::Double(x), Value::Long(y)) => box_val(Value::Double(x * *y as f64)),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_div(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
match (a, b) {
(Value::Long(x), Value::Long(y)) if *y != 0 => box_val(Value::Long(x / y)),
(Value::Double(x), Value::Double(y)) => box_val(Value::Double(x / y)),
(Value::Long(x), Value::Double(y)) => box_val(Value::Double(*x as f64 / y)),
(Value::Double(x), Value::Long(y)) => box_val(Value::Double(x / *y as f64)),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_rem(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
match (a, b) {
(Value::Long(x), Value::Long(y)) if *y != 0 => box_val(Value::Long(x % y)),
(Value::Double(x), Value::Double(y)) => box_val(Value::Double(x % y)),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_eq(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
box_val(Value::Bool(a == b))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_lt(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
let result = match (a, b) {
(Value::Long(x), Value::Long(y)) => x < y,
(Value::Double(x), Value::Double(y)) => x < y,
(Value::Long(x), Value::Double(y)) => (*x as f64) < *y,
(Value::Double(x), Value::Long(y)) => *x < (*y as f64),
_ => false,
};
box_val(Value::Bool(result))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_gt(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
let result = match (a, b) {
(Value::Long(x), Value::Long(y)) => x > y,
(Value::Double(x), Value::Double(y)) => x > y,
(Value::Long(x), Value::Double(y)) => (*x as f64) > *y,
(Value::Double(x), Value::Long(y)) => *x > (*y as f64),
_ => false,
};
box_val(Value::Bool(result))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_lte(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
let result = match (a, b) {
(Value::Long(x), Value::Long(y)) => x <= y,
(Value::Double(x), Value::Double(y)) => x <= y,
(Value::Long(x), Value::Double(y)) => (*x as f64) <= *y,
(Value::Double(x), Value::Long(y)) => *x <= (*y as f64),
_ => false,
};
box_val(Value::Bool(result))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_gte(a: *const Value, b: *const Value) -> *const Value {
let a = unsafe { val_ref(a) };
let b = unsafe { val_ref(b) };
let result = match (a, b) {
(Value::Long(x), Value::Long(y)) => x >= y,
(Value::Double(x), Value::Double(y)) => x >= y,
(Value::Long(x), Value::Double(y)) => (*x as f64) >= *y,
(Value::Double(x), Value::Long(y)) => *x >= (*y as f64),
_ => false,
};
box_val(Value::Bool(result))
}
use std::cell::RefCell;
thread_local! {
#[allow(clippy::vec_box)]
static RT_REGION_STACK: RefCell<Vec<Box<cljrs_gc::region::Region>>> =
const { RefCell::new(Vec::new()) };
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_region_start() -> *const Value {
let mut region = Box::new(cljrs_gc::region::Region::new());
let region_ptr: *mut cljrs_gc::region::Region = &mut *region;
unsafe { cljrs_gc::region::push_region_raw(region_ptr) };
RT_REGION_STACK.with(|s| s.borrow_mut().push(region));
rt_const_nil()
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_region_end(_handle: *const Value) -> *const Value {
cljrs_gc::region::pop_region_guard();
RT_REGION_STACK.with(|s| {
s.borrow_mut().pop();
});
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_region_alloc_vector(
_handle: *const Value,
elems: *const *const Value,
n: u64,
) -> *const Value {
let n = n as usize;
let items: Vec<Value> = if n > 0 {
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
Vec::new()
};
let pv = PersistentVector::from_iter(items);
let ptr = if cljrs_gc::region::region_is_active() {
unsafe { cljrs_gc::region::try_alloc_in_region(pv).unwrap() }
} else {
GcPtr::new(pv)
};
box_val(Value::Vector(ptr))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_region_alloc_map(
_handle: *const Value,
pairs: *const *const Value,
n: u64,
) -> *const Value {
let n = n as usize;
let kv_pairs: Vec<(Value, Value)> = if n > 0 {
let slice = unsafe { std::slice::from_raw_parts(pairs, n * 2) };
(0..n)
.map(|i| {
let k = unsafe { val_ref(slice[i * 2]) }.clone();
let v = unsafe { val_ref(slice[i * 2 + 1]) }.clone();
(k, v)
})
.collect()
} else {
Vec::new()
};
box_val(Value::Map(MapValue::from_pairs(kv_pairs)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_region_alloc_set(
_handle: *const Value,
elems: *const *const Value,
n: u64,
) -> *const Value {
let n = n as usize;
let items: Vec<Value> = if n > 0 {
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
Vec::new()
};
let set = PersistentHashSet::from_iter(items);
box_val(Value::Set(SetValue::Hash(GcPtr::new(set))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_region_alloc_list(
_handle: *const Value,
elems: *const *const Value,
n: u64,
) -> *const Value {
let n = n as usize;
let items: Vec<Value> = if n > 0 {
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
Vec::new()
};
let list = PersistentList::from_iter(items);
let ptr = if cljrs_gc::region::region_is_active() {
unsafe { cljrs_gc::region::try_alloc_in_region(list).unwrap() }
} else {
GcPtr::new(list)
};
box_val(Value::List(ptr))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_region_alloc_cons(
_handle: *const Value,
head: *const Value,
tail: *const Value,
) -> *const Value {
let h = unsafe { val_ref(head) }.clone();
let t = unsafe { val_ref(tail) }.clone();
let cons = CljxCons { head: h, tail: t };
let ptr = if cljrs_gc::region::region_is_active() {
unsafe { cljrs_gc::region::try_alloc_in_region(cons).unwrap() }
} else {
GcPtr::new(cons)
};
box_val(Value::Cons(ptr))
}
fn unwind_regions_to(depth: usize) {
cljrs_gc::region::unwind_region_stack_to(depth);
RT_REGION_STACK.with(|s| {
let mut stack = s.borrow_mut();
while stack.len() > depth {
stack.pop(); }
});
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_alloc_vector(elems: *const *const Value, n: u64) -> *const Value {
let n = n as usize;
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
let items: Vec<Value> = slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect();
box_val(Value::Vector(GcPtr::new(PersistentVector::from_iter(
items,
))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_alloc_map(pairs: *const *const Value, n: u64) -> *const Value {
let n = n as usize;
let slice = unsafe { std::slice::from_raw_parts(pairs, n * 2) };
let kv_pairs: Vec<(Value, Value)> = (0..n)
.map(|i| {
let k = unsafe { val_ref(slice[i * 2]) }.clone();
let v = unsafe { val_ref(slice[i * 2 + 1]) }.clone();
(k, v)
})
.collect();
box_val(Value::Map(MapValue::from_pairs(kv_pairs)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_alloc_set(elems: *const *const Value, n: u64) -> *const Value {
let n = n as usize;
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
let items: Vec<Value> = slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect();
let set = PersistentHashSet::from_iter(items);
box_val(Value::Set(SetValue::Hash(GcPtr::new(set))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_alloc_list(elems: *const *const Value, n: u64) -> *const Value {
let n = n as usize;
let slice = unsafe { std::slice::from_raw_parts(elems, n) };
let items: Vec<Value> = slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect();
box_val(Value::List(GcPtr::new(PersistentList::from_iter(items))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_alloc_cons(head: *const Value, tail: *const Value) -> *const Value {
let h = unsafe { val_ref(head) }.clone();
let t = unsafe { val_ref(tail) }.clone();
box_val(Value::Cons(GcPtr::new(CljxCons { head: h, tail: t })))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_get(coll: *const Value, key: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let key = unsafe { val_ref(key) };
let result = match coll {
Value::Map(m) => m.get(key),
Value::TypeInstance(ti) => ti.get().fields.get(key),
Value::Vector(v) => {
if let Value::Long(i) = key {
v.get().nth(*i as *const () as usize).cloned()
} else {
None
}
}
_ => None,
};
box_val(result.unwrap_or(Value::Nil))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_count(coll: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let n = match coll {
Value::Vector(v) => v.get().count(),
Value::Map(m) => m.count(),
Value::Set(s) => s.count(),
Value::List(l) => l.get().count(),
Value::Str(s) => s.get().len(),
Value::Nil => 0,
_ => 0,
};
box_val(Value::Long(n as i64))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_first(coll: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let result = match coll {
Value::List(l) => l.get().first().cloned().unwrap_or(Value::Nil),
Value::Vector(v) => v.get().nth(0).cloned().unwrap_or(Value::Nil),
Value::Cons(c) => c.get().head.clone(),
Value::Nil => Value::Nil,
_ => Value::Nil,
};
box_val(result)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_rest(coll: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let result = match coll {
Value::List(l) => {
let rest = l.get().rest();
Value::List(GcPtr::new((*rest).clone()))
}
Value::Vector(v) => {
if v.get().count() <= 1 {
Value::List(GcPtr::new(PersistentList::empty()))
} else {
let items: Vec<Value> = v.get().iter().skip(1).cloned().collect();
Value::List(GcPtr::new(PersistentList::from_iter(items)))
}
}
Value::Cons(c) => c.get().tail.clone(),
Value::Nil => Value::List(GcPtr::new(PersistentList::empty())),
_ => Value::List(GcPtr::new(PersistentList::empty())),
};
box_val(result)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_assoc(
m: *const Value,
k: *const Value,
v: *const Value,
) -> *const Value {
let m = unsafe { val_ref(m) };
let k = unsafe { val_ref(k) }.clone();
let v = unsafe { val_ref(v) }.clone();
match m {
Value::Map(map) => {
let new_map = map.assoc(k, v);
box_val(Value::Map(new_map))
}
Value::TypeInstance(ti) => {
let mut fields = ti.get().fields.clone();
fields = fields.assoc(k, v);
box_val(Value::TypeInstance(GcPtr::new(TypeInstance {
type_tag: ti.get().type_tag.clone(),
fields,
})))
}
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_conj(coll: *const Value, val: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let val = unsafe { val_ref(val) }.clone();
match coll {
Value::Vector(v) => box_val(Value::Vector(GcPtr::new(v.get().conj(val)))),
Value::List(l) => {
let new_list = PersistentList::cons(val, Arc::new((*l.get()).clone()));
box_val(Value::List(GcPtr::new(new_list)))
}
Value::Set(s) => {
let new_set = s.conj(val);
box_val(Value::Set(new_set))
}
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_make_fn(
name_ptr: *const u8,
name_len: u64,
fn_ptr: *const u8,
param_count: u64,
captures: *const *const Value,
ncaptures: u64,
) -> *const Value {
let name_str = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
let name: Arc<str> = Arc::from(name_str);
let param_count = param_count as usize;
let ncaptures = ncaptures as usize;
let captured_values: Vec<Value> = if ncaptures > 0 {
let capture_slice = unsafe { std::slice::from_raw_parts(captures, ncaptures) };
capture_slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
vec![]
};
let fn_addr = fn_ptr as usize;
let total_params = ncaptures + param_count;
let native_fn = cljrs_value::NativeFn {
name: name.clone(),
arity: cljrs_value::Arity::Fixed(param_count),
func: Arc::new(move |args: &[Value]| {
if args.len() != param_count {
return Err(cljrs_value::ValueError::ArityError {
name: "compiled-fn".to_string(),
expected: param_count.to_string(),
got: args.len(),
});
}
let mut all_ptrs: Vec<*const Value> = Vec::with_capacity(total_params);
for cap in &captured_values {
all_ptrs.push(box_val(cap.clone()));
}
for arg in args {
all_ptrs.push(box_val(arg.clone()));
}
let result_ptr = unsafe { rt_call_compiled(fn_addr, all_ptrs.as_ptr(), total_params) };
Ok(unsafe { val_ref(result_ptr) }.clone())
}),
};
box_val(Value::NativeFunction(GcPtr::new(native_fn)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_make_fn_variadic(
name_ptr: *const u8,
name_len: u64,
fn_ptr: *const u8,
fixed_param_count: u64,
captures: *const *const Value,
ncaptures: u64,
) -> *const Value {
let name_str = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
let name: Arc<str> = Arc::from(name_str);
let fixed_count = fixed_param_count as usize;
let ncaptures = ncaptures as usize;
let captured_values: Vec<Value> = if ncaptures > 0 {
let capture_slice = unsafe { std::slice::from_raw_parts(captures, ncaptures) };
capture_slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
vec![]
};
let fn_addr = fn_ptr as usize;
let total_compiled_params = ncaptures + fixed_count + 1;
let native_fn = cljrs_value::NativeFn {
name: name.clone(),
arity: cljrs_value::Arity::Variadic { min: fixed_count },
func: Arc::new(move |args: &[Value]| {
if args.len() < fixed_count {
return Err(cljrs_value::ValueError::ArityError {
name: "compiled-fn".to_string(),
expected: format!("{fixed_count}+"),
got: args.len(),
});
}
let mut all_ptrs: Vec<*const Value> = Vec::with_capacity(total_compiled_params);
for cap in &captured_values {
all_ptrs.push(box_val(cap.clone()));
}
for arg in &args[..fixed_count] {
all_ptrs.push(box_val(arg.clone()));
}
let rest_args: Vec<Value> = args[fixed_count..].to_vec();
let rest_list = if rest_args.is_empty() {
Value::Nil
} else {
Value::List(GcPtr::new(PersistentList::from_iter(rest_args)))
};
all_ptrs.push(box_val(rest_list));
let result_ptr =
unsafe { rt_call_compiled(fn_addr, all_ptrs.as_ptr(), total_compiled_params) };
Ok(unsafe { val_ref(result_ptr) }.clone())
}),
};
box_val(Value::NativeFunction(GcPtr::new(native_fn)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_make_fn_multi(
name_ptr: *const u8,
name_len: u64,
fn_ptrs: *const *const u8,
param_counts_ptr: *const u64,
is_variadic_ptr: *const u8,
n_arities: u64,
captures: *const *const Value,
ncaptures: u64,
) -> *const Value {
let name_str = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
let name: Arc<str> = Arc::from(name_str);
let n_arities = n_arities as usize;
let ncaptures = ncaptures as usize;
let fn_ptr_slice = unsafe { std::slice::from_raw_parts(fn_ptrs, n_arities) };
let param_count_slice = unsafe { std::slice::from_raw_parts(param_counts_ptr, n_arities) };
let variadic_slice = unsafe { std::slice::from_raw_parts(is_variadic_ptr, n_arities) };
let arity_table: Vec<(usize, usize, bool)> = fn_ptr_slice
.iter()
.zip(param_count_slice.iter())
.zip(variadic_slice.iter())
.map(|((&fp, &pc), &v)| (fp as usize, pc as usize, v != 0))
.collect();
let captured_values: Vec<Value> = if ncaptures > 0 {
let capture_slice = unsafe { std::slice::from_raw_parts(captures, ncaptures) };
capture_slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
vec![]
};
let has_variadic = arity_table.iter().any(|(_, _, v)| *v);
let min_params = arity_table.iter().map(|(_, pc, _)| *pc).min().unwrap_or(0);
let max_params = arity_table.iter().map(|(_, pc, _)| *pc).max().unwrap_or(0);
let arity = if has_variadic {
cljrs_value::Arity::Variadic { min: min_params }
} else if min_params == max_params {
cljrs_value::Arity::Fixed(min_params)
} else {
cljrs_value::Arity::Variadic { min: min_params }
};
let fn_name = name.clone();
let native_fn = cljrs_value::NativeFn {
name,
arity,
func: Arc::new(move |args: &[Value]| {
let argc = args.len();
let matched = arity_table.iter().find(|(_, pc, v)| !v && *pc == argc);
if let Some(&(fn_addr, _pc, _v)) = matched {
let total_params = ncaptures + argc;
let mut all_ptrs: Vec<*const Value> = Vec::with_capacity(total_params);
for cap in &captured_values {
all_ptrs.push(box_val(cap.clone()));
}
for arg in args {
all_ptrs.push(box_val(arg.clone()));
}
let result_ptr =
unsafe { rt_call_compiled(fn_addr, all_ptrs.as_ptr(), total_params) };
return Ok(unsafe { val_ref(result_ptr) }.clone());
}
let variadic_match = arity_table.iter().find(|(_, pc, v)| *v && argc >= *pc);
if let Some(&(fn_addr, fixed_count, _)) = variadic_match {
let total_compiled = ncaptures + fixed_count + 1;
let mut all_ptrs: Vec<*const Value> = Vec::with_capacity(total_compiled);
for cap in &captured_values {
all_ptrs.push(box_val(cap.clone()));
}
for arg in &args[..fixed_count] {
all_ptrs.push(box_val(arg.clone()));
}
let rest_args: Vec<Value> = args[fixed_count..].to_vec();
let rest_list = if rest_args.is_empty() {
Value::Nil
} else {
Value::List(GcPtr::new(PersistentList::from_iter(rest_args)))
};
all_ptrs.push(box_val(rest_list));
let result_ptr =
unsafe { rt_call_compiled(fn_addr, all_ptrs.as_ptr(), total_compiled) };
return Ok(unsafe { val_ref(result_ptr) }.clone());
}
let counts: Vec<String> = arity_table
.iter()
.map(
|(_, pc, v)| {
if *v { format!("{pc}+") } else { pc.to_string() }
},
)
.collect();
Err(cljrs_value::ValueError::ArityError {
name: fn_name.to_string(),
expected: counts.join(" or "),
got: argc,
})
}),
};
box_val(Value::NativeFunction(GcPtr::new(native_fn)))
}
unsafe fn rt_call_compiled(
fn_addr: usize,
args: *const *const Value,
nargs: usize,
) -> *const Value {
let args = unsafe { std::slice::from_raw_parts(args, nargs) };
match nargs {
0 => {
let f: extern "C" fn() -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f()
}
1 => {
let f: extern "C" fn(*const Value) -> *const Value =
unsafe { std::mem::transmute(fn_addr) };
f(args[0])
}
2 => {
let f: extern "C" fn(*const Value, *const Value) -> *const Value =
unsafe { std::mem::transmute(fn_addr) };
f(args[0], args[1])
}
3 => {
let f: extern "C" fn(*const Value, *const Value, *const Value) -> *const Value =
unsafe { std::mem::transmute(fn_addr) };
f(args[0], args[1], args[2])
}
4 => {
let f: extern "C" fn(
*const Value,
*const Value,
*const Value,
*const Value,
) -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f(args[0], args[1], args[2], args[3])
}
5 => {
let f: extern "C" fn(
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
) -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f(args[0], args[1], args[2], args[3], args[4])
}
6 => {
let f: extern "C" fn(
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
) -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f(args[0], args[1], args[2], args[3], args[4], args[5])
}
7 => {
let f: extern "C" fn(
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
) -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f(
args[0], args[1], args[2], args[3], args[4], args[5], args[6],
)
}
8 => {
let f: extern "C" fn(
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
*const Value,
) -> *const Value = unsafe { std::mem::transmute(fn_addr) };
f(
args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
)
}
_ => {
eprintln!("[rt] warning: compiled function with {nargs} args, falling back");
rt_const_nil()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_load_global(
ns_ptr: *const u8,
ns_len: u64,
name_ptr: *const u8,
name_len: u64,
) -> *const Value {
let ns = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(ns_ptr, ns_len as usize))
};
let name = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
if let Some((globals, current_ns)) = cljrs_env::callback::capture_eval_context() {
if let Some(val) = globals.lookup_in_ns(ns, name) {
return box_val(val);
}
if let Some(resolved_ns) = globals.resolve_alias(¤t_ns, ns)
&& let Some(val) = globals.lookup_in_ns(&resolved_ns, name)
{
return box_val(val);
}
if ns == current_ns.as_ref()
&& let Some(val) = globals.lookup_in_ns(¤t_ns, name)
{
return box_val(val);
}
}
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_def_var(
ns_ptr: *const u8,
ns_len: u64,
name_ptr: *const u8,
name_len: u64,
val: *const Value,
) -> *const Value {
let ns = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(ns_ptr, ns_len as usize))
};
let name = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
let val = unsafe { val_ref(val) }.clone();
if let Some((globals, _)) = cljrs_env::callback::capture_eval_context() {
let var = globals.intern(ns, Arc::from(name), val);
box_val(Value::Var(var))
} else {
rt_const_nil()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_load_var(
ns_ptr: *const u8,
ns_len: u64,
name_ptr: *const u8,
name_len: u64,
) -> *const Value {
let ns = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(ns_ptr, ns_len as usize))
};
let name = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(name_ptr, name_len as usize))
};
if let Some((globals, current_ns)) = cljrs_env::callback::capture_eval_context() {
if let Some(var) = globals.lookup_var_in_ns(ns, name) {
return box_val(Value::Var(var));
}
if ns == current_ns.as_ref()
&& let Some(var) = globals.lookup_var_in_ns(¤t_ns, name)
{
return box_val(Value::Var(var));
}
}
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_call(
callee: *const Value,
args: *const *const Value,
nargs: u64,
) -> *const Value {
let callee = unsafe { val_ref(callee) };
let nargs = nargs as usize;
let arg_slice = unsafe { std::slice::from_raw_parts(args, nargs) };
let arg_values: Vec<Value> = arg_slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect();
match cljrs_env::callback::invoke(callee, arg_values) {
Ok(result) => box_val(result),
Err(cljrs_value::ValueError::Thrown(val)) => {
PENDING_EXCEPTION.with(|cell| {
*cell.borrow_mut() = Some(box_val(val));
});
rt_const_nil()
}
Err(_e) => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_deref(v: *const Value) -> *const Value {
let v = unsafe { val_ref(v) }.clone();
match cljrs_interp::eval::deref_value(v) {
Ok(result) => box_val(result),
Err(_) => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_println(v: *const Value) -> *const Value {
let v = unsafe { val_ref(v) };
cljrs_builtins::builtins::emit_output_ln(&format!("{}", PrintValue(v)));
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_pr(v: *const Value) -> *const Value {
let v = unsafe { val_ref(v) };
cljrs_builtins::builtins::emit_output(&format!("{v}"));
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_is_nil(v: *const Value) -> *const Value {
box_val(Value::Bool(matches!(unsafe { val_ref(v) }, Value::Nil)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_is_seq(v: *const Value) -> *const Value {
let v = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(
v,
Value::List(_) | Value::Cons(_) | Value::LazySeq(_)
)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_is_vector(v: *const Value) -> *const Value {
box_val(Value::Bool(matches!(
unsafe { val_ref(v) },
Value::Vector(_)
)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_is_map(v: *const Value) -> *const Value {
box_val(Value::Bool(matches!(unsafe { val_ref(v) }, Value::Map(_))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_identical(a: *const Value, b: *const Value) -> *const Value {
box_val(Value::Bool(std::ptr::eq(a, b)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_str(v: *const Value) -> *const Value {
let v = unsafe { val_ref(v) };
let s = format!("{}", PrintValue(v));
box_val(Value::Str(GcPtr::new(s)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_str_n(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
let mut result = String::new();
for v in &args {
if !matches!(v, Value::Nil) {
result.push_str(&format!("{}", PrintValue(v)));
}
}
box_val(Value::Str(GcPtr::new(result)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_println_n(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
let s: String = args
.iter()
.map(|v| format!("{}", PrintValue(v)))
.collect::<Vec<_>>()
.join(" ");
cljrs_builtins::builtins::emit_output_ln(&s);
rt_const_nil()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_with_out_str(body_fn: *const Value) -> *const Value {
let f = unsafe { val_ref(body_fn) }.clone();
cljrs_builtins::builtins::push_output_capture();
let _result = cljrs_env::callback::invoke(&f, vec![]);
let captured = cljrs_builtins::builtins::pop_output_capture().unwrap_or_default();
box_val(Value::Str(GcPtr::new(captured)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_dissoc(m: *const Value, k: *const Value) -> *const Value {
let m = unsafe { val_ref(m) };
let k = unsafe { val_ref(k) };
match m {
Value::Map(map) => box_val(Value::Map(map.dissoc(k))),
_ => box_val(m.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_disj(set: *const Value, val: *const Value) -> *const Value {
let set = unsafe { val_ref(set) };
let val = unsafe { val_ref(val) };
match set {
Value::Set(s) => box_val(Value::Set(s.disj(val))),
_ => box_val(set.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_nth(coll: *const Value, idx: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let idx = unsafe { val_ref(idx) };
let i = match idx {
Value::Long(n) => *n as usize,
_ => return rt_const_nil(),
};
let result = match coll {
Value::Vector(v) => v.get().nth(i).cloned(),
Value::List(l) => l.get().iter().nth(i).cloned(),
Value::Str(s) => s.get().chars().nth(i).map(Value::Char),
_ => None,
};
box_val(result.unwrap_or(Value::Nil))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_contains(coll: *const Value, key: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
let key = unsafe { val_ref(key) };
let result = match coll {
Value::Map(m) => m.contains_key(key),
Value::Set(s) => s.contains(key),
Value::Vector(v) => {
if let Value::Long(i) = key {
let i = *i as usize;
i < v.get().count()
} else {
false
}
}
_ => false,
};
box_val(Value::Bool(result))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_seq(coll: *const Value) -> *const Value {
let coll = unsafe { val_ref(coll) };
match coll {
Value::Nil => rt_const_nil(),
Value::List(l) => {
if l.get().is_empty() {
rt_const_nil()
} else {
box_val(coll.clone())
}
}
Value::Vector(v) => {
if v.get().count() == 0 {
rt_const_nil()
} else {
let items: Vec<Value> = v.get().iter().cloned().collect();
box_val(Value::List(GcPtr::new(PersistentList::from_iter(items))))
}
}
Value::Map(m) => {
if m.count() == 0 {
rt_const_nil()
} else {
let mut pairs = Vec::new();
m.for_each(|k, v| {
pairs.push(Value::Vector(GcPtr::new(PersistentVector::from_iter(
vec![k.clone(), v.clone()],
))));
});
box_val(Value::List(GcPtr::new(PersistentList::from_iter(pairs))))
}
}
Value::Set(s) => {
if s.count() == 0 {
rt_const_nil()
} else {
let items: Vec<Value> = s.iter().cloned().collect();
box_val(Value::List(GcPtr::new(PersistentList::from_iter(items))))
}
}
Value::Str(s) => {
if s.get().is_empty() {
rt_const_nil()
} else {
let chars: Vec<Value> = s.get().chars().map(Value::Char).collect();
box_val(Value::List(GcPtr::new(PersistentList::from_iter(chars))))
}
}
Value::Cons(_) | Value::LazySeq(_) => box_val(coll.clone()),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_lazy_seq(thunk_fn: *const Value) -> *const Value {
use cljrs_value::types::{LazySeq, Thunk};
let f = unsafe { val_ref(thunk_fn) }.clone();
#[derive(Debug)]
struct CompiledThunk(Value);
impl Thunk for CompiledThunk {
fn force(&self) -> Result<Value, String> {
let _root = cljrs_env::gc_roots::root_value(&self.0);
cljrs_env::callback::invoke(&self.0, vec![]).map_err(|e| format!("{e}"))
}
}
impl cljrs_gc::Trace for CompiledThunk {
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.0.trace(visitor);
}
}
unsafe impl Send for CompiledThunk {}
unsafe impl Sync for CompiledThunk {}
box_val(Value::LazySeq(GcPtr::new(LazySeq::new(Box::new(
CompiledThunk(f),
)))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_transient(coll: *const Value) -> *const Value {
use cljrs_value::collections::{TransientMap, TransientSet, TransientVector};
let coll = unsafe { val_ref(coll) };
match coll {
Value::Vector(v) => box_val(Value::TransientVector(GcPtr::new(
TransientVector::new_from_vector(v.get().inner()),
))),
Value::Map(MapValue::Hash(m)) => box_val(Value::TransientMap(GcPtr::new(
TransientMap::new_from_map(m.get().inner()),
))),
Value::Set(SetValue::Hash(s)) => box_val(Value::TransientSet(GcPtr::new(
TransientSet::new_from_set(s.get().inner()),
))),
_ => box_val(coll.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_assoc_bang(
t: *const Value,
k: *const Value,
v: *const Value,
) -> *const Value {
let t = unsafe { val_ref(t) };
let k = unsafe { val_ref(k) }.clone();
let v = unsafe { val_ref(v) }.clone();
match t {
Value::TransientMap(m) => {
let _ = m.get().assoc(k, v);
box_val(t.clone())
}
Value::TransientVector(tv) => {
if let Value::Long(idx) = &k {
let _ = tv.get().set(*idx as usize, v);
}
box_val(t.clone())
}
_ => box_val(t.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_conj_bang(t: *const Value, v: *const Value) -> *const Value {
let t = unsafe { val_ref(t) };
let v = unsafe { val_ref(v) }.clone();
match t {
Value::TransientVector(tv) => {
let _ = tv.get().append(v);
box_val(t.clone())
}
Value::TransientMap(m) => {
if let Value::Vector(pair) = &v
&& pair.get().count() == 2
{
let k = pair.get().nth(0).cloned().unwrap_or(Value::Nil);
let val = pair.get().nth(1).cloned().unwrap_or(Value::Nil);
let _ = m.get().assoc(k, val);
}
box_val(t.clone())
}
Value::TransientSet(s) => {
let _ = s.get().conj(v);
box_val(t.clone())
}
_ => box_val(t.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_persistent_bang(t: *const Value) -> *const Value {
let t = unsafe { val_ref(t) };
match t {
Value::TransientVector(tv) => match tv.get().persistent() {
Ok(v) => box_val(Value::Vector(GcPtr::new(v))),
Err(_) => rt_const_nil(),
},
Value::TransientMap(m) => match m.get().persistent() {
Ok(m) => box_val(Value::Map(MapValue::Hash(GcPtr::new(m)))),
Err(_) => rt_const_nil(),
},
Value::TransientSet(s) => match s.get().persistent() {
Ok(s) => box_val(Value::Set(SetValue::Hash(GcPtr::new(s)))),
Err(_) => rt_const_nil(),
},
_ => box_val(t.clone()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_atom_reset(atom: *const Value, val: *const Value) -> *const Value {
let atom = unsafe { val_ref(atom) };
let val = unsafe { val_ref(val) }.clone();
match atom {
Value::Atom(a) => box_val(a.get().reset(val)),
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_atom_swap(
atom: *const Value,
f: *const Value,
extra_args: *const *const Value,
nextra: u64,
) -> *const Value {
let atom_val = unsafe { val_ref(atom) };
let f = unsafe { val_ref(f) }.clone();
let nextra = nextra as usize;
let extra: Vec<Value> = if nextra > 0 {
let slice = unsafe { std::slice::from_raw_parts(extra_args, nextra) };
slice
.iter()
.map(|p| unsafe { val_ref(*p) }.clone())
.collect()
} else {
vec![]
};
match atom_val {
Value::Atom(a) => {
loop {
let current = a.get().value.lock().unwrap().clone();
let mut args = vec![current.clone()];
args.extend(extra.iter().cloned());
match cljrs_env::callback::invoke(&f, args) {
Ok(new_val) => {
let mut guard = a.get().value.lock().unwrap();
if *guard == current {
*guard = new_val.clone();
return box_val(new_val);
}
}
Err(_) => return rt_const_nil(),
}
}
}
_ => rt_const_nil(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_apply(f: *const Value, arglist: *const Value) -> *const Value {
let f = unsafe { val_ref(f) }.clone();
let arglist = unsafe { val_ref(arglist) };
let mut args = Vec::new();
let mut current = arglist.clone();
loop {
match ¤t {
Value::Nil => break,
Value::List(l) => {
for item in l.get().iter() {
args.push(item.clone());
}
break;
}
Value::Vector(v) => {
for item in v.get().iter() {
args.push(item.clone());
}
break;
}
Value::Cons(c) => {
args.push(c.get().head.clone());
current = c.get().tail.clone();
}
Value::LazySeq(ls) => {
current = ls.get().realize();
}
_ => break,
}
}
match cljrs_env::callback::invoke(&f, args) {
Ok(result) => box_val(result),
Err(cljrs_value::ValueError::Thrown(val)) => {
PENDING_EXCEPTION.with(|cell| {
*cell.borrow_mut() = Some(box_val(val));
});
rt_const_nil()
}
Err(_) => rt_const_nil(),
}
}
fn call_global_fn(ns: &str, name: &str, args: Vec<Value>) -> *const Value {
if let Some((globals, _)) = cljrs_env::callback::capture_eval_context()
&& let Some(val) = globals.lookup_in_ns(ns, name)
{
match cljrs_env::callback::invoke(&val, args) {
Ok(result) => box_val(result),
Err(cljrs_value::ValueError::Thrown(v)) => {
PENDING_EXCEPTION.with(|cell| {
*cell.borrow_mut() = Some(box_val(v));
});
rt_const_nil()
}
Err(_) => rt_const_nil(),
}
} else {
rt_const_nil()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_reduce2(f: *const Value, coll: *const Value) -> *const Value {
let f = unsafe { val_ref(f) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "reduce", vec![f, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_reduce3(
f: *const Value,
init: *const Value,
coll: *const Value,
) -> *const Value {
let f = unsafe { val_ref(f) }.clone();
let init = unsafe { val_ref(init) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "reduce", vec![f, init, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_map(f: *const Value, coll: *const Value) -> *const Value {
let f = unsafe { val_ref(f) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "map", vec![f, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_filter(pred: *const Value, coll: *const Value) -> *const Value {
let pred = unsafe { val_ref(pred) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "filter", vec![pred, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_mapv(f: *const Value, coll: *const Value) -> *const Value {
let f = unsafe { val_ref(f) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "mapv", vec![f, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_filterv(pred: *const Value, coll: *const Value) -> *const Value {
let pred = unsafe { val_ref(pred) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "filterv", vec![pred, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_some(pred: *const Value, coll: *const Value) -> *const Value {
let pred = unsafe { val_ref(pred) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "some", vec![pred, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_every(pred: *const Value, coll: *const Value) -> *const Value {
let pred = unsafe { val_ref(pred) }.clone();
let coll = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "every?", vec![pred, coll])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_into(to: *const Value, from: *const Value) -> *const Value {
let to = unsafe { val_ref(to) }.clone();
let from = unsafe { val_ref(from) }.clone();
call_global_fn("clojure.core", "into", vec![to, from])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_into3(
to: *const Value,
xform: *const Value,
from: *const Value,
) -> *const Value {
let to = unsafe { val_ref(to) }.clone();
let xform = unsafe { val_ref(xform) }.clone();
let from = unsafe { val_ref(from) }.clone();
call_global_fn("clojure.core", "into", vec![to, xform, from])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_set_bang(var_ptr: *const Value, val_ptr: *const Value) -> *const Value {
let var_val = unsafe { val_ref(var_ptr) };
let val = unsafe { val_ref(val_ptr) }.clone();
match var_val {
Value::Var(var) => {
if !cljrs_env::dynamics::set_thread_local(var, val.clone()) {
var.get().bind(val.clone());
}
box_val(val)
}
_ => box_val(val),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_with_bindings(
bindings: *const *const Value,
npairs: u64,
body_fn: *const Value,
) -> *const Value {
use std::collections::HashMap;
let npairs = npairs as usize;
let binding_slice = unsafe { std::slice::from_raw_parts(bindings, npairs * 2) };
let mut frame: HashMap<usize, Value> = HashMap::new();
for i in 0..npairs {
let var_val = unsafe { val_ref(binding_slice[i * 2]) };
let val = unsafe { val_ref(binding_slice[i * 2 + 1]) }.clone();
if let Value::Var(var) = var_val {
frame.insert(cljrs_env::dynamics::var_key_of(var), val);
}
}
let _guard = cljrs_env::dynamics::push_frame(frame);
let body = unsafe { val_ref(body_fn) }.clone();
match cljrs_env::callback::invoke(&body, vec![]) {
Ok(result) => box_val(result),
Err(cljrs_value::ValueError::Thrown(val)) => {
PENDING_EXCEPTION.with(|cell| {
*cell.borrow_mut() = Some(box_val(val));
});
rt_const_nil()
}
Err(_) => rt_const_nil(),
}
}
thread_local! {
static PENDING_EXCEPTION: std::cell::RefCell<Option<*const Value>> = const { std::cell::RefCell::new(None) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_throw(val: *const Value) -> *const Value {
PENDING_EXCEPTION.with(|cell| {
*cell.borrow_mut() = Some(val);
});
rt_const_nil()
}
fn take_pending_exception() -> Option<*const Value> {
PENDING_EXCEPTION.with(|cell| cell.borrow_mut().take())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_try(
body_fn: *const Value,
catch_fn: *const Value,
finally_fn: *const Value,
) -> *const Value {
let body = unsafe { val_ref(body_fn) }.clone();
let catch = unsafe { val_ref(catch_fn) }.clone();
let finally = unsafe { val_ref(finally_fn) }.clone();
let region_depth = cljrs_gc::region::region_stack_depth();
let body_result = cljrs_env::callback::invoke(&body, vec![]);
let ret = if let Some(thrown_ptr) = take_pending_exception() {
unwind_regions_to(region_depth);
if !matches!(catch, Value::Nil) {
let thrown_val = unsafe { val_ref(thrown_ptr) }.clone();
match cljrs_env::callback::invoke(&catch, vec![thrown_val]) {
Ok(v) => box_val(v),
Err(_) => rt_const_nil(),
}
} else {
rt_const_nil()
}
} else {
match body_result {
Ok(val) => box_val(val),
Err(val_err) => {
unwind_regions_to(region_depth);
if !matches!(catch, Value::Nil) {
let thrown_val = match val_err {
cljrs_value::ValueError::Thrown(v) => v,
other => Value::Str(GcPtr::new(other.to_string())),
};
match cljrs_env::callback::invoke(&catch, vec![thrown_val]) {
Ok(v) => box_val(v),
Err(_) => rt_const_nil(),
}
} else {
rt_const_nil()
}
}
}
};
if !matches!(finally, Value::Nil) {
let _ = cljrs_env::callback::invoke(&finally, vec![]);
}
ret
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_group_by(f: *const Value, coll: *const Value) -> *const Value {
let fv = unsafe { val_ref(f) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "group-by", vec![fv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_partition2(n: *const Value, coll: *const Value) -> *const Value {
let nv = unsafe { val_ref(n) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "partition", vec![nv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_partition3(
n: *const Value,
step: *const Value,
coll: *const Value,
) -> *const Value {
let nv = unsafe { val_ref(n) }.clone();
let sv = unsafe { val_ref(step) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "partition", vec![nv, sv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_partition4(
n: *const Value,
step: *const Value,
pad: *const Value,
coll: *const Value,
) -> *const Value {
let nv = unsafe { val_ref(n) }.clone();
let sv = unsafe { val_ref(step) }.clone();
let pv = unsafe { val_ref(pad) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "partition", vec![nv, sv, pv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_frequencies(coll: *const Value) -> *const Value {
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "frequencies", vec![cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_keep(f: *const Value, coll: *const Value) -> *const Value {
let fv = unsafe { val_ref(f) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "keep", vec![fv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_remove(pred: *const Value, coll: *const Value) -> *const Value {
let pv = unsafe { val_ref(pred) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "remove", vec![pv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_map_indexed(f: *const Value, coll: *const Value) -> *const Value {
let fv = unsafe { val_ref(f) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "map-indexed", vec![fv, cv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_zipmap(keys: *const Value, vals: *const Value) -> *const Value {
let kv = unsafe { val_ref(keys) }.clone();
let vv = unsafe { val_ref(vals) }.clone();
call_global_fn("clojure.core", "zipmap", vec![kv, vv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_juxt(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
call_global_fn("clojure.core", "juxt", args)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_comp(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
call_global_fn("clojure.core", "comp", args)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_partial(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
call_global_fn("clojure.core", "partial", args)
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_complement(f: *const Value) -> *const Value {
let fv = unsafe { val_ref(f) }.clone();
call_global_fn("clojure.core", "complement", vec![fv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_concat(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
call_global_fn("clojure.core", "concat", args)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_range1(end: *const Value) -> *const Value {
let e = unsafe { val_ref(end) }.clone();
call_global_fn("clojure.core", "range", vec![e])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_range2(start: *const Value, end: *const Value) -> *const Value {
let s = unsafe { val_ref(start) }.clone();
let e = unsafe { val_ref(end) }.clone();
call_global_fn("clojure.core", "range", vec![s, e])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_range3(
start: *const Value,
end: *const Value,
step: *const Value,
) -> *const Value {
let s = unsafe { val_ref(start) }.clone();
let e = unsafe { val_ref(end) }.clone();
let st = unsafe { val_ref(step) }.clone();
call_global_fn("clojure.core", "range", vec![s, e, st])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_take(n: *const Value, coll: *const Value) -> *const Value {
let nv = unsafe { val_ref(n) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "take", vec![nv, cv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_drop(n: *const Value, coll: *const Value) -> *const Value {
let nv = unsafe { val_ref(n) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "drop", vec![nv, cv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_reverse(coll: *const Value) -> *const Value {
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "reverse", vec![cv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_sort(coll: *const Value) -> *const Value {
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "sort", vec![cv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_sort_by(keyfn: *const Value, coll: *const Value) -> *const Value {
let kf = unsafe { val_ref(keyfn) }.clone();
let cv = unsafe { val_ref(coll) }.clone();
call_global_fn("clojure.core", "sort-by", vec![kf, cv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_keys(m: *const Value) -> *const Value {
let mv = unsafe { val_ref(m) }.clone();
call_global_fn("clojure.core", "keys", vec![mv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_vals(m: *const Value) -> *const Value {
let mv = unsafe { val_ref(m) }.clone();
call_global_fn("clojure.core", "vals", vec![mv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_merge(elems: *const *const Value, n: i64) -> *const Value {
let args = unsafe { collect_args(elems, n) };
call_global_fn("clojure.core", "merge", args)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_update(
m: *const Value,
k: *const Value,
f: *const Value,
) -> *const Value {
let mv = unsafe { val_ref(m) }.clone();
let kv = unsafe { val_ref(k) }.clone();
let fv = unsafe { val_ref(f) }.clone();
call_global_fn("clojure.core", "update", vec![mv, kv, fv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_get_in(m: *const Value, ks: *const Value) -> *const Value {
let mv = unsafe { val_ref(m) }.clone();
let kv = unsafe { val_ref(ks) }.clone();
call_global_fn("clojure.core", "get-in", vec![mv, kv])
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rt_assoc_in(
m: *const Value,
ks: *const Value,
v: *const Value,
) -> *const Value {
let mv = unsafe { val_ref(m) }.clone();
let kv = unsafe { val_ref(ks) }.clone();
let vv = unsafe { val_ref(v) }.clone();
call_global_fn("clojure.core", "assoc-in", vec![mv, kv, vv])
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_number(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(
val,
Value::Long(_) | Value::Double(_)
)))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_string(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(val, Value::Str(_))))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_keyword(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(val, Value::Keyword(_))))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_symbol(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(val, Value::Symbol(_))))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_bool(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(val, Value::Bool(_))))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_is_int(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
box_val(Value::Bool(matches!(val, Value::Long(_))))
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_prn(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
cljrs_builtins::builtins::emit_output_ln(&format!("{val}"));
box_val(Value::Nil)
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_print(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) };
cljrs_builtins::builtins::emit_output(&format!("{}", PrintValue(val)));
box_val(Value::Nil)
}
#[unsafe(no_mangle)]
pub extern "C" fn rt_atom(v: *const Value) -> *const Value {
let val = unsafe { val_ref(v) }.clone();
call_global_fn("clojure.core", "atom", vec![val])
}
#[inline(never)]
pub fn anchor_rt_symbols() {
std::hint::black_box(rt_const_nil as *const () as usize);
std::hint::black_box(rt_const_true as *const () as usize);
std::hint::black_box(rt_const_false as *const () as usize);
std::hint::black_box(rt_const_long as *const () as usize);
std::hint::black_box(rt_const_double as *const () as usize);
std::hint::black_box(rt_const_char as *const () as usize);
std::hint::black_box(rt_const_string as *const () as usize);
std::hint::black_box(rt_const_keyword as *const () as usize);
std::hint::black_box(rt_const_symbol as *const () as usize);
std::hint::black_box(rt_truthiness as *const () as usize);
std::hint::black_box(rt_add as *const () as usize);
std::hint::black_box(rt_sub as *const () as usize);
std::hint::black_box(rt_mul as *const () as usize);
std::hint::black_box(rt_div as *const () as usize);
std::hint::black_box(rt_rem as *const () as usize);
std::hint::black_box(rt_eq as *const () as usize);
std::hint::black_box(rt_lt as *const () as usize);
std::hint::black_box(rt_gt as *const () as usize);
std::hint::black_box(rt_lte as *const () as usize);
std::hint::black_box(rt_gte as *const () as usize);
std::hint::black_box(rt_alloc_vector as *const () as usize);
std::hint::black_box(rt_alloc_map as *const () as usize);
std::hint::black_box(rt_alloc_set as *const () as usize);
std::hint::black_box(rt_alloc_list as *const () as usize);
std::hint::black_box(rt_alloc_cons as *const () as usize);
std::hint::black_box(rt_get as *const () as usize);
std::hint::black_box(rt_count as *const () as usize);
std::hint::black_box(rt_first as *const () as usize);
std::hint::black_box(rt_rest as *const () as usize);
std::hint::black_box(rt_assoc as *const () as usize);
std::hint::black_box(rt_conj as *const () as usize);
std::hint::black_box(rt_call as *const () as usize);
std::hint::black_box(rt_deref as *const () as usize);
std::hint::black_box(rt_load_global as *const () as usize);
std::hint::black_box(rt_def_var as *const () as usize);
std::hint::black_box(rt_println as *const () as usize);
std::hint::black_box(rt_pr as *const () as usize);
std::hint::black_box(rt_is_nil as *const () as usize);
std::hint::black_box(rt_is_vector as *const () as usize);
std::hint::black_box(rt_is_map as *const () as usize);
std::hint::black_box(rt_is_seq as *const () as usize);
std::hint::black_box(rt_identical as *const () as usize);
std::hint::black_box(rt_str as *const () as usize);
std::hint::black_box(rt_str_n as *const () as usize);
std::hint::black_box(rt_println_n as *const () as usize);
std::hint::black_box(rt_with_out_str as *const () as usize);
std::hint::black_box(rt_make_fn as *const () as usize);
std::hint::black_box(rt_make_fn_variadic as *const () as usize);
std::hint::black_box(rt_make_fn_multi as *const () as usize);
std::hint::black_box(rt_throw as *const () as usize);
std::hint::black_box(rt_try as *const () as usize);
std::hint::black_box(rt_dissoc as *const () as usize);
std::hint::black_box(rt_disj as *const () as usize);
std::hint::black_box(rt_nth as *const () as usize);
std::hint::black_box(rt_contains as *const () as usize);
std::hint::black_box(rt_seq as *const () as usize);
std::hint::black_box(rt_lazy_seq as *const () as usize);
std::hint::black_box(rt_transient as *const () as usize);
std::hint::black_box(rt_assoc_bang as *const () as usize);
std::hint::black_box(rt_conj_bang as *const () as usize);
std::hint::black_box(rt_persistent_bang as *const () as usize);
std::hint::black_box(rt_atom_reset as *const () as usize);
std::hint::black_box(rt_atom_swap as *const () as usize);
std::hint::black_box(rt_apply as *const () as usize);
std::hint::black_box(rt_reduce2 as *const () as usize);
std::hint::black_box(rt_reduce3 as *const () as usize);
std::hint::black_box(rt_map as *const () as usize);
std::hint::black_box(rt_filter as *const () as usize);
std::hint::black_box(rt_mapv as *const () as usize);
std::hint::black_box(rt_filterv as *const () as usize);
std::hint::black_box(rt_some as *const () as usize);
std::hint::black_box(rt_every as *const () as usize);
std::hint::black_box(rt_into as *const () as usize);
std::hint::black_box(rt_into3 as *const () as usize);
std::hint::black_box(rt_set_bang as *const () as usize);
std::hint::black_box(rt_with_bindings as *const () as usize);
std::hint::black_box(rt_load_var as *const () as usize);
std::hint::black_box(rt_concat as *const () as usize);
std::hint::black_box(rt_range1 as *const () as usize);
std::hint::black_box(rt_range2 as *const () as usize);
std::hint::black_box(rt_range3 as *const () as usize);
std::hint::black_box(rt_take as *const () as usize);
std::hint::black_box(rt_drop as *const () as usize);
std::hint::black_box(rt_reverse as *const () as usize);
std::hint::black_box(rt_sort as *const () as usize);
std::hint::black_box(rt_sort_by as *const () as usize);
std::hint::black_box(rt_keys as *const () as usize);
std::hint::black_box(rt_vals as *const () as usize);
std::hint::black_box(rt_merge as *const () as usize);
std::hint::black_box(rt_update as *const () as usize);
std::hint::black_box(rt_get_in as *const () as usize);
std::hint::black_box(rt_assoc_in as *const () as usize);
std::hint::black_box(rt_is_number as *const () as usize);
std::hint::black_box(rt_is_string as *const () as usize);
std::hint::black_box(rt_is_keyword as *const () as usize);
std::hint::black_box(rt_is_symbol as *const () as usize);
std::hint::black_box(rt_is_bool as *const () as usize);
std::hint::black_box(rt_is_int as *const () as usize);
std::hint::black_box(rt_prn as *const () as usize);
std::hint::black_box(rt_print as *const () as usize);
std::hint::black_box(rt_atom as *const () as usize);
std::hint::black_box(rt_group_by as *const () as usize);
std::hint::black_box(rt_partition2 as *const () as usize);
std::hint::black_box(rt_partition3 as *const () as usize);
std::hint::black_box(rt_partition4 as *const () as usize);
std::hint::black_box(rt_frequencies as *const () as usize);
std::hint::black_box(rt_keep as *const () as usize);
std::hint::black_box(rt_remove as *const () as usize);
std::hint::black_box(rt_map_indexed as *const () as usize);
std::hint::black_box(rt_zipmap as *const () as usize);
std::hint::black_box(rt_juxt as *const () as usize);
std::hint::black_box(rt_comp as *const () as usize);
std::hint::black_box(rt_partial as *const () as usize);
std::hint::black_box(rt_complement as *const () as usize);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_const_nil() {
let p = rt_const_nil();
assert!(!p.is_null());
assert!(matches!(unsafe { &*p }, Value::Nil));
}
#[test]
fn test_const_long() {
let p = rt_const_long(42);
assert!(matches!(unsafe { &*p }, Value::Long(42)));
}
#[test]
fn test_truthiness() {
assert_eq!(unsafe { rt_truthiness(rt_const_nil()) }, 0);
assert_eq!(unsafe { rt_truthiness(rt_const_false()) }, 0);
assert_eq!(unsafe { rt_truthiness(rt_const_true()) }, 1);
assert_eq!(unsafe { rt_truthiness(rt_const_long(0)) }, 1);
}
#[test]
fn test_add_longs() {
let a = rt_const_long(10);
let b = rt_const_long(32);
let result = unsafe { rt_add(a, b) };
assert!(matches!(unsafe { &*result }, Value::Long(42)));
}
#[test]
fn test_eq() {
let a = rt_const_long(5);
let b = rt_const_long(5);
let result = unsafe { rt_eq(a, b) };
assert!(matches!(unsafe { &*result }, Value::Bool(true)));
let c = rt_const_long(6);
let result2 = unsafe { rt_eq(a, c) };
assert!(matches!(unsafe { &*result2 }, Value::Bool(false)));
}
#[test]
fn test_alloc_vector() {
let elems = [rt_const_long(1), rt_const_long(2), rt_const_long(3)];
let v = unsafe { rt_alloc_vector(elems.as_ptr(), 3) };
let val = unsafe { val_ref(v) };
if let Value::Vector(vec) = val {
assert_eq!(vec.get().count(), 3);
} else {
panic!("expected vector");
}
}
#[test]
fn test_count() {
let elems = [rt_const_long(1), rt_const_long(2)];
let v = unsafe { rt_alloc_vector(elems.as_ptr(), 2) };
let n = unsafe { rt_count(v) };
assert!(matches!(unsafe { &*n }, Value::Long(2)));
}
}