use std::{
borrow::Cow,
ffi::{CString, c_char},
fmt,
ptr::null,
};
#[cfg(ruby_gte_3_3)]
use rb_sys::rb_data_define;
use rb_sys::{
VALUE, rb_struct_aref, rb_struct_aset, rb_struct_define, rb_struct_getmember,
rb_struct_members, rb_struct_size, ruby_value_type,
};
use seq_macro::seq;
use crate::{
Ruby,
class::RClass,
error::{Error, protect},
into_value::IntoValue,
object::Object,
r_array::RArray,
symbol::Symbol,
try_convert::TryConvert,
value::{self, IntoId, NonZeroValue, ReprValue, Value, private::ReprValue as _},
};
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RStruct(NonZeroValue);
impl RStruct {
#[inline]
pub fn from_value(val: Value) -> Option<Self> {
unsafe {
(val.rb_type() == ruby_value_type::RUBY_T_STRUCT)
.then(|| Self(NonZeroValue::new_unchecked(val)))
}
}
pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
unsafe { Self(NonZeroValue::new_unchecked(Value::new(val))) }
}
pub fn aref<T, U>(self, index: T) -> Result<U, Error>
where
T: IntoValue,
U: TryConvert,
{
let index = Ruby::get_with(self).into_value(index);
protect(|| unsafe { Value::new(rb_struct_aref(self.as_rb_value(), index.as_rb_value())) })
.and_then(TryConvert::try_convert)
}
pub fn aset<T, U>(self, index: T, val: U) -> Result<(), Error>
where
T: IntoValue,
U: IntoValue,
{
let handle = Ruby::get_with(self);
let index = handle.into_value(index);
let val = handle.into_value(val);
unsafe {
protect(|| {
Value::new(rb_struct_aset(
self.as_rb_value(),
index.as_rb_value(),
val.as_rb_value(),
))
})?;
}
Ok(())
}
pub fn size(self) -> usize {
unsafe { usize::try_convert(Value::new(rb_struct_size(self.as_rb_value()))).unwrap() }
}
pub fn members(self) -> Result<Vec<Cow<'static, str>>, Error> {
unsafe {
let array = RArray::from_rb_value_unchecked(rb_struct_members(self.as_rb_value()));
array
.as_slice()
.iter()
.map(|v| Symbol::from_value(*v).unwrap().name())
.collect()
}
}
pub fn getmember<T, U>(self, id: T) -> Result<U, Error>
where
T: IntoId,
U: TryConvert,
{
let id = id.into_id_with(&Ruby::get_with(self));
protect(|| unsafe { Value::new(rb_struct_getmember(self.as_rb_value(), id.as_rb_id())) })
.and_then(TryConvert::try_convert)
}
}
impl fmt::Display for RStruct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe { self.to_s_infallible() })
}
}
impl fmt::Debug for RStruct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inspect())
}
}
impl IntoValue for RStruct {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
self.0.get()
}
}
impl Object for RStruct {}
unsafe impl value::private::ReprValue for RStruct {}
impl ReprValue for RStruct {}
impl TryConvert for RStruct {
fn try_convert(val: Value) -> Result<Self, Error> {
Self::from_value(val).ok_or_else(|| {
Error::new(
Ruby::get_with(val).exception_type_error(),
format!("no implicit conversion of {} into Struct", unsafe {
val.classname()
},),
)
})
}
}
impl Ruby {
pub fn define_struct<T>(&self, name: Option<&str>, members: T) -> Result<RClass, Error>
where
T: StructMembers,
{
members.define(name)
}
#[cfg(any(ruby_gte_3_3, docsrs))]
#[cfg_attr(docsrs, doc(cfg(ruby_gte_3_3)))]
pub fn define_data<T>(&self, super_class: Option<RClass>, members: T) -> Result<RClass, Error>
where
T: StructMembers,
{
members.define_data(super_class)
}
}
#[deprecated(note = "please use `Ruby::define_struct` instead")]
#[cfg(feature = "old-api")]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn define_struct<T>(name: Option<&str>, members: T) -> Result<RClass, Error>
where
T: StructMembers,
{
get_ruby!().define_struct(name, members)
}
mod private {
use super::*;
pub trait StructMembers {
fn define(self, name: Option<&str>) -> Result<RClass, Error>;
#[cfg(ruby_gte_3_3)]
fn define_data(self, super_class: Option<RClass>) -> Result<RClass, Error>;
}
}
use private::StructMembers;
macro_rules! impl_struct_members {
($n:literal) => {
seq!(N in 0..$n {
impl StructMembers for (#(&str,)*) {
fn define(self, name: Option<&str>) -> Result<RClass, Error> {
let name = name.map(|n| CString::new(n).unwrap());
#(let arg~N = CString::new(self.N).unwrap();)*
protect(|| unsafe {
RClass::from_rb_value_unchecked(rb_struct_define(
name.as_ref().map(|n| n.as_ptr()).unwrap_or_else(null),
#(arg~N.as_ptr(),)*
null::<c_char>(),
))
})
}
#[cfg(ruby_gte_3_3)]
fn define_data(self, super_class: Option<RClass>) -> Result<RClass, Error> {
#(let arg~N = CString::new(self.N).unwrap();)*
protect(|| unsafe {
RClass::from_rb_value_unchecked(rb_data_define(
super_class.map(|s| s.as_rb_value()).unwrap_or(0),
#(arg~N.as_ptr(),)*
null::<c_char>(),
))
})
}
}
});
}
}
seq!(N in 1..=12 {
impl_struct_members!(N);
});