#![allow(
unpredictable_function_pointer_comparisons,
reason = "Not a better option until 1.85.0"
)]
use core::{
cell::Cell, cmp::Ordering, ffi::c_void, fmt, marker::PhantomData, mem::ManuallyDrop, ptr,
};
pub use evil_janet::JanetAbstractType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AbstractError {
MismatchedSize,
MismatchedAbstractType,
NullDataPointer,
}
impl fmt::Display for AbstractError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MismatchedSize => f.pad("Mismatched size between requested type and actual type"),
Self::MismatchedAbstractType => {
f.pad("Mismatched fn pointers between requested type and actual type")
},
Self::NullDataPointer => f.pad("Data pointer is NULL"),
}
}
}
impl core::error::Error for AbstractError {}
#[derive(PartialEq, Eq)]
#[repr(transparent)]
pub struct JanetAbstract {
pub(crate) raw: *mut c_void,
phantom: PhantomData<Cell<()>>,
}
impl JanetAbstract {
#[inline]
pub fn new<A: IsJanetAbstract>(value: A) -> Self {
let s = Self {
raw: unsafe { evil_janet::janet_abstract(A::type_info(), A::SIZE as _) },
phantom: PhantomData,
};
unsafe {
ptr::write(s.raw as *mut _, value);
}
s
}
#[inline]
pub unsafe fn from_raw(raw: *mut c_void) -> Self {
Self {
raw,
phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub unsafe fn get_unchecked<A: IsJanetAbstract>(&self) -> &A::Get {
&*(self.raw as *const A::Get)
}
#[inline]
pub unsafe fn get_mut_unchecked<A: IsJanetAbstract>(&mut self) -> &mut A::Get {
&mut *(self.raw as *mut A::Get)
}
#[inline]
#[must_use]
pub fn is<A: IsJanetAbstract>(&self) -> bool {
if self.size() != A::SIZE {
return false;
}
let ty_self = self.type_info();
let ty_a = A::type_info();
if ty_self.call != ty_a.call
|| ty_self.compare != ty_a.compare
|| ty_self.tostring != ty_a.tostring
|| ty_self.marshal != ty_a.marshal
|| ty_self.unmarshal != ty_a.unmarshal
|| ty_self.hash != ty_a.hash
|| ty_self.next != ty_a.next
|| ty_self.put != ty_a.put
|| ty_self.get != ty_a.get
|| ty_self.gc != ty_a.gc
|| ty_self.gcmark != ty_a.gcmark
{
return false;
}
true
}
fn check<A: IsJanetAbstract>(&self) -> Result<(), AbstractError> {
if self.size() != A::SIZE {
return Err(AbstractError::MismatchedSize);
}
let ty_self = self.type_info();
let ty_a = A::type_info();
if ty_self.call != ty_a.call
|| ty_self.compare != ty_a.compare
|| ty_self.tostring != ty_a.tostring
|| ty_self.marshal != ty_a.marshal
|| ty_self.unmarshal != ty_a.unmarshal
|| ty_self.hash != ty_a.hash
|| ty_self.next != ty_a.next
|| ty_self.put != ty_a.put
|| ty_self.get != ty_a.get
|| ty_self.gc != ty_a.gc
|| ty_self.gcmark != ty_a.gcmark
{
return Err(AbstractError::MismatchedAbstractType);
}
Ok(())
}
#[inline]
pub fn get<A: IsJanetAbstract>(&self) -> Result<&A::Get, AbstractError> {
self.check::<A>()?;
let ptr = self.raw as *const A::Get;
if ptr.is_null() {
return Err(AbstractError::NullDataPointer);
}
Ok(unsafe { &*ptr })
}
#[inline]
pub fn get_mut<A: IsJanetAbstract>(&mut self) -> Result<&mut A::Get, AbstractError> {
self.check::<A>()?;
let ptr = self.raw as *mut A::Get;
if ptr.is_null() {
return Err(AbstractError::NullDataPointer);
}
Ok(unsafe { &mut *(ptr) })
}
pub fn take<A: IsJanetAbstract>(&mut self) -> Result<A, AbstractError> {
self.check::<A>()?;
let ptr = self.raw as *mut A;
if ptr.is_null() {
return Err(AbstractError::NullDataPointer);
}
let value = unsafe { ptr::read(ptr) };
self.raw = ptr::null_mut();
Ok(value)
}
pub fn into_inner<A: IsJanetAbstract>(self) -> Result<A, AbstractError> {
self.check::<A>()?;
let ptr = self.raw as *mut A;
if ptr.is_null() {
return Err(AbstractError::NullDataPointer);
}
Ok(unsafe { ptr::read(ptr) })
}
#[allow(clippy::wrong_self_convention)] #[inline]
#[must_use]
pub const fn as_raw(&self) -> *const c_void {
self.raw
}
#[allow(clippy::wrong_self_convention)]
#[inline]
pub fn as_mut_raw(&mut self) -> *mut c_void {
self.raw
}
#[inline]
pub fn cast<U: IsJanetAbstract>(self) -> *mut U {
self.raw as *mut U
}
#[inline]
#[must_use]
pub fn size(&self) -> usize {
unsafe { (*evil_janet::janet_abstract_head(self.raw)).size }
}
#[inline]
#[must_use]
pub fn type_info(&self) -> JanetAbstractType {
unsafe { *(*evil_janet::janet_abstract_head(self.raw)).type_ }
}
#[inline]
fn raw_type_info(&self) -> *const JanetAbstractType {
unsafe { (*evil_janet::janet_abstract_head(self.raw)).type_ }
}
}
impl fmt::Debug for JanetAbstract {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JanetAbstract")
.field("mem_size", &self.size())
.finish()
}
}
impl PartialOrd for JanetAbstract {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for JanetAbstract {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
if self.raw == other.raw {
return Ordering::Equal;
}
let self_ty = self.raw_type_info();
let other_ty = self.raw_type_info();
if self_ty != other_ty && self_ty > other_ty {
return Ordering::Greater;
}
let self_ty = self.type_info();
if let Some(f) = self_ty.compare {
let res = unsafe { f(self.raw, other.raw) };
match res {
-1 => Ordering::Less,
0 => Ordering::Equal,
1 => Ordering::Greater,
_ => unreachable!(),
}
} else if self.raw > other.raw {
Ordering::Greater
} else {
Ordering::Less
}
}
}
pub unsafe trait IsJanetAbstract {
type Get: IsJanetAbstract;
const SIZE: usize;
fn type_info() -> &'static JanetAbstractType;
}
pub fn register<T: IsJanetAbstract>() {
let at = T::type_info();
unsafe {
let syn = evil_janet::janet_wrap_symbol(evil_janet::janet_csymbol(at.name));
let abs_type_ptr = evil_janet::janet_get_abstract_type(syn);
if abs_type_ptr.is_null() {
evil_janet::janet_register_abstract_type(at);
}
}
}
unsafe impl IsJanetAbstract for i64 {
type Get = i64;
const SIZE: usize = core::mem::size_of::<Self>();
#[inline]
fn type_info() -> &'static JanetAbstractType {
unsafe { &evil_janet::janet_s64_type }
}
}
unsafe impl IsJanetAbstract for u64 {
type Get = u64;
const SIZE: usize = core::mem::size_of::<Self>();
#[inline]
fn type_info() -> &'static JanetAbstractType {
unsafe { &evil_janet::janet_u64_type }
}
}
unsafe impl<A> IsJanetAbstract for ManuallyDrop<A>
where
A: IsJanetAbstract,
{
type Get = ManuallyDrop<A::Get>;
const SIZE: usize = A::SIZE;
#[inline]
fn type_info() -> &'static JanetAbstractType {
A::type_info()
}
}
#[cfg(all(test, any(feature = "amalgation", feature = "link-system")))]
mod tests {
use super::*;
#[test]
fn creation_and_getting_value() -> Result<(), crate::client::Error> {
let _client = crate::client::JanetClient::init()?;
let test = JanetAbstract::new(10u64);
let test2 = JanetAbstract::new(12i64);
let val = test.get::<u64>();
let val2 = test2.get::<i64>();
let val3 = test.get::<i64>();
assert_eq!(Ok(&10), val);
assert_eq!(Ok(&12), val2);
assert_eq!(Err(AbstractError::MismatchedAbstractType), val3);
Ok(())
}
#[test]
fn get_mut() -> Result<(), crate::client::Error> {
let _client = crate::client::JanetClient::init()?;
let mut test = JanetAbstract::new(10u64);
let mut test2 = JanetAbstract::new(12i64);
let val = test.get_mut::<u64>();
let val2 = test2.get_mut::<i64>();
assert_eq!(Ok(&mut 10), val);
assert_eq!(Ok(&mut 12), val2);
if let Ok(val) = val {
*val = 1000;
assert_eq!(&mut 1000, val);
}
if let Ok(val2) = val2 {
*val2 = 2000;
assert_eq!(&mut 2000, val2);
}
Ok(())
}
#[test]
fn size() -> Result<(), crate::client::Error> {
let _client = crate::client::JanetClient::init()?;
let test = JanetAbstract::new(10u64);
let test2 = JanetAbstract::new(12i64);
assert_eq!(u64::SIZE, test.size());
assert_eq!(u64::SIZE, test2.size());
Ok(())
}
#[derive(Debug, PartialEq)]
struct TestDrop(bool);
static mut TEST_DROP: JanetAbstractType = JanetAbstractType {
name: c"TestDrop".as_ptr().cast::<i8>(),
gc: None,
gcmark: None,
get: None,
put: None,
marshal: None,
unmarshal: None,
tostring: None,
compare: None,
hash: None,
next: None,
call: None,
length: None,
bytes: None,
};
unsafe impl IsJanetAbstract for TestDrop {
type Get = ManuallyDrop<Self>;
const SIZE: usize = core::mem::size_of::<Self>();
#[inline]
fn type_info() -> &'static JanetAbstractType {
unsafe { &*ptr::addr_of!(TEST_DROP) }
}
}
impl Drop for TestDrop {
fn drop(&mut self) {
self.0 = false;
panic!("Dropping TestNonCopy");
}
}
#[test]
fn non_copy() -> Result<(), crate::client::Error> {
let _client = crate::client::JanetClient::init()?;
let test = JanetAbstract::new(TestDrop(true));
let val = test.get::<TestDrop>();
assert_eq!(Ok(&ManuallyDrop::new(TestDrop(true))), val);
Ok(())
}
#[derive(Debug, PartialEq)]
struct TestDrop2(bool);
static mut TEST_DROP2: JanetAbstractType = JanetAbstractType {
name: c"TestDrop2".as_ptr().cast::<i8>(),
gc: None,
gcmark: None,
get: None,
put: None,
marshal: None,
unmarshal: None,
tostring: None,
compare: None,
hash: None,
next: None,
call: None,
length: None,
bytes: None,
};
unsafe impl IsJanetAbstract for TestDrop2 {
type Get = Self;
const SIZE: usize = core::mem::size_of::<Self>();
#[inline]
fn type_info() -> &'static JanetAbstractType {
unsafe { &*ptr::addr_of!(TEST_DROP2) }
}
}
impl Drop for TestDrop2 {
fn drop(&mut self) {
self.0 = false;
panic!("Dropping TestNonCopy");
}
}
#[test]
#[should_panic]
fn non_copy_ill_implemented() {
let _client = crate::client::JanetClient::init().unwrap();
let test = JanetAbstract::new(TestDrop2(true));
let val = test.get::<TestDrop2>();
assert_eq!(Ok(&TestDrop2(true)), val);
}
}