use std::{
cell::RefCell,
ffi::{CString, c_char},
marker::PhantomData,
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{LispContext, ListError, ValueError, callback, sys};
static BOOTED: AtomicBool = AtomicBool::new(false);
static WAS_SHUTDOWN: AtomicBool = AtomicBool::new(false);
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum LispError {
#[error("Runtime error: {0}")]
RuntimeError(String),
#[error("Runtime is already running")]
RuntimeAlreadyRunning,
#[error("ECL cannot be restarted in this process")]
RuntimeStopped,
#[error("Invalid symbol: {0}")]
InvalidSymbol(String),
#[error("Value error: {0}")]
ValueError(#[from] ValueError),
#[error("List error: {0}")]
ListError(#[from] ListError),
}
pub struct Lisp {
pub(crate) callbacks: RefCell<callback::CallbackRegistry>,
_program: CString,
_argv: Box<[*mut c_char; 1]>,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Lisp {
pub fn new() -> Result<Self, LispError> {
if WAS_SHUTDOWN.load(Ordering::Acquire) {
return Err(LispError::RuntimeStopped);
}
if BOOTED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return Err(LispError::RuntimeAlreadyRunning);
}
let program = CString::new("eclisp").unwrap();
let mut argv = Box::new([program.as_ptr().cast_mut()]);
let status = unsafe { sys::cl_boot(1, argv.as_mut_ptr()) };
if status != 1 {
WAS_SHUTDOWN.store(true, Ordering::Release);
return Err(LispError::RuntimeError(format!(
"Failed to boot ECL: {status}"
)));
}
let status = unsafe { sys::eclisp_register_dispatcher(Some(callback::dispatch)) };
if status != 0 {
unsafe {
sys::cl_shutdown();
}
WAS_SHUTDOWN.store(true, Ordering::Release);
return Err(LispError::RuntimeError(format!(
"Failed to register Rust dispatcher: {status}"
)));
}
Ok(Self {
callbacks: RefCell::new(callback::CallbackRegistry::default()),
_program: program,
_argv: argv,
_not_send_sync: PhantomData,
})
}
pub fn enter<R>(&self, f: impl FnOnce(LispContext<'_>) -> R) -> R {
callback::enter(self, f)
}
}
impl Drop for Lisp {
fn drop(&mut self) {
unsafe { sys::cl_shutdown() };
WAS_SHUTDOWN.store(true, Ordering::Release);
}
}