use std::{fmt, mem::forget, ops::Deref, os::raw::c_int};
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, rb_proc_lambda_p, rb_proc_new, rb_yield, rb_yield_splat, rb_yield_values2, VALUE,
};
use crate::{
enumerator::Enumerator,
error::{ensure, protect, Error},
exception, memoize,
method::{Block, BlockReturn},
object::Object,
r_array::RArray,
r_typed_data::{DataType, DataTypeFunctions},
try_convert::{ArgList, RArrayArgList, TryConvert},
value::{private, NonZeroValue, ReprValue, Value},
};
#[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)))
}
pub fn new<R>(block: fn(&[Value], Option<Proc>) -> R) -> Self
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(&[Value], Option<Proc>) -> R>(callback_arg);
Block::new(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;
#[cfg(ruby_lt_2_7)]
let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) };
unsafe {
#[allow(clippy::fn_to_numeric_cast)]
Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), block as VALUE))
}
}
pub fn from_fn<F, R>(block: F) -> Self
where
F: 'static + Send + FnMut(&[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(&[Value], Option<Proc>) -> R,
R: BlockReturn,
{
let closure = &mut *(callback_arg as *mut F);
Block::new(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;
#[cfg(ruby_lt_2_7)]
let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) };
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
}
pub fn call<A, T>(self, args: A) -> Result<T, Error>
where
A: RArrayArgList,
T: TryConvert,
{
let args = args.into_array_arg_list();
unsafe {
protect(|| Value::new(rb_proc_call(self.as_rb_value(), args.as_rb_value())))
.and_then(|v| v.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 Deref for Proc {
type Target = Value;
fn deref(&self) -> &Self::Target {
self.0.get_ref()
}
}
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 From<Proc> for Value {
fn from(val: Proc) -> Self {
*val
}
}
impl Object for Proc {}
unsafe impl private::ReprValue for Proc {
fn to_value(self) -> Value {
*self
}
unsafe fn from_value_unchecked(val: Value) -> Self {
Self(NonZeroValue::new_unchecked(val))
}
}
impl ReprValue for Proc {}
impl TryConvert for Proc {
fn try_convert(val: Value) -> Result<Self, Error> {
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(
exception::type_error(),
format!("no implicit conversion of {} into Proc", unsafe {
val.classname()
},),
))
}
};
Proc::from_value(val).ok_or_else(|| {
Error::new(
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(&[Value], Option<Proc>) -> R,
R: BlockReturn,
{
struct Closure();
impl DataTypeFunctions for Closure {}
let data_type = memoize!(DataType: {
let mut builder = DataType::builder::<Closure>("rust closure");
builder.free_immediately();
builder.build()
});
let boxed = Box::new(func);
let ptr = Box::into_raw(boxed);
let value = unsafe {
Value::new(rb_data_typed_object_wrap(
0, ptr as *mut _,
data_type.as_rb_data_type() as *const _,
))
};
(ptr, value)
}
pub fn block_given() -> bool {
unsafe { rb_block_given_p() != 0 }
}
pub fn block_proc() -> Result<Proc, Error> {
let val = unsafe { protect(|| Value::new(rb_block_proc()))? };
Ok(Proc::from_value(val).unwrap())
}
pub fn yield_value<T, U>(val: T) -> Result<U, Error>
where
T: Into<Value>,
U: TryConvert,
{
let val = val.into();
unsafe { protect(|| Value::new(rb_yield(val.as_rb_value()))).and_then(|v| v.try_convert()) }
}
pub fn yield_values<T, U>(vals: T) -> Result<U, Error>
where
T: ArgList,
U: TryConvert,
{
let vals = vals.into_arg_list();
let slice = vals.as_ref();
unsafe {
protect(|| {
Value::new(rb_yield_values2(
slice.len() as c_int,
slice.as_ptr() as *const VALUE,
))
})
.and_then(|v| v.try_convert())
}
}
pub fn yield_splat<T>(vals: RArray) -> Result<T, Error>
where
T: TryConvert,
{
unsafe {
protect(|| Value::new(rb_yield_splat(vals.as_rb_value()))).and_then(|v| v.try_convert())
}
}
pub(crate) unsafe fn do_yield_iter<I, T>(mut iter: I)
where
I: Iterator<Item = T>,
T: Into<Value>,
{
let ptr = &mut iter as *mut I;
forget(iter); ensure(
|| {
for val in &mut *ptr {
rb_yield(val.into().as_rb_value());
}
Value::default()
},
|| {
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 ptr = &mut iter as *mut I;
forget(iter);
ensure(
|| {
for val in &mut *ptr {
let vals = val.into_arg_list();
let slice = vals.as_ref();
rb_yield_values2(slice.len() as c_int, slice.as_ptr() as *const VALUE);
}
Value::default()
},
|| {
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());
}
Value::default()
},
|| {
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),
}