use std::{
cell::RefCell,
ffi::{CString, c_char},
marker::PhantomData,
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{LispContext, ListError, Value, 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<'rt, R>(&'rt self, f: impl FnOnce(LispContext<'rt>) -> R) -> R {
callback::enter(self, f)
}
pub fn run<'rt>(&'rt self, code: impl AsRef<str>) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.run(code.as_ref()))
}
pub fn call<'rt>(
&'rt self,
function: &str,
args: &[&Value<'_>],
) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| {
let fun = ctx.fun(function)?;
ctx.callfun(&fun, args)
})
}
pub fn fun<'rt>(&'rt self, name: &str) -> Result<crate::Function<'rt>, LispError> {
self.enter(|ctx| ctx.fun(name))
}
pub fn callfun<'rt>(
&'rt self,
function: &crate::Function<'_>,
args: &[&Value<'_>],
) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.callfun(function, args))
}
pub fn nil<'rt>(&'rt self) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.nil())
}
pub fn t<'rt>(&'rt self) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.t())
}
pub fn int<'rt>(&'rt self, value: i64) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.int(value))
}
pub fn float<'rt>(&'rt self, value: f64) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.float(value))
}
pub fn cons<'rt>(&'rt self, car: &Value<'_>, cdr: &Value<'_>) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.cons(car, cdr))
}
pub fn list<'rt>(&'rt self, values: &[&Value<'_>]) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.list(values))
}
pub fn string<'rt>(&'rt self, contents: impl AsRef<str>) -> Result<Value<'rt>, LispError> {
self.enter(|ctx| ctx.string(contents))
}
}
impl Drop for Lisp {
fn drop(&mut self) {
unsafe { sys::cl_shutdown() };
WAS_SHUTDOWN.store(true, Ordering::Release);
}
}