#[macro_export]
macro_rules! class {
($class: ident) => {
#[repr(C)]
#[derive(Debug, PartialEq)]
pub struct $class {
value: $crate::types::Value,
}
impl From<$crate::types::Value> for $class {
fn from(value: $crate::types::Value) -> Self {
$class { value: value }
}
}
impl $crate::Object for $class {
#[inline]
fn value(&self) -> $crate::types::Value {
self.value
}
}
};
}
#[macro_export]
macro_rules! module {
($module: ident) => {
#[repr(C)]
#[derive(Debug, PartialEq)]
pub struct $module {
value: $crate::types::Value,
}
impl From<$crate::types::Value> for $module {
fn from(value: $crate::types::Value) -> Self {
$module { value: value }
}
}
impl $crate::Object for $module {
#[inline]
fn value(&self) -> $crate::types::Value {
self.value
}
}
};
}
#[macro_export]
macro_rules! unsafe_methods {
(
$rtself_class: ty,
$rtself_name: ident,
$(
fn $method_name: ident
($($arg_name: ident: $arg_type: ty),* $(,)?) -> $return_type: ty $body: block
$(,)?
)*
) => {
$(
$crate::rutie_callback! {
pub fn $method_name(argc: $crate::types::Argc,
argv: *const $crate::AnyObject,
#[allow(unused_mut)]
#[allow(unused_variables)]
mut $rtself_name: $rtself_class) -> $return_type {
let _arguments = $crate::util::parse_arguments(argc, argv);
let mut _i = 0;
$(
let $arg_name = unsafe {
<$crate::AnyObject as $crate::Object>
::to::<$arg_type>(&_arguments[_i])
};
_i += 1;
)*
$body
}
}
)*
}
}
#[macro_export]
macro_rules! methods {
(@method $rtself_class: ty, $rtself_name: ident, $method_name: ident,
($($arg_name: ident: $arg_type: ty),+ , * $splat_name: ident $(,)?),
$return_type: ty, $body: block) => {
$crate::methods!(@define $rtself_class, $rtself_name, $method_name,
[$($arg_name: $arg_type),+], [$splat_name], $return_type, $body);
};
(@method $rtself_class: ty, $rtself_name: ident, $method_name: ident,
(* $splat_name: ident $(,)?), $return_type: ty, $body: block) => {
$crate::methods!(@define $rtself_class, $rtself_name, $method_name,
[], [$splat_name], $return_type, $body);
};
(@method $rtself_class: ty, $rtself_name: ident, $method_name: ident,
($($arg_name: ident: $arg_type: ty),* $(,)?), $return_type: ty, $body: block) => {
$crate::methods!(@define $rtself_class, $rtself_name, $method_name,
[$($arg_name: $arg_type),*], [], $return_type, $body);
};
(@define $rtself_class: ty, $rtself_name: ident, $method_name: ident,
[$($arg_name: ident: $arg_type: ty),*], [$($splat_name: ident)?],
$return_type: ty, $body: block) => {
$crate::rutie_callback! {
pub fn $method_name(argc: $crate::types::Argc,
argv: *const $crate::AnyObject,
#[allow(unused_mut)]
#[allow(unused_variables)]
mut $rtself_name: $rtself_class) -> $return_type {
let _arguments = $crate::util::parse_arguments(argc, argv);
let mut _i = 0;
$(
let $arg_name =
_arguments
.get(_i)
.ok_or_else(|| {
<$crate::AnyException as $crate::Exception>::new("ArgumentError",
Some(&format!(
"Argument '{}: {}' not found for method '{}'",
stringify!($arg_name),
stringify!($arg_type),
stringify!($method_name)
))
)
}).and_then(|argument| {
<$crate::AnyObject as $crate::Object>
::try_convert_to::<$arg_type>(argument)
});
_i += 1;
)*
$(
let $splat_name: $crate::Array = _arguments.iter().skip(_i).cloned().collect();
)?
$body
}
}
};
(
$rtself_class: ty,
$rtself_name: ident,
$(
fn $method_name: ident
($($params: tt)*) -> $return_type: ty $body: block
$(,)?
)*
) => {
$(
$crate::methods!(@method $rtself_class, $rtself_name, $method_name,
($($params)*), $return_type, $body);
)*
}
}
#[macro_export]
macro_rules! wrappable_struct {
(@parse $struct_name: ty, $wrapper: ident, $static_name: ident,
{ $($mark: tt)* }, { $($size: tt)* }, { $($compact: tt)* }, $(,)?) => {
$crate::wrappable_struct!(@define $struct_name, $wrapper, $static_name,
{ $($mark)* }, { $($size)* }, { $($compact)* });
};
(@parse $struct_name: ty, $wrapper: ident, $static_name: ident,
{ }, { $($size: tt)* }, { $($compact: tt)* },
, mark($object: ident) $body: block $($rest: tt)*) => {
$crate::wrappable_struct!(@parse $struct_name, $wrapper, $static_name,
{ $object $body }, { $($size)* }, { $($compact)* }, $($rest)*);
};
(@parse $struct_name: ty, $wrapper: ident, $static_name: ident,
{ $($mark: tt)* }, { }, { $($compact: tt)* },
, size($object: ident) $body: block $($rest: tt)*) => {
$crate::wrappable_struct!(@parse $struct_name, $wrapper, $static_name,
{ $($mark)* }, { $object $body }, { $($compact)* }, $($rest)*);
};
(@parse $struct_name: ty, $wrapper: ident, $static_name: ident,
{ $($mark: tt)* }, { $($size: tt)* }, { },
, compact($object: ident) $body: block $($rest: tt)*) => {
$crate::wrappable_struct!(@parse $struct_name, $wrapper, $static_name,
{ $($mark)* }, { $($size)* }, { $object $body }, $($rest)*);
};
(@mark_function_pointer { }) => {
None as Option<extern "C" fn(*mut $crate::types::c_void)>
};
(@mark_function_pointer { $object: ident $body: block }) => {
Some(Self::mark as extern "C" fn(*mut $crate::types::c_void))
};
(@mark_function_definition $struct_name: ty, { }) => {};
(@mark_function_definition $struct_name: ty, { $object: ident $body: block }) => {
pub extern "C" fn mark(data: *mut $crate::types::c_void) {
let mut data = unsafe { (data as *mut $struct_name).as_mut() };
if let Some(ref mut $object) = data {
$body
}
}
};
(@size_function_pointer { }) => {
None as Option<extern "C" fn(*const $crate::types::c_void) -> $crate::types::size_t>
};
(@size_function_pointer { $object: ident $body: block }) => {
Some(Self::size as extern "C" fn(*const $crate::types::c_void) -> $crate::types::size_t)
};
(@size_function_definition $struct_name: ty, { }) => {};
(@size_function_definition $struct_name: ty, { $object: ident $body: block }) => {
pub extern "C" fn size(data: *const $crate::types::c_void) -> $crate::types::size_t {
let data = unsafe { (data as *const $struct_name).as_ref() };
match data {
Some($object) => {
let size: usize = $body;
size as $crate::types::size_t
}
None => 0,
}
}
};
(@compact_function_pointer { }) => {
::std::ptr::null_mut()
};
(@compact_function_pointer { $object: ident $body: block }) => {
Self::compact as extern "C" fn(*mut $crate::types::c_void) as *mut $crate::types::c_void
};
(@compact_function_definition $struct_name: ty, { }) => {};
(@compact_function_definition $struct_name: ty, { $object: ident $body: block }) => {
pub extern "C" fn compact(data: *mut $crate::types::c_void) {
let mut data = unsafe { (data as *mut $struct_name).as_mut() };
if let Some(ref mut $object) = data {
$body
}
}
};
(@define $struct_name: ty, $wrapper: ident, $static_name: ident,
{ $($mark: tt)* }, { $($size: tt)* }, { $($compact: tt)* }) => {
pub struct $wrapper<T> {
data_type: $crate::types::DataType,
_marker: ::std::marker::PhantomData<T>,
}
::lazy_static::lazy_static! {
pub static ref $static_name: $wrapper<$struct_name> = $wrapper::new();
}
impl<T> $wrapper<T> {
fn new() -> $wrapper<T> {
let name = concat!("Rutie/", stringify!($struct_name));
let name = $crate::util::str_to_cstring(name);
let dcompact = $crate::wrappable_struct!(@compact_function_pointer { $($compact)* });
let reserved_bytes: [*mut $crate::types::c_void; 2] = [dcompact, ::std::ptr::null_mut()];
let dmark = $crate::wrappable_struct!(@mark_function_pointer { $($mark)* });
let dsize = $crate::wrappable_struct!(@size_function_pointer { $($size)* });
let data_type = $crate::types::DataType {
wrap_struct_name: name.into_raw(),
parent: ::std::ptr::null(),
data: ::std::ptr::null_mut(),
flags: $crate::types::Value::from(0),
function: $crate::types::DataTypeFunction {
dmark: dmark,
dfree: Some($crate::typed_data::free::<T>),
dsize: dsize,
reserved: reserved_bytes,
},
};
$wrapper {
data_type: data_type,
_marker: ::std::marker::PhantomData,
}
}
$crate::wrappable_struct!(@mark_function_definition $struct_name, { $($mark)* });
$crate::wrappable_struct!(@size_function_definition $struct_name, { $($size)* });
$crate::wrappable_struct!(@compact_function_definition $struct_name, { $($compact)* });
}
unsafe impl<T> Sync for $wrapper<T> {}
impl<T> $crate::typed_data::DataTypeWrapper<T> for $wrapper<T> {
fn data_type(&self) -> &$crate::types::DataType {
&self.data_type
}
}
};
($struct_name: ty, $wrapper: ident, $static_name: ident $($tail: tt)*) => {
$crate::wrappable_struct!(@parse $struct_name, $wrapper, $static_name, { }, { }, { }, $($tail)*);
};
}
#[macro_export]
macro_rules! eval {
($string_arg:expr) => {{
$crate::VM::eval($string_arg)
}};
($string_arg:expr, $binding_arg:expr) => {{
let eval_str: $crate::AnyObject = $crate::RString::from($string_arg).into();
let bndng: $crate::AnyObject = $binding_arg.into();
let arguments = &[eval_str, bndng];
$crate::Class::from_existing("Kernel").protect_send("eval", arguments)
}};
($string_arg:expr, $binding_arg:expr, $filename:expr) => {{
let eval_str: $crate::AnyObject = $crate::RString::from($string_arg).into();
let bndng: $crate::AnyObject = $binding_arg.into();
let filename: $crate::AnyObject = $crate::RString::from($filename).into();
let arguments = &[eval_str, bndng, filename];
$crate::Class::from_existing("Kernel").protect_send("eval", arguments)
}};
($string_arg:expr, $binding_arg:expr, $filename:expr, $linenumber:expr) => {{
let eval_str: $crate::AnyObject = $crate::RString::from($string_arg).into();
let bndng: $crate::AnyObject = $binding_arg.into();
let filename: $crate::AnyObject = $crate::RString::from($filename).into();
let linenumber: $crate::AnyObject = $crate::Integer::from($linenumber as i64).into();
let arguments = &[eval_str, bndng, filename, linenumber];
$crate::Class::from_existing("Kernel").protect_send("eval", arguments)
}};
}
#[cfg(test)]
mod tests {
use crate::{AnyObject, Array, Class, Exception, Fixnum, Object, RString, GC, VM};
crate::class!(RutieDslSplat);
crate::methods!(
RutieDslSplat,
rtself,
fn dsl_rest_only(*rest) -> Array {
rest
}
fn dsl_first_and_rest(first: Fixnum, *rest,) -> Array {
let mut result = Array::new();
result.push(first.map(|n| n.to_any_object()).unwrap_or_else(|e| e.to_any_object()));
result.push(rest);
result
}
fn dsl_plain(a: Fixnum, b: Fixnum) -> Fixnum {
Fixnum::new(a.unwrap().to_i64() + b.unwrap().to_i64())
}
);
crate::unsafe_methods!(
RutieDslSplat,
rtself,
fn dsl_unsafe_two(a: Fixnum, b: Fixnum) -> Fixnum {
Fixnum::new(a.to_i64() * b.to_i64())
}
);
pub struct Payload {
objects: Vec<AnyObject>,
extra: usize,
}
crate::wrappable_struct! {
Payload,
PayloadWrapper,
PAYLOAD_WRAPPER,
size(data) {
std::mem::size_of::<Payload>() + data.extra
},
mark(data) {
for object in &data.objects {
GC::mark(object);
}
},
}
pub struct Movable {
strings: Vec<RString>,
}
static MOVED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
crate::wrappable_struct! {
Movable,
MovableWrapper,
MOVABLE_WRAPPER,
compact(data) {
for string in data.strings.iter_mut() {
let moved = GC::location(string);
if moved.value().value != string.value().value {
MOVED.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
*string = moved;
}
},
mark(data) {
for string in &data.strings {
GC::mark_movable(string);
}
},
}
#[inline(never)]
fn movable_object() -> AnyObject {
let was_disabled = GC::disable();
let strings = (0..200)
.map(|i| RString::new_utf8(&format!("s{}", i)))
.collect();
let object =
Class::new("RutieDslMovable", None).wrap_data(Movable { strings }, &*MOVABLE_WRAPPER);
if !was_disabled {
GC::enable();
}
object
}
pub struct Plain;
crate::wrappable_struct!(Plain, PlainWrapper, PLAIN_WRAPPER);
fn eval_array(code: &str) -> Array {
VM::eval(code).unwrap().try_convert_to::<Array>().unwrap()
}
#[test]
fn test_methods_splat() {
crate::on_ruby_thread(|| {
Class::new("RutieDslSplat", None).define(|klass| {
klass.def("rest_only", dsl_rest_only);
klass.def("first_and_rest", dsl_first_and_rest);
klass.def("plain", dsl_plain);
klass.def("unsafe_two", dsl_unsafe_two);
});
assert_eq!(eval_array("RutieDslSplat.new.rest_only").length(), 0);
assert_eq!(
eval_array("RutieDslSplat.new.rest_only(1, 2, 3)").length(),
3
);
let result = eval_array("RutieDslSplat.new.first_and_rest(1, :a, :b)");
assert_eq!(result.at(0).try_convert_to::<Fixnum>(), Ok(Fixnum::new(1)));
assert_eq!(result.at(1).try_convert_to::<Array>().unwrap().length(), 2);
let result = eval_array("RutieDslSplat.new.first_and_rest");
assert!(Class::argument_error().case_equals(&result.at(0)));
assert_eq!(result.at(1).try_convert_to::<Array>().unwrap().length(), 0);
let sum = VM::eval("RutieDslSplat.new.plain(2, 3)").unwrap();
assert_eq!(sum.try_convert_to::<Fixnum>(), Ok(Fixnum::new(5)));
let result = eval_array("RutieDslSplat.new.rest_only(1, mode: :fast)");
assert_eq!(result.length(), 2);
assert!(result.at(1).try_convert_to::<crate::Hash>().is_ok());
});
}
#[test]
fn test_unsafe_methods() {
crate::on_ruby_thread(|| {
Class::new("RutieDslSplat", None).define(|klass| {
klass.def("unsafe_two", dsl_unsafe_two);
});
let product = VM::eval("RutieDslSplat.new.unsafe_two(6, 7)").unwrap();
assert_eq!(product.try_convert_to::<Fixnum>(), Ok(Fixnum::new(42)));
let product = VM::eval("RutieDslSplat.new.unsafe_two(6, 7, 8)").unwrap();
assert_eq!(product.try_convert_to::<Fixnum>(), Ok(Fixnum::new(42)));
});
}
#[test]
fn test_wrappable_struct_mark_and_size() {
crate::on_ruby_thread(|| {
VM::init_loadpath();
VM::protect_require("objspace").unwrap();
let class = Class::new("RutieDslPayload", None);
GC::disable();
let payload = Payload {
objects: vec![RString::new_utf8("kept alive").to_any_object()],
extra: 1 << 20,
};
let object: AnyObject = class.wrap_data(payload, &*PAYLOAD_WRAPPER);
GC::enable();
GC::start();
let kept = object.get_data(&*PAYLOAD_WRAPPER).objects[0].clone();
assert_eq!(
kept.try_convert_to::<RString>().unwrap().to_str(),
"kept alive"
);
let memsize = |object: &AnyObject| {
Class::from_existing("ObjectSpace")
.protect_send("memsize_of", &[object.clone()])
.unwrap()
.try_convert_to::<Fixnum>()
.unwrap()
.to_i64()
};
assert!(memsize(&object) >= 1 << 20);
let plain: AnyObject = class.wrap_data(Plain, &*PLAIN_WRAPPER);
assert!(memsize(&plain) < 1 << 20);
});
}
#[test]
fn test_wrappable_struct_compact() {
crate::on_ruby_thread(|| {
let object = movable_object();
let compacted = VM::eval(
"begin
GC.verify_compaction_references(toward: :empty, double_heap: true)
true
rescue NotImplementedError
false
end",
)
.unwrap();
let strings = &object.get_data(&*MOVABLE_WRAPPER).strings;
for (i, string) in strings.iter().enumerate() {
assert_eq!(string.to_str(), format!("s{}", i));
}
if compacted.value().is_true() {
assert!(MOVED.load(std::sync::atomic::Ordering::SeqCst) > 0);
}
});
}
}