use std::{
ffi::{CStr, CString, c_char},
marker::PhantomData,
ptr::{self, NonNull},
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{ListError, Value, sys, value::ValueError};
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("Value error: {0}")]
ValueError(#[from] ValueError),
#[error("List error: {0}")]
ListError(#[from] ListError),
}
#[derive(Debug, PartialEq)]
pub struct Lisp {
_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}"
)));
}
Ok(Self {
_program: program,
_argv: argv,
_not_send_sync: PhantomData,
})
}
pub fn eval(&self, code: impl AsRef<str>) -> Result<Value<'_>, LispError> {
let source =
CString::new(code.as_ref()).map_err(|e| LispError::RuntimeError(e.to_string()))?;
let mut root = ptr::null_mut();
let mut error = ptr::null_mut();
let status = unsafe { sys::eclisp_eval(source.as_ptr(), &mut root, &mut error) };
if status != 0 {
let message = if error.is_null() {
format!("ECL evaluation failed: {status}")
} else {
let message = unsafe { CStr::from_ptr(error).to_string_lossy().into_owned() };
unsafe {
sys::eclisp_string_free(error);
}
message
};
return Err(LispError::RuntimeError(message));
}
let root = NonNull::new(root)
.ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
Ok(unsafe { Value::from_root(self, root) })
}
pub fn call(&self, function: &str, args: &[&Value<'_>]) -> Result<Value<'_>, LispError> {
let function =
CString::new(function).map_err(|e| LispError::RuntimeError(e.to_string()))?;
if args.iter().any(|value| !value.belongs_to(self)) {
return Err(LispError::RuntimeError(
"Argument belongs to another Lisp runtime".into(),
));
}
let raw_args: Vec<sys::cl_object> = args.iter().map(|value| value.as_raw()).collect();
let mut root = ptr::null_mut();
let mut error = ptr::null_mut();
let status = unsafe {
sys::eclisp_call(
function.as_ptr(),
raw_args.len(),
raw_args.as_ptr(),
&mut root,
&mut error,
)
};
if status != 0 {
let message = if !error.is_null() {
let message = unsafe { CStr::from_ptr(error).to_string_lossy().into_owned() };
unsafe { sys::eclisp_string_free(error) };
message
} else if status == 3 {
format!("Unknown function: {}", function.to_string_lossy())
} else {
format!("Lisp function call failed: {status}")
};
return Err(LispError::RuntimeError(message));
}
let root = NonNull::new(root)
.ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
Ok(unsafe { Value::from_root(self, root) })
}
pub fn int(&self, value: i64) -> Result<Value<'_>, LispError> {
let mut root = ptr::null_mut();
let status = unsafe { sys::eclisp_int_new(value, &mut root) };
self.value_from_result(status, root)
}
pub fn float(&self, value: f64) -> Result<Value<'_>, LispError> {
let mut root = ptr::null_mut();
let status = unsafe { sys::eclisp_float_new(value, &mut root) };
self.value_from_result(status, root)
}
pub fn cons<'rt>(
&'rt self,
car: &Value<'rt>,
cdr: &Value<'rt>,
) -> Result<Value<'rt>, LispError> {
if !car.belongs_to(self) || !cdr.belongs_to(self) {
return Err(LispError::RuntimeError(
"Value belongs to another Lisp runtime".into(),
));
}
let mut root = ptr::null_mut();
let status = unsafe { sys::eclisp_cons_new(car.as_raw(), cdr.as_raw(), &mut root) };
self.value_from_result(status, root)
}
pub fn list(&self, values: &[&Value<'_>]) -> Result<Value<'_>, LispError> {
if values.iter().any(|v| !v.belongs_to(self)) {
return Err(LispError::RuntimeError(
"Value belongs to another Lisp runtime".into(),
));
}
let objects: Vec<sys::cl_object> = values.iter().map(|v| v.as_raw()).collect();
let mut root = ptr::null_mut();
let status = unsafe { sys::eclisp_list_new(objects.len(), objects.as_ptr(), &mut root) };
self.value_from_result(status, root)
}
pub fn string(&self, contents: impl AsRef<str>) -> Result<Value<'_>, LispError> {
let bytes = contents.as_ref().as_bytes();
let mut root = ptr::null_mut();
let status =
unsafe { sys::eclisp_string_new(bytes.as_ptr().cast(), bytes.len(), &mut root) };
self.value_from_result(status, root)
}
fn value_from_result(
&self,
status: i32,
root: *mut sys::eclisp_root,
) -> Result<Value<'_>, LispError> {
if status != 0 {
return Err(LispError::RuntimeError(format!(
"Failed to create Lisp value: {status}"
)));
}
let root = NonNull::new(root)
.ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
Ok(unsafe { Value::from_root(self, root) })
}
}
impl Drop for Lisp {
fn drop(&mut self) {
unsafe { sys::cl_shutdown() };
WAS_SHUTDOWN.store(true, Ordering::Release);
}
}