use std::{
any::Any,
cell::RefCell,
panic::{AssertUnwindSafe, catch_unwind},
};
use crate::{FromLisp, IntoLisp, LispContext, LispError, ValueRef, callback, sys};
thread_local! {
static ERROR: RefCell<Option<String>> = const {
RefCell::new(None)
};
}
fn clear_error() {
ERROR.with(|error| error.borrow_mut().take());
}
fn set_error(message: String) {
ERROR.with(|error| *error.borrow_mut() = Some(message));
}
fn panic_message(payload: Box<dyn Any + Send>) -> String {
if let Some(message) = payload.downcast_ref::<&str>() {
(*message).to_string()
} else if let Some(message) = payload.downcast_ref::<String>() {
message.clone()
} else {
"Native callback panicked".into()
}
}
#[doc(hidden)]
pub fn invoke(
f: impl FnOnce(&LispContext<'_>) -> Result<sys::cl_object, LispError>,
) -> sys::cl_object {
clear_error();
let result = catch_unwind(AssertUnwindSafe(|| callback::with_active_context(f)));
match result {
Ok(Some(Ok(raw))) => raw,
Ok(Some(Err(error))) => {
set_error(error.to_string());
unsafe { sys::eclisp_native_failure_raw() }
}
Ok(None) => {
set_error("No active Lisp context".into());
unsafe { sys::eclisp_native_failure_raw() }
}
Err(payload) => {
set_error(panic_message(payload));
unsafe { sys::eclisp_native_failure_raw() }
}
}
}
pub(crate) unsafe extern "C" fn take_error() -> sys::cl_object {
let message = ERROR.with(|error| error.borrow_mut().take());
let Some(message) = message else {
return unsafe { sys::eclisp_nil_raw() };
};
match callback::with_active_context(|ctx| {
ctx.string(message).map(callback::retain_native_result)
}) {
Some(Ok(raw)) => raw,
Some(Err(_)) | None => unsafe { sys::eclisp_native_failure_raw() },
}
}
#[doc(hidden)]
pub unsafe fn from_native<T: FromLisp>(raw: sys::cl_object) -> Result<T, LispError> {
let value = unsafe { ValueRef::from_raw(raw) };
T::from_lisp(value)
}
#[doc(hidden)]
pub fn to_native<T: IntoLisp>(
ctx: &LispContext<'_>,
value: T,
) -> Result<sys::cl_object, LispError> {
let value = value.into_lisp(ctx)?;
Ok(callback::retain_native_result(value))
}