eclisp 0.2.0-beta.0

Bindings for Embeddable Common Lisp
use std::{any::TypeId, cell::Cell, ptr::NonNull};

use crate::{Lisp, LispError, Value, sys};

#[repr(C)]
struct ScopedPointerCell {
    type_id: TypeId,
    pointer: Option<NonNull<()>>,
    borrowed: Cell<bool>,
}

pub unsafe fn with_scoped_object_pointer<T: 'static, R>(
    handle: sys::cl_object,
    callback: impl FnOnce(&mut T) -> R,
) -> Option<R> {
    let data = unsafe {
        sys::eclisp_scoped_pointer_data(handle, size_of::<ScopedPointerCell>())
            .cast::<ScopedPointerCell>()
    };
    let cell = NonNull::new(data)?;
    let cell_ref = unsafe { cell.as_ref() };

    if cell_ref.type_id != TypeId::of::<T>() {
        return None;
    }

    let pointer = cell_ref.pointer?.cast::<T>();

    if cell_ref.borrowed.replace(true) {
        return None;
    }

    struct BorrowGuard<'a>(&'a Cell<bool>);

    impl Drop for BorrowGuard<'_> {
        fn drop(&mut self) {
            self.0.set(false);
        }
    }

    let _guard = BorrowGuard(&cell_ref.borrowed);
    Some(callback(unsafe { &mut *pointer.as_ptr() }))
}

pub(crate) unsafe extern "C" fn scoped_pointer_valid<T: 'static>(
    handle: sys::cl_object,
) -> sys::cl_object {
    if unsafe { with_scoped_object_pointer::<T, _>(handle, |_| ()) }.is_some() {
        unsafe { sys::eclisp_t_raw() }
    } else {
        unsafe { sys::eclisp_nil_raw() }
    }
}

pub(crate) unsafe extern "C" fn native_failure_p(object: sys::cl_object) -> sys::cl_object {
    if unsafe { sys::eclisp_is_native_failure(object) } != 0 {
        unsafe { sys::eclisp_t_raw() }
    } else {
        unsafe { sys::eclisp_nil_raw() }
    }
}

pub(crate) struct ScopedObject<'rt> {
    handle: Value<'rt>,
    cell: NonNull<ScopedPointerCell>,
}

impl<'rt> ScopedObject<'rt> {
    pub(crate) fn new<T: 'static>(runtime: &'rt Lisp, value: &mut T) -> Result<Self, LispError> {
        let mut root = std::ptr::null_mut();
        let status =
            unsafe { sys::eclisp_scoped_pointer_new(size_of::<ScopedPointerCell>(), &mut root) };

        if status != sys::ECLISP_OK {
            return Err(LispError::RuntimeError(format!(
                "Failed to create scoped pointer: {status}"
            )));
        }

        let root = NonNull::new(root)
            .ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
        let handle = unsafe { Value::from_root(runtime, root) };
        let data = unsafe {
            sys::eclisp_scoped_pointer_data(handle.as_raw(), size_of::<ScopedPointerCell>())
                .cast::<ScopedPointerCell>()
        };
        let cell = NonNull::new(data)
            .ok_or_else(|| LispError::RuntimeError("Missing scoped pointer data".into()))?;

        unsafe {
            cell.as_ptr().write(ScopedPointerCell {
                type_id: TypeId::of::<T>(),
                pointer: NonNull::new(value).map(NonNull::cast),
                borrowed: Cell::new(false),
            });
        }

        Ok(Self { handle, cell })
    }

    pub(crate) fn handle(&self) -> &Value<'rt> {
        &self.handle
    }
}

impl Drop for ScopedObject<'_> {
    fn drop(&mut self) {
        unsafe {
            self.cell.as_mut().pointer = None;
        }
    }
}