eclisp 0.1.3

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, 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) {
            // 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<'rt, R>(&'rt self, f: impl FnOnce(LispContext<'rt>) -> R) -> R {
        callback::enter(self, f)
    }

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