use core::fmt;
use std::{ffi::CStr, ptr::NonNull};
use crate::{Lisp, ListError, ListIter, sys};
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum ValueError {
#[error("Expected {expected}, got {actual}")]
TypeMismatch {
expected: &'static str,
actual: String,
},
#[error("Invalid UTF-8")]
InvalidUtf8,
#[error("Failed to convert Lisp value")]
ConversionFailed,
}
pub struct Value<'rt> {
runtime: &'rt Lisp,
root: NonNull<sys::eclisp_root>,
}
impl<'rt> Value<'rt> {
pub(crate) unsafe fn from_root(runtime: &'rt Lisp, root: NonNull<sys::eclisp_root>) -> Self {
Self { runtime, root }
}
pub(crate) fn as_raw(&self) -> sys::cl_object {
unsafe { sys::eclisp_root_get(self.root.as_ptr()) }
}
pub(crate) fn belongs_to(&self, runtime: &Lisp) -> bool {
std::ptr::eq(self.runtime, runtime)
}
pub fn iter(&self) -> ListIter<'_, 'rt> {
ListIter::new(self)
}
pub fn is_nil(&self) -> bool {
unsafe { sys::eclisp_root_is_nil(self.root.as_ptr()) != 0 }
}
pub fn is_t(&self) -> bool {
unsafe { sys::eclisp_root_is_t(self.root.as_ptr()) != 0 }
}
pub fn is_int(&self) -> bool {
unsafe { sys::eclisp_root_is_int(self.root.as_ptr()) != 0 }
}
pub fn is_float(&self) -> bool {
unsafe { sys::eclisp_root_is_float(self.root.as_ptr()) != 0 }
}
pub fn is_string(&self) -> bool {
unsafe { sys::eclisp_root_is_string(self.root.as_ptr()) != 0 }
}
pub fn try_as_i64(&self) -> Result<i64, ValueError> {
if !self.is_int() {
return Err(ValueError::TypeMismatch {
expected: "int",
actual: "other".into(),
});
}
let mut value = 0i64;
let success = unsafe { sys::eclisp_root_as_int(self.root.as_ptr(), &mut value) };
if success == 0 {
return Err(ValueError::ConversionFailed);
}
Ok(value)
}
pub fn try_as_f64(&self) -> Result<f64, ValueError> {
if !self.is_float() {
return Err(ValueError::TypeMismatch {
expected: "float",
actual: "other".into(),
});
}
let mut value = 0f64;
let success = unsafe { sys::eclisp_root_as_float(self.root.as_ptr(), &mut value) };
if success == 0 {
return Err(ValueError::ConversionFailed);
}
Ok(value)
}
pub fn try_as_string(&self) -> Result<String, ValueError> {
let mut buffer = std::ptr::null_mut();
let mut length = 0usize;
let success =
unsafe { sys::eclisp_root_as_string(self.root.as_ptr(), &mut buffer, &mut length) };
if success == 0 || buffer.is_null() {
return Err(ValueError::ConversionFailed);
}
let result = unsafe {
let bytes = std::slice::from_raw_parts(buffer.cast::<u8>(), length);
std::str::from_utf8(bytes)
.map(str::to_owned)
.map_err(|_| ValueError::InvalidUtf8)
};
unsafe {
sys::eclisp_string_free(buffer);
}
result
}
pub fn repr(&self) -> Result<String, ValueError> {
let mut buffer = std::ptr::null_mut();
let success = unsafe { sys::eclisp_root_repr(self.root.as_ptr(), &mut buffer) };
if buffer.is_null() {
return Err(ValueError::ConversionFailed);
}
let result = if success != 0 {
unsafe { CStr::from_ptr(buffer) }
.to_str()
.map(str::to_owned)
.map_err(|_| ValueError::InvalidUtf8)
} else {
Err(ValueError::ConversionFailed)
};
unsafe {
sys::eclisp_string_free(buffer);
}
result
}
pub fn is_list(&self) -> bool {
unsafe { sys::eclisp_root_is_list(self.root.as_ptr()) != 0 }
}
pub fn is_cons(&self) -> bool {
unsafe { sys::eclisp_root_is_cons(self.root.as_ptr()) != 0 }
}
pub fn car(&self) -> Result<Value<'rt>, ListError> {
if !self.is_cons() {
return Err(ListError::NotCons);
}
let mut root = std::ptr::null_mut();
let success = unsafe { sys::eclisp_root_car(self.root.as_ptr(), &mut root) };
if success == 0 {
return Err(ListError::AllocationFailed);
}
let root = NonNull::new(root).ok_or(ListError::AllocationFailed)?;
Ok(unsafe { Value::from_root(self.runtime, root) })
}
pub fn cdr(&self) -> Result<Value<'rt>, ListError> {
if !self.is_cons() {
return Err(ListError::NotCons);
}
let mut root = std::ptr::null_mut();
let success = unsafe { sys::eclisp_root_cdr(self.root.as_ptr(), &mut root) };
if success == 0 {
return Err(ListError::AllocationFailed);
}
let root = NonNull::new(root).ok_or(ListError::AllocationFailed)?;
Ok(unsafe { Value::from_root(self.runtime, root) })
}
}
impl Drop for Value<'_> {
fn drop(&mut self) {
unsafe { sys::eclisp_root_free(self.root.as_ptr()) }
}
}
impl fmt::Debug for Value<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.repr() {
Ok(repr) => write!(f, "Value({repr})"),
Err(_) => write!(f, "Value(<error>)"),
}
}
}
impl fmt::Display for Value<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.repr() {
Ok(repr) => write!(f, "{repr}"),
Err(_) => write!(f, "<error>"),
}
}
}