eclisp 0.1.1

Bindings for Embeddable Common Lisp
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) {
            // 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}"
            )));
        }

        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);
    }
}