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 run(&self, path: impl AsRef<std::path::Path>) -> Result<Value<'rt>, LispError> {
let path = path
.as_ref()
.to_str()
.ok_or_else(|| LispError::RuntimeError("Invalid file path".into()))?;
let source =
std::fs::read_to_string(path).map_err(|e| LispError::RuntimeError(e.to_string()))?;
self.eval(source)
}
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<Option<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) };
match status {
sys::ECLISP_NOT_FOUND => return Ok(None),
sys::ECLISP_OK => {
let root = NonNull::new(root)
.ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
Ok(Some(Function {
value: Rc::new(unsafe { Value::from_root(self.runtime, root) }),
}))
}
_ => {
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));
}
}
}
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'_')
}