use std::{
cell::{Cell, RefCell},
panic::{AssertUnwindSafe, catch_unwind},
ptr,
rc::Rc,
};
use crate::{Lisp, LispContext, LispError, Value, ValueRef, sys};
pub(crate) type Callback = Rc<
dyn for<'rt, 'call> Fn(LispContext<'rt>, &[ValueRef<'call>]) -> Result<Value<'rt>, LispError>,
>;
#[derive(Default)]
pub(crate) struct CallbackRegistry {
callbacks: Vec<Option<Callback>>,
}
impl CallbackRegistry {
pub fn insert(&mut self, callback: Callback) -> usize {
let id = self.callbacks.len();
self.callbacks.push(Some(callback));
id
}
pub fn get(&self, id: usize) -> Option<Callback> {
self.callbacks.get(id)?.clone()
}
pub fn remove(&mut self, id: usize) {
if let Some(slot) = self.callbacks.get_mut(id) {
*slot = None;
}
}
}
thread_local! {
static ACTIVE: Cell<*const Lisp> = const {
Cell::new(ptr::null())
};
static NATIVE_ROOTS: RefCell<Vec<*mut sys::eclisp_root>> = const {
RefCell::new(Vec::new())
};
}
pub(crate) fn enter<'rt, R>(runtime: &'rt Lisp, f: impl FnOnce(LispContext<'rt>) -> R) -> R {
ACTIVE.with(|active| {
let previous = active.replace(runtime as *const Lisp);
let root_start = NATIVE_ROOTS.with(|roots| roots.borrow().len());
struct Guard<'a> {
active: &'a Cell<*const Lisp>,
previous: *const Lisp,
root_start: usize,
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
let stale =
NATIVE_ROOTS.with(|roots| roots.borrow_mut().split_off(self.root_start));
for root in stale {
unsafe { sys::eclisp_root_free(root) };
}
self.active.set(self.previous);
}
}
let _guard = Guard {
active,
previous,
root_start,
};
f(LispContext { runtime })
})
}
pub(crate) fn retain_native_result(value: Value<'_>) -> sys::cl_object {
let raw = value.as_raw();
let root = value.into_raw_root();
NATIVE_ROOTS.with(|roots| roots.borrow_mut().push(root));
raw
}
fn write_error(buffer: *mut u8, capacity: usize, message: &str) {
if buffer.is_null() || capacity == 0 {
return;
}
let bytes = message.as_bytes();
let length = bytes.len().min(capacity - 1);
unsafe {
let output = std::slice::from_raw_parts_mut(buffer, capacity);
for (dst, src) in output[..length].iter_mut().zip(&bytes[..length]) {
*dst = if *src == 0 { b'?' } else { *src };
}
output[length] = 0;
}
}
fn invoke(id: usize, arguments: sys::cl_object) -> Result<*mut sys::eclisp_root, LispError> {
ACTIVE.with(|active| {
let runtime = active.get();
if runtime.is_null() {
return Err(LispError::RuntimeError("No active Lisp context".into()));
}
let runtime = unsafe { &*runtime };
let callback = runtime
.callbacks
.borrow()
.get(id)
.ok_or_else(|| LispError::RuntimeError(format!("Unknown callback ID: {id}")))?;
let mut length = 0usize;
let success = unsafe { sys::eclisp_args_length(arguments, &mut length) };
if success == 0 {
return Err(LispError::RuntimeError(
"Invalid callback argument list".into(),
));
}
let mut args = Vec::new();
args.try_reserve_exact(length)
.map_err(|e| LispError::RuntimeError(e.to_string()))?;
for index in 0..length {
let mut object = ptr::null_mut();
let success = unsafe { sys::eclisp_args_get(arguments, index, &mut object) };
if success == 0 {
return Err(LispError::RuntimeError(
"Failed to read callback argument".into(),
));
}
args.push(unsafe { ValueRef::from_raw(object) });
}
let ctx = LispContext { runtime };
let result = callback(ctx, &args)?;
Ok(result.into_raw_root())
})
}
pub(crate) fn with_active_context<R>(f: impl FnOnce(&LispContext<'_>) -> R) -> Option<R> {
ACTIVE.with(|active| {
let runtime = active.get();
if runtime.is_null() {
return None;
}
let runtime = unsafe { &*runtime };
let ctx = LispContext { runtime };
Some(f(&ctx))
})
}
pub(crate) unsafe extern "C" fn dispatch(
id: usize,
arguments: sys::cl_object,
out: *mut *mut sys::eclisp_root,
error: *mut std::ffi::c_char,
error_capacity: usize,
) -> i32 {
if out.is_null() {
write_error(error.cast(), error_capacity, "Missing result pointer");
return 1;
}
unsafe {
*out = std::ptr::null_mut();
}
let result = catch_unwind(AssertUnwindSafe(|| invoke(id, arguments)));
match result {
Ok(Ok(root)) => {
unsafe {
*out = root;
}
0
}
Ok(Err(err)) => {
write_error(error.cast(), error_capacity, &err.to_string());
1
}
Err(_) => {
write_error(error.cast(), error_capacity, "Rust callback panicked");
1
}
}
}