use core::{fmt, slice};
use std::{
ffi::CStr,
marker::PhantomData,
ptr::{self, 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,
}
#[derive(Clone, Copy)]
pub struct ValueRef<'a> {
object: sys::cl_object,
_lifetime: PhantomData<&'a ()>,
}
impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_raw(object: sys::cl_object) -> Self {
Self {
object,
_lifetime: PhantomData,
}
}
pub(crate) fn as_raw(self) -> sys::cl_object {
self.object
}
fn raw(&self) -> RawValue {
RawValue::new(self.object)
}
pub fn is_nil(&self) -> bool {
self.raw().is_nil()
}
pub fn is_t(&self) -> bool {
self.raw().is_t()
}
pub fn is_int(&self) -> bool {
self.raw().is_int()
}
pub fn is_float(&self) -> bool {
self.raw().is_float()
}
pub fn is_string(&self) -> bool {
self.raw().is_string()
}
pub fn is_cons(&self) -> bool {
self.raw().is_cons()
}
pub fn is_list(&self) -> bool {
self.raw().is_list()
}
pub fn try_as_i64(&self) -> Result<i64, ValueError> {
self.raw().try_as_i64()
}
pub fn try_as_f64(&self) -> Result<f64, ValueError> {
self.raw().try_as_f64()
}
pub fn try_as_string(&self) -> Result<String, ValueError> {
self.raw().try_as_string()
}
pub fn repr(&self) -> Result<String, ValueError> {
self.raw().repr()
}
}
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 {
ptr::eq(self.runtime, runtime)
}
pub(crate) fn into_raw_root(self) -> *mut sys::eclisp_root {
let root = self.root.as_ptr();
std::mem::forget(self);
root
}
fn raw(&self) -> RawValue {
RawValue::new(self.as_raw())
}
pub fn as_ref(&self) -> ValueRef<'_> {
unsafe { ValueRef::from_raw(self.as_raw()) }
}
pub fn iter(&self) -> ListIter<'_, 'rt> {
ListIter::new(self)
}
pub fn is_nil(&self) -> bool {
self.raw().is_nil()
}
pub fn is_t(&self) -> bool {
self.raw().is_t()
}
pub fn is_int(&self) -> bool {
self.raw().is_int()
}
pub fn is_float(&self) -> bool {
self.raw().is_float()
}
pub fn is_string(&self) -> bool {
self.raw().is_string()
}
pub fn is_cons(&self) -> bool {
self.raw().is_cons()
}
pub fn is_list(&self) -> bool {
self.raw().is_list()
}
pub fn try_as_i64(&self) -> Result<i64, ValueError> {
self.raw().try_as_i64()
}
pub fn try_as_f64(&self) -> Result<f64, ValueError> {
self.raw().try_as_f64()
}
pub fn try_as_string(&self) -> Result<String, ValueError> {
self.raw().try_as_string()
}
pub fn repr(&self) -> Result<String, ValueError> {
self.raw().repr()
}
pub fn car(&self) -> Result<Value<'rt>, ListError> {
if !self.is_cons() {
return Err(ListError::NotCons);
}
let mut root = ptr::null_mut();
let success = unsafe { sys::eclisp_car(self.as_raw(), &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 = ptr::null_mut();
let success = unsafe { sys::eclisp_cdr(self.as_raw(), &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 fmt::Formatter<'_>) -> 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 fmt::Formatter<'_>) -> fmt::Result {
match self.repr() {
Ok(repr) => write!(f, "{repr}"),
Err(_) => write!(f, "<error>"),
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct RawValue {
object: sys::cl_object,
}
impl RawValue {
fn new(object: sys::cl_object) -> Self {
Self { object }
}
fn is_nil(self) -> bool {
unsafe { sys::eclisp_is_nil(self.object) != 0 }
}
fn is_t(self) -> bool {
unsafe { sys::eclisp_is_t(self.object) != 0 }
}
fn is_int(self) -> bool {
unsafe { sys::eclisp_is_int(self.object) != 0 }
}
fn is_float(self) -> bool {
unsafe { sys::eclisp_is_float(self.object) != 0 }
}
fn is_string(self) -> bool {
unsafe { sys::eclisp_is_string(self.object) != 0 }
}
fn is_cons(self) -> bool {
unsafe { sys::eclisp_is_cons(self.object) != 0 }
}
fn is_list(self) -> bool {
unsafe { sys::eclisp_is_list(self.object) != 0 }
}
fn try_as_i64(self) -> Result<i64, ValueError> {
if !self.is_int() {
return Err(ValueError::TypeMismatch {
expected: "integer",
actual: "other".into(),
});
}
let mut value = 0i64;
let success = unsafe { sys::eclisp_as_int(self.object, &mut value) };
if success == 0 {
return Err(ValueError::ConversionFailed);
}
Ok(value)
}
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_as_float(self.object, &mut value) };
if success == 0 {
return Err(ValueError::ConversionFailed);
}
Ok(value)
}
fn try_as_string(self) -> Result<String, ValueError> {
let mut buffer = ptr::null_mut();
let mut length = 0usize;
let success = unsafe { sys::eclisp_as_string(self.object, &mut buffer, &mut length) };
if success == 0 || buffer.is_null() {
if !buffer.is_null() {
unsafe { sys::eclisp_string_free(buffer) };
}
return Err(ValueError::ConversionFailed);
}
let result = unsafe {
let bytes = slice::from_raw_parts(buffer.cast::<u8>(), length);
str::from_utf8(bytes)
.map(str::to_owned)
.map_err(|_| ValueError::InvalidUtf8)
};
unsafe { sys::eclisp_string_free(buffer) };
result
}
fn repr(self) -> Result<String, ValueError> {
let mut buffer = ptr::null_mut();
let success = unsafe { sys::eclisp_repr(self.object, &mut buffer) };
if success == 0 || buffer.is_null() {
if !buffer.is_null() {
unsafe { sys::eclisp_string_free(buffer) };
}
return Err(ValueError::ConversionFailed);
}
let result = unsafe { CStr::from_ptr(buffer) }
.to_str()
.map(str::to_owned)
.map_err(|_| ValueError::InvalidUtf8);
unsafe { sys::eclisp_string_free(buffer) };
result
}
}