eclisp 0.1.0

Bindings for Embeddable Common Lisp
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) {
            // TODO: Restart
            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()) };

        // 1 is a succcess
        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);
    }
}