eclisp 0.1.4

Bindings for Embeddable Common Lisp
use std::{
    ffi::{CStr, CString},
    ptr::{self, NonNull},
    rc::Rc,
};

use crate::{Class, Function, Lisp, LispError, Value, ValueRef, sys};

#[derive(Clone, Copy)]
pub struct LispContext<'rt> {
    pub(crate) runtime: &'rt Lisp,
}

impl LispContext<'_> {
    pub fn defun<F>(&self, name: &str, callback: F) -> Result<(), LispError>
    where
        F: for<'a, 'call> Fn(LispContext<'a>, &[ValueRef<'call>]) -> Result<Value<'a>, LispError>
            + 'static,
    {
        if !valid_symbol(name) {
            return Err(LispError::InvalidSymbol(name.to_string()));
        }

        let id = self
            .runtime
            .callbacks
            .borrow_mut()
            .insert(Rc::new(callback));

        let definition = format!("(defun {name} (&rest args) (%eclisp-dispatch {id} args))");

        if let Err(error) = self.eval(definition) {
            self.runtime.callbacks.borrow_mut().remove(id);
            return Err(error);
        }

        Ok(())
    }

    pub fn defclass(&self, class: &Class) -> Result<(), LispError> {
        let mut names = vec![class.name.as_str()];

        names.extend(class.supers.iter().map(String::as_str));

        for slot in &class.slots {
            names.push(&slot.name);

            if let Some(name) = &slot.initarg {
                names.push(name);
            }

            if let Some(name) = &slot.accessor {
                names.push(name);
            }
        }

        for name in names {
            if !valid_symbol(name) {
                return Err(LispError::InvalidSymbol(name.to_string()));
            }
        }

        let supers = class.supers.join(" ");

        let slots = class
            .slots
            .iter()
            .map(|slot| {
                let mut form = format!("({}", slot.name);

                if let Some(initarg) = &slot.initarg {
                    form.push_str(&format!(" :initarg :{initarg}"));
                }

                if let Some(accessor) = &slot.accessor {
                    form.push_str(&format!(" :accessor {accessor}"));
                }

                if let Some(initform) = &slot.initform {
                    form.push_str(&format!(" :initform {initform}"));
                }

                form.push(')');
                form
            })
            .collect::<Vec<_>>()
            .join(" ");

        self.eval(format!("(defclass {} ({supers}) ({slots}))", class.name))?;

        Ok(())
    }

    pub fn defmethod<F>(
        &self,
        name: &str,
        parameters: &[(&str, &str)],
        callback: F,
    ) -> Result<(), LispError>
    where
        F: for<'a, 'call> Fn(LispContext<'a>, &[ValueRef<'call>]) -> Result<Value<'a>, LispError>
            + 'static,
    {
        if !valid_symbol(name)
            || parameters
                .iter()
                .any(|(parameter, class)| !valid_symbol(parameter) || !valid_symbol(class))
        {
            return Err(LispError::InvalidSymbol(name.to_string()));
        }

        let id = self
            .runtime
            .callbacks
            .borrow_mut()
            .insert(Rc::new(callback));

        let lambda_list = parameters
            .iter()
            .map(|(parameter, class)| format!("({parameter} {class})"))
            .collect::<Vec<_>>()
            .join(" ");

        let arguments = parameters
            .iter()
            .map(|(parameter, _)| *parameter)
            .collect::<Vec<_>>()
            .join(" ");

        let definition = format!(
            "(defmethod {name} ({lambda_list}) \
           (%eclisp-dispatch {id} (list {arguments})))"
        );

        if let Err(error) = self.eval(definition) {
            self.runtime.callbacks.borrow_mut().remove(id);
            return Err(error);
        }

        Ok(())
    }
}

impl<'rt> LispContext<'rt> {
    pub fn load(&self, code: impl AsRef<str>) -> Result<(), LispError> {
        self.eval(code).map(|_| ())
    }

    pub fn root(&self, value: ValueRef<'_>) -> Result<Value<'rt>, LispError> {
        let mut root = std::ptr::null_mut();

        let success = unsafe { sys::eclisp_root_copy(value.as_raw(), &mut root) };

        if success == 0 {
            return Err(LispError::RuntimeError("Failed to create Lisp root".into()));
        }

        let root = NonNull::new(root)
            .ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;

        Ok(unsafe { Value::from_root(self.runtime, root) })
    }

    pub fn eval(&self, code: impl AsRef<str>) -> Result<Value<'rt>, 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.runtime, root) })
    }

    pub fn call(&self, function: &str, args: &[&Value<'_>]) -> Result<Value<'rt>, LispError> {
        let function =
            CString::new(function).map_err(|e| LispError::RuntimeError(e.to_string()))?;

        if args.iter().any(|value| !value.belongs_to(self.runtime)) {
            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.runtime, root) })
    }

    pub fn fun(&self, name: &str) -> Result<Function<'rt>, LispError> {
        let name = CString::new(name).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_function_get(name.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 {
                format!("Unknown function: {}", name.to_string_lossy())
            };

            return Err(LispError::RuntimeError(message));
        }

        let root = NonNull::new(root)
            .ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;

        Ok(Function {
            value: Rc::new(unsafe { Value::from_root(self.runtime, root) }),
        })
    }

    pub fn callfun(
        &self,
        function: &Function<'_>,
        args: &[&Value<'_>],
    ) -> Result<Value<'rt>, LispError> {
        if !function.value.belongs_to(self.runtime)
            || args.iter().any(|v| !v.belongs_to(self.runtime))
        {
            return Err(LispError::RuntimeError(
                "Value belongs to another Lisp runtime".into(),
            ));
        }

        let raw_args: Vec<sys::cl_object> = args.iter().map(|v| v.as_raw()).collect();

        let mut root = ptr::null_mut();
        let mut error = ptr::null_mut();

        let status = unsafe {
            sys::eclisp_function_call(
                function.value.as_raw(),
                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 {
                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.runtime, root) })
    }

    pub fn int(&self, value: i64) -> Result<Value<'rt>, 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<'rt>, 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(&self, car: &Value<'_>, cdr: &Value<'_>) -> Result<Value<'rt>, LispError> {
        if !car.belongs_to(self.runtime) || !cdr.belongs_to(self.runtime) {
            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<'rt>, LispError> {
        if values.iter().any(|v| !v.belongs_to(self.runtime)) {
            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<'rt>, 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)
    }

    pub fn nil(&self) -> Result<Value<'rt>, LispError> {
        let mut root = ptr::null_mut();
        let status = unsafe { sys::eclisp_nil_new(&mut root) };
        self.value_from_result(status, root)
    }

    pub fn t(&self) -> Result<Value<'rt>, LispError> {
        let mut root = ptr::null_mut();
        let status = unsafe { sys::eclisp_t_new(&mut root) };
        self.value_from_result(status, root)
    }

    fn value_from_result(
        &self,
        status: i32,
        root: *mut sys::eclisp_root,
    ) -> Result<Value<'rt>, 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.runtime, root) })
    }
}

fn valid_symbol(name: &str) -> bool {
    let mut bytes = name.bytes();

    matches!(bytes.next(), Some(b'a'..=b'z' | b'A'..=b'Z'))
        && bytes.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
}