use std::{
fmt,
mem::{forget, size_of},
os::raw::c_int,
slice,
};
use rb_sys::{
rb_block_given_p, rb_block_proc, rb_data_typed_object_wrap, rb_obj_is_proc, rb_proc_arity,
rb_proc_call_kw, rb_proc_lambda_p, rb_proc_new, rb_yield, rb_yield_splat, rb_yield_values_kw,
VALUE,
};
use crate::{
data_type_builder,
enumerator::Enumerator,
error::{ensure, protect, Error},
gc,
into_value::{kw_splat, ArgList, IntoValue, RArrayArgList},
method::{Block, BlockReturn},
object::Object,
r_array::RArray,
try_convert::TryConvert,
typed_data::{DataType, DataTypeFunctions},
value::{
private::{self, ReprValue as _},
NonZeroValue, ReprValue, Value,
},
Ruby,
};
impl Ruby {
pub fn proc_new<R>(&self, block: fn(&Ruby, &[Value], Option<Proc>) -> R) -> Proc
where
R: BlockReturn,
{
unsafe extern "C" fn call<R>(
_yielded_arg: VALUE,
callback_arg: VALUE,
argc: c_int,
argv: *const VALUE,
blockarg: VALUE,
) -> VALUE
where
R: BlockReturn,
{
let func =
std::mem::transmute::<VALUE, fn(&Ruby, &[Value], Option<Proc>) -> R>(callback_arg);
func.call_handle_error(argc, argv as *const Value, Value::new(blockarg))
.as_rb_value()
}
let call_func =
call::<R> as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE;
unsafe {
#[allow(clippy::fn_to_numeric_cast)]
Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), block as VALUE))
}
}
pub fn proc_from_fn<F, R>(&self, block: F) -> Proc
where
F: 'static + Send + FnMut(&Ruby, &[Value], Option<Proc>) -> R,
R: BlockReturn,
{
unsafe extern "C" fn call<F, R>(
_yielded_arg: VALUE,
callback_arg: VALUE,
argc: c_int,
argv: *const VALUE,
blockarg: VALUE,
) -> VALUE
where
F: FnMut(&Ruby, &[Value], Option<Proc>) -> R,
R: BlockReturn,
{
let closure = &mut *(callback_arg as *mut F);
closure
.call_handle_error(argc, argv as *const Value, Value::new(blockarg))
.as_rb_value()
}
let (closure, keepalive) = wrap_closure(block);
let call_func =
call::<F, R> as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE;
let proc = unsafe {
Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), closure as VALUE))
};
proc.ivar_set("__rust_closure", keepalive).unwrap();
proc
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Proc(NonZeroValue);
impl Proc {
#[inline]
pub fn from_value(val: Value) -> Option<Self> {
unsafe {
Value::new(rb_obj_is_proc(val.as_rb_value()))
.to_bool()
.then(|| Self(NonZeroValue::new_unchecked(val)))
}
}
#[inline]
pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
Self(NonZeroValue::new_unchecked(Value::new(val)))
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::proc_new` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn new<R>(block: fn(&Ruby, &[Value], Option<Proc>) -> R) -> Self
where
R: BlockReturn,
{
get_ruby!().proc_new(block)
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::proc_from_fn` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn from_fn<F, R>(block: F) -> Self
where
F: 'static + Send + FnMut(&Ruby, &[Value], Option<Proc>) -> R,
R: BlockReturn,
{
get_ruby!().proc_from_fn(block)
}
pub fn call<A, T>(self, args: A) -> Result<T, Error>
where
A: RArrayArgList,
T: TryConvert,
{
let kw_splat = kw_splat(&args);
let args = args.into_array_arg_list_with(&Ruby::get_with(self));
unsafe {
protect(|| {
Value::new(rb_proc_call_kw(
self.as_rb_value(),
args.as_rb_value(),
kw_splat as c_int,
))
})
.and_then(TryConvert::try_convert)
}
}
pub fn arity(self) -> i64 {
unsafe { rb_proc_arity(self.as_rb_value()) as i64 }
}
pub fn is_lambda(self) -> bool {
unsafe { Value::new(rb_proc_lambda_p(self.as_rb_value())).to_bool() }
}
}
impl fmt::Display for Proc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe { self.to_s_infallible() })
}
}
impl fmt::Debug for Proc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inspect())
}
}
impl IntoValue for Proc {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
self.0.get()
}
}
impl Object for Proc {}
unsafe impl private::ReprValue for Proc {}
impl ReprValue for Proc {}
impl TryConvert for Proc {
fn try_convert(val: Value) -> Result<Self, Error> {
let handle = Ruby::get_with(val);
if let Some(p) = Proc::from_value(val) {
return Ok(p);
}
let p_val: Value = match val.funcall("to_proc", ()) {
Ok(v) => v,
Err(_) => {
return Err(Error::new(
handle.exception_type_error(),
format!("no implicit conversion of {} into Proc", unsafe {
val.classname()
},),
))
}
};
Proc::from_value(val).ok_or_else(|| {
Error::new(
handle.exception_type_error(),
format!(
"can't convert {0} to Proc ({0}#to_proc gives {1})",
unsafe { val.classname() },
unsafe { p_val.classname() },
),
)
})
}
}
fn wrap_closure<F, R>(func: F) -> (*mut F, Value)
where
F: FnMut(&Ruby, &[Value], Option<Proc>) -> R,
R: BlockReturn,
{
struct Closure<F>(F, DataType);
unsafe impl<F> Send for Closure<F> {}
impl<F> DataTypeFunctions for Closure<F> {
fn mark(&self, marker: &gc::Marker) {
marker.mark_slice(unsafe {
slice::from_raw_parts(
&self.0 as *const _ as *const Value,
size_of::<F>() / size_of::<Value>(),
)
});
}
}
let data_type = data_type_builder!(Closure<F>, "rust closure")
.free_immediately()
.mark()
.build();
let boxed = Box::new(Closure(func, data_type));
let ptr = Box::into_raw(boxed);
let value = unsafe {
Value::new(rb_data_typed_object_wrap(
0, ptr as *mut _,
(*ptr).1.as_rb_data_type() as *const _,
))
};
unsafe { (&mut (*ptr).0 as *mut F, value) }
}
impl Ruby {
pub fn block_given(&self) -> bool {
unsafe { rb_block_given_p() != 0 }
}
pub fn block_proc(&self) -> Result<Proc, Error> {
let val = unsafe { protect(|| Value::new(rb_block_proc()))? };
Ok(Proc::from_value(val).unwrap())
}
pub fn yield_value<T, U>(&self, val: T) -> Result<U, Error>
where
T: IntoValue,
U: TryConvert,
{
let val = self.into_value(val);
unsafe {
protect(|| Value::new(rb_yield(val.as_rb_value()))).and_then(TryConvert::try_convert)
}
}
pub fn yield_values<T, U>(&self, vals: T) -> Result<U, Error>
where
T: ArgList,
U: TryConvert,
{
let kw_splat = kw_splat(&vals);
let vals = vals.into_arg_list_with(self);
let slice = vals.as_ref();
unsafe {
protect(|| {
Value::new(rb_yield_values_kw(
slice.len() as c_int,
slice.as_ptr() as *const VALUE,
kw_splat as c_int,
))
})
.and_then(TryConvert::try_convert)
}
}
pub fn yield_splat<T>(&self, vals: RArray) -> Result<T, Error>
where
T: TryConvert,
{
unsafe {
protect(|| Value::new(rb_yield_splat(vals.as_rb_value())))
.and_then(TryConvert::try_convert)
}
}
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::block_given` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn block_given() -> bool {
get_ruby!().block_given()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::block_proc` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn block_proc() -> Result<Proc, Error> {
get_ruby!().block_proc()
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::yield_value` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn yield_value<T, U>(val: T) -> Result<U, Error>
where
T: IntoValue,
U: TryConvert,
{
get_ruby!().yield_value(val)
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::yield_values` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn yield_values<T, U>(vals: T) -> Result<U, Error>
where
T: ArgList,
U: TryConvert,
{
get_ruby!().yield_values(vals)
}
#[cfg_attr(
not(feature = "old-api"),
deprecated(note = "please use `Ruby::yield_splat` instead")
)]
#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
#[inline]
pub fn yield_splat<T>(vals: RArray) -> Result<T, Error>
where
T: TryConvert,
{
get_ruby!().yield_splat(vals)
}
pub(crate) unsafe fn do_yield_iter<I, T>(mut iter: I)
where
I: Iterator<Item = T>,
T: IntoValue,
{
let handle = Ruby::get_unchecked();
let ptr = &mut iter as *mut I;
forget(iter); ensure(
|| {
for val in &mut *ptr {
rb_yield(handle.into_value(val).as_rb_value());
}
handle.qnil()
},
|| {
ptr.drop_in_place();
},
);
}
pub(crate) unsafe fn do_yield_values_iter<I, T>(mut iter: I)
where
I: Iterator<Item = T>,
T: ArgList,
{
let handle = Ruby::get_unchecked();
let ptr = &mut iter as *mut I;
forget(iter);
ensure(
|| {
for val in &mut *ptr {
let kw_splat = kw_splat(&val);
let vals = val.into_arg_list_with(&handle);
let slice = vals.as_ref();
rb_yield_values_kw(
slice.len() as c_int,
slice.as_ptr() as *const VALUE,
kw_splat as c_int,
);
}
handle.qnil()
},
|| {
ptr.drop_in_place();
},
);
}
pub(crate) unsafe fn do_yield_splat_iter<I>(mut iter: I)
where
I: Iterator<Item = RArray>,
{
let ptr = &mut iter as *mut I;
forget(iter);
ensure(
|| {
for val in &mut *ptr {
rb_yield_splat(val.as_rb_value());
}
Ruby::get_unchecked().qnil()
},
|| {
ptr.drop_in_place();
},
);
}
pub enum Yield<I> {
Iter(I),
Enumerator(Enumerator),
}
pub enum YieldValues<I> {
Iter(I),
Enumerator(Enumerator),
}
pub enum YieldSplat<I> {
Iter(I),
Enumerator(Enumerator),
}