diff --git a/crates/libcruby-sys/src/lib.rs b/crates/libcruby-sys/src/lib.rs index 380b9c5d..4da59c25 100644 --- a/crates/libcruby-sys/src/lib.rs +++ b/crates/libcruby-sys/src/lib.rs @@ -54,15 +54,15 @@ extern "C" { #[link_name = "HELIX_RSTRING_PTR"] pub fn RSTRING_PTR(string: VALUE) -> c_string; + #[link_name = "HELIX_rb_utf8_str_new"] + pub fn rb_utf8_str_new(string: c_string, len: libc::c_long) -> VALUE; + #[link_name = "HELIX_RARRAY_LEN"] pub fn RARRAY_LEN(array: VALUE) -> isize; #[link_name = "HELIX_RARRAY_PTR"] pub fn RARRAY_PTR(array: VALUE) -> void_ptr; - #[link_name = "helix_rb_utf8_str_new"] - pub fn rb_utf8_str_new(string: c_string, len: libc::c_long) -> VALUE; - #[link_name = "HELIX_RB_TYPE_P"] pub fn RB_TYPE_P(val: VALUE, rb_type: isize) -> bool; @@ -88,6 +88,7 @@ extern "C" { pub fn rb_define_module_under(namespace: VALUE, name: c_string) -> VALUE; pub fn rb_define_class(name: c_string, superclass: VALUE) -> VALUE; pub fn rb_define_class_under(namespace: VALUE, name: c_string, superclass: VALUE) -> VALUE; + pub fn rb_define_alloc_func(klass: VALUE, func: extern "C" fn(klass: VALUE) -> VALUE); pub fn rb_define_method(class: VALUE, name: c_string, func: void_ptr, arity: isize); pub fn rb_intern(string: c_string) -> ID; pub fn rb_jump_tag(state: RubyException) -> !; @@ -95,4 +96,13 @@ extern "C" { arg: void_ptr, state: *mut RubyException) -> VALUE; + + #[link_name = "HELIX_Data_Wrap_Struct"] + pub fn Data_Wrap_Struct(klass: VALUE, mark: extern "C" fn(void_ptr), free: extern "C" fn(void_ptr), data: void_ptr) -> VALUE; + + #[link_name = "HELIX_Data_Get_Struct_Value"] + pub fn Data_Get_Struct_Value(obj: VALUE) -> void_ptr; + + #[link_name = "HELIX_Data_Set_Struct_Value"] + pub fn Data_Set_Struct_Value(obj: VALUE, data: void_ptr); } diff --git a/examples/console/Cargo.lock b/examples/console/Cargo.lock index ec4f3c55..ef9bf943 100644 --- a/examples/console/Cargo.lock +++ b/examples/console/Cargo.lock @@ -3,33 +3,36 @@ name = "console" version = "0.1.0" dependencies = [ "helix 0.1.0", - "libc 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "libcruby-sys 0.1.0", ] [[package]] name = "cslice" -version = "0.1.0" -source = "git+https://github.com/rustbridge/neon#f28e32c77a04c3b79834324290a1b9cabcd50230" +version = "0.1.1" +source = "git+https://github.com/rustbridge/neon#e27e5651cc9a8151f58c9a6dafda2ce251456034" [[package]] name = "helix" version = "0.1.0" dependencies = [ - "cslice 0.1.0 (git+https://github.com/rustbridge/neon)", - "libc 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cslice 0.1.1 (git+https://github.com/rustbridge/neon)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "libcruby-sys 0.1.0", ] [[package]] name = "libc" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libcruby-sys" version = "0.1.0" dependencies = [ - "libc 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[metadata] +"checksum cslice 0.1.1 (git+https://github.com/rustbridge/neon)" = "" +"checksum libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "39dfaaa0f4da0f1a06876c5d94329d739ad0150868069cc235f1ddf80a0480e7" diff --git a/examples/duration/.gitignore b/examples/duration/.gitignore new file mode 100644 index 00000000..4268bc75 --- /dev/null +++ b/examples/duration/.gitignore @@ -0,0 +1,3 @@ +target +*.bundle +*.gem diff --git a/examples/duration/Cargo.toml b/examples/duration/Cargo.toml new file mode 100644 index 00000000..ff3445c5 --- /dev/null +++ b/examples/duration/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "duration" +version = "0.1.0" +authors = ["Godfrey Chan "] + +[lib] + +crate-type = ["staticlib"] + +[dependencies] + +libc = "*" + +[dependencies.libcruby-sys] + +path = "../../crates/libcruby-sys" + +[dependencies.helix] + +path = "../.." + +[profile.release] + +lto = true diff --git a/examples/duration/Gemfile b/examples/duration/Gemfile new file mode 100644 index 00000000..674ce24e --- /dev/null +++ b/examples/duration/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gemspec + +gem 'helix_runtime', path: '../../ruby' diff --git a/examples/duration/Gemfile.lock b/examples/duration/Gemfile.lock new file mode 100644 index 00000000..04304e5c --- /dev/null +++ b/examples/duration/Gemfile.lock @@ -0,0 +1,29 @@ +PATH + remote: ../../ruby + specs: + helix_runtime (0.5.0) + +PATH + remote: . + specs: + duration (0.1.0) + helix_runtime + +GEM + remote: https://rubygems.org/ + specs: + minitest (5.8.3) + rake (10.5.0) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.11) + duration! + helix_runtime! + minitest (~> 5.0) + rake (~> 10.0) + +BUNDLED WITH + 1.12.5 diff --git a/examples/duration/Rakefile b/examples/duration/Rakefile new file mode 100644 index 00000000..1e983199 --- /dev/null +++ b/examples/duration/Rakefile @@ -0,0 +1,34 @@ +require 'rake/clean' +require 'rake/testtask' +require 'bundler/setup' +require 'bundler/gem_tasks' + +directory 'target' +directory 'lib/duration' + +task :cargo_build do + sh "cargo build --release" +end +CLEAN.include('target') + +file "lib/duration/native.bundle" => ['lib/duration', :cargo_build] do + sh "gcc -Wl,-force_load,target/release/libduration.a --shared -Wl,-undefined,dynamic_lookup -o lib/duration/native.bundle" +end +CLOBBER.include('lib/duration/native.bundle') + +task :irb => "lib/duration/native.bundle" do + exec "irb -Ilib -rduration" +end + +task :benchmark => "lib/duration/native.bundle" do + exec "ruby -Ilib benchmark.rb" +end + +Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList['test/**/*_test.rb'] +end + +task :test => "lib/duration/native.bundle" +task :default => :test diff --git a/examples/duration/duration.gemspec b/examples/duration/duration.gemspec new file mode 100644 index 00000000..c4addb51 --- /dev/null +++ b/examples/duration/duration.gemspec @@ -0,0 +1,32 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'duration/version' + +Gem::Specification.new do |spec| + spec.name = "duration" + spec.version = Duration::VERSION + spec.authors = ["Godfrey Chan"] + spec.email = ["godfreykfc@gmail.com"] + + spec.summary = "Rust ActiveSupport::Duration" + spec.license = "MIT" + + # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or + # delete this section to allow pushing this gem to any host. + if spec.respond_to?(:metadata) + spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" + else + raise "RubyGems 2.0 or newer is required to protect against public gem pushes." + end + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_runtime_dependency "helix_runtime" + spec.add_development_dependency "bundler", "~> 1.11" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "minitest", "~> 5.0" +end diff --git a/examples/duration/lib/duration.rb b/examples/duration/lib/duration.rb new file mode 100644 index 00000000..565f1337 --- /dev/null +++ b/examples/duration/lib/duration.rb @@ -0,0 +1,2 @@ +require "helix_runtime" +require "duration/native" diff --git a/examples/duration/lib/duration/version.rb b/examples/duration/lib/duration/version.rb new file mode 100644 index 00000000..e0d68344 --- /dev/null +++ b/examples/duration/lib/duration/version.rb @@ -0,0 +1,3 @@ +class Duration + VERSION = "0.1.0" +end diff --git a/examples/duration/src/lib.rs b/examples/duration/src/lib.rs new file mode 100644 index 00000000..987e693d --- /dev/null +++ b/examples/duration/src/lib.rs @@ -0,0 +1,18 @@ +#[macro_use] +extern crate helix; + +declare_types! { + class Duration { + struct { + inner: u32 + } + + def initialize() -> Duration { + Duration { inner: 0 } + } + + def inspect(self) -> String { + format!("{}", self.inner) + } + } +} diff --git a/examples/duration/test/duration_test.rb b/examples/duration/test/duration_test.rb new file mode 100644 index 00000000..369f0aba --- /dev/null +++ b/examples/duration/test/duration_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class DurationTest < Minitest::Test + def test_blank + assert_equal 'zomg', Duration.new.inspect + end +end diff --git a/examples/duration/test/test_helper.rb b/examples/duration/test/test_helper.rb new file mode 100644 index 00000000..f462e49c --- /dev/null +++ b/examples/duration/test/test_helper.rb @@ -0,0 +1,4 @@ +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +require 'duration' + +require 'minitest/autorun' diff --git a/ruby/ext/helix_runtime/native/helix_runtime.c b/ruby/ext/helix_runtime/native/helix_runtime.c index 2f3f73b5..6362b794 100644 --- a/ruby/ext/helix_runtime/native/helix_runtime.c +++ b/ruby/ext/helix_runtime/native/helix_runtime.c @@ -35,10 +35,28 @@ VALUE HELIX_FIX2INT(VALUE v) { return FIX2INT(v); } -VALUE helix_rb_utf8_str_new(const char* str, long len) { +VALUE HELIX_rb_utf8_str_new(const char* str, long len) { return rb_utf8_str_new(str, len); } +VALUE HELIX_Data_Wrap_Struct(VALUE klass, HELIX_RUBY_DATA_FUNC mark, HELIX_RUBY_DATA_FUNC free, void* data) { + return Data_Wrap_Struct(klass, mark, free, data); +} + +void* HELIX_Data_Get_Struct_Value(VALUE obj) { + void* data; + Data_Get_Struct(obj, void*, data); + return data; +} + +void HELIX_Data_Set_Struct_Value(VALUE obj, void* data) { + DATA_PTR(obj) = data; +} + +// void HELIX_rb_define_alloc_func(VALUE klass, HELIX_rb_alloc_func_t func) { +// rb_define_alloc_func(klass, func); +// } + int HELIX_TYPE(VALUE v) { return TYPE(v); } diff --git a/ruby/ext/helix_runtime/native/helix_runtime.h b/ruby/ext/helix_runtime/native/helix_runtime.h index fd53f17c..6ba69dd1 100644 --- a/ruby/ext/helix_runtime/native/helix_runtime.h +++ b/ruby/ext/helix_runtime/native/helix_runtime.h @@ -21,7 +21,16 @@ int HELIX_TYPE(VALUE v); VALUE HELIX_INT2FIX(int c_int); VALUE HELIX_FIX2INT(VALUE fix); -VALUE helix_rb_utf8_str_new(const char* str, long len); +VALUE HELIX_rb_utf8_str_new(const char* str, long len); + +// typedef VALUE (*HELIX_rb_alloc_func_t)(VALUE); +// void HELIX_rb_define_alloc_func(VALUE klass, HELIX_rb_alloc_func_t func); + +typedef void (*HELIX_RUBY_DATA_FUNC)(void*); + +VALUE HELIX_Data_Wrap_Struct(VALUE klass, HELIX_RUBY_DATA_FUNC mark, HELIX_RUBY_DATA_FUNC free, void* data); +void* HELIX_Data_Get_Struct_Value(VALUE obj); +void HELIX_Data_Set_Struct_Value(VALUE obj, void* data); extern int HELIX_T_NONE; extern int HELIX_T_NIL; diff --git a/ruby/spec/helix_runtime_spec.rb b/ruby/spec/helix_runtime_spec.rb index 4d0bd1d3..9f5d6921 100644 --- a/ruby/spec/helix_runtime_spec.rb +++ b/ruby/spec/helix_runtime_spec.rb @@ -113,5 +113,52 @@ module TYPES # expect(Dummy.TYPE({})).to eq(Dummy::T_HASH) # expect(Dummy.TYPE([])).to_not eq(Dummy::T_OBJECT) # end + + describe "helix_rb_utf8_str_new" do + it "allocates a new string" do + str1 = "hello world" + str2 = Dummy.STR2STR(str1, 5) + + expect(str2).to eq("hello") + + str1[0...5] = "goodbye" + + expect(str1).to eq("goodbye world") + expect(str2).to eq("hello") + + str2 << " world!" + + expect(str1).to eq("goodbye world") + expect(str2).to eq("hello world!") + end + end + + describe "Data_{Wrap,Get,Set}_Struct" do + it "can allocate then change the data" do + wrapper = Dummy::Wrapper.new + + expect(Dummy.get_data(wrapper)).to eq(0) + + ptr = Dummy.get_data_ptr(wrapper) + + expect(Dummy.set_data(wrapper, 1)).to eq(1) + + expect(Dummy.get_data(wrapper)).to eq(1) + expect(Dummy.get_data_ptr(wrapper)).to eq(ptr) + end + + it "can allocate then replace the data" do + wrapper = Dummy::Wrapper.new + + expect(Dummy.get_data(wrapper)).to eq(0) + + ptr = Dummy.get_data_ptr(wrapper) + + expect(Dummy.replace_data(wrapper, 1)).to eq(1) + + expect(Dummy.get_data(wrapper)).to eq(1) + expect(Dummy.get_data_ptr(wrapper)).not_to eq(ptr) + end + end end diff --git a/ruby/spec/support/dummy/ext/dummy/dummy.c b/ruby/spec/support/dummy/ext/dummy/dummy.c index db14c2ce..b8aeb50e 100644 --- a/ruby/spec/support/dummy/ext/dummy/dummy.c +++ b/ruby/spec/support/dummy/ext/dummy/dummy.c @@ -48,9 +48,56 @@ static VALUE TEST_FIX2INT(VALUE _self, VALUE val) { return INT2FIX(HELIX_FIX2INT(val)); } +static VALUE TEST_STR2STR(VALUE _self, VALUE str, VALUE len) { + return HELIX_rb_utf8_str_new(RSTRING_PTR(str), FIX2LONG(len)); +} + +void deallocate_wrapper(void* num) { + free(num); +} + +VALUE allocate_wrapper(VALUE klass) { + int* num = malloc(sizeof(int)); + + *num = 0; + + return HELIX_Data_Wrap_Struct(klass, NULL, deallocate_wrapper, num); +} + +static VALUE TEST_get_data(VALUE _self, VALUE wrapped) { + int* num = HELIX_Data_Get_Struct_Value(wrapped); + return INT2FIX(*num); +} + +static VALUE TEST_get_data_ptr(VALUE _self, VALUE wrapped) { + int* num = HELIX_Data_Get_Struct_Value(wrapped); + return INT2FIX(num); +} + +static VALUE TEST_set_data(VALUE _self, VALUE wrapped, VALUE value) { + int* num = HELIX_Data_Get_Struct_Value(wrapped); + *num = FIX2INT(value); + return value; +} + +static VALUE TEST_replace_data(VALUE _self, VALUE wrapped, VALUE value) { + int* old = HELIX_Data_Get_Struct_Value(wrapped); + int* new = malloc(sizeof(int)); + + *new = FIX2INT(value); + + HELIX_Data_Set_Struct_Value(wrapped, new); + + free(old); + + return value; +} + void Init_dummy() { VALUE mDummy = rb_define_module("Dummy"); VALUE mRuby = rb_define_module_under(mDummy, "Ruby"); + VALUE cWrapper = rb_define_class_under(mDummy, "Wrapper", rb_cObject); + rb_define_alloc_func(cWrapper, allocate_wrapper); EXPORT_VALUE(Qtrue); EXPORT_VALUE(Qfalse); @@ -93,4 +140,11 @@ void Init_dummy() { EXPORT_FUNC(TYPE, 1); EXPORT_FUNC(INT2FIX, 1); EXPORT_FUNC(FIX2INT, 1); + + EXPORT_FUNC(STR2STR, 2); + + EXPORT_FUNC(get_data, 1); + EXPORT_FUNC(get_data_ptr, 1); + EXPORT_FUNC(set_data, 2); + EXPORT_FUNC(replace_data, 2); } diff --git a/src/class_definition.rs b/src/class_definition.rs index 4e3edd29..e4c74a6f 100644 --- a/src/class_definition.rs +++ b/src/class_definition.rs @@ -24,6 +24,12 @@ impl ClassDefinition { ClassDefinition { class: Class(raw_class) } } + pub fn wrapped(name: &str, alloc_func: extern "C" fn(klass: sys::VALUE) -> sys::VALUE) -> ClassDefinition { + let raw_class = unsafe { sys::rb_define_class(CString::new(name).unwrap().as_ptr(), sys::rb_cObject) }; + unsafe { sys::rb_define_alloc_func(raw_class, alloc_func) }; + ClassDefinition { class: Class(raw_class) } + } + pub fn reopen(name: &str) -> ClassDefinition { let raw_class = unsafe { let class_id = sys::rb_intern(CString::new(name).unwrap().as_ptr()); diff --git a/src/macros.rs b/src/macros.rs index e435ce21..37bb5227 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -22,24 +22,40 @@ macro_rules! declare_types { #[doc(hidden)] #[macro_export] macro_rules! define_struct { - (true $(#[$attr:meta])* $cls:ident) => ( + (true $(#[$attr:meta])* $cls:ident $($ident:ident : $ty:ty)*) => ( #[derive(Copy, Clone, Debug)] #[repr(C)] $(#[$attr])* - pub struct $cls($crate::sys::VALUE); + pub struct $cls { + $($ident : $ty),* + } ); - (false $(#[$attr:meta])* $cls:ident) => ( + (false $(#[$attr:meta])* $cls:ident $($ident:ident : $ty:ty)*) => ( #[derive(Copy, Clone, Debug)] #[repr(C)] $(#[$attr])* - struct $cls($crate::sys::VALUE); + struct $cls { + $($ident : $ty),* + } ); } #[doc(hidden)] #[macro_export] macro_rules! define_class { + { #![reopen(false)] #![pub($is_pub:tt)] $(#[$attr:meta])* class $cls:ident { struct { $($ident:ident : $ty:ty),* } def initialize() -> $initty:ty { $($initbody:tt)* } $($body:tt)* } $($rest:tt)* } => { + define_struct!($(#[$attr:meta])* $is_pub $cls $($ident : $ty),*); + wrapped_class_definition! { + $cls ; + fn __initialize__() -> $cls { $($initbody:tt)* } ; + () ; + () ; + $($body)* + } + declare_types! { $($rest)* } + }; + { #![reopen(false)] #![pub($is_pub:tt)] $(#[$attr:meta])* class $cls:ident { $($body:tt)* } $($rest:tt)* } => { define_struct!($(#[$attr:meta])* $is_pub $cls); class_definition! { $cls ; () ; () ; $($body)* } @@ -55,6 +71,102 @@ macro_rules! define_class { } +#[doc(hidden)] +#[macro_export] +macro_rules! wrapped_class_definition { + { $cls:ident ; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; defn $name:ident ; $self_arg:tt ; ($($arg:ident : $argty:ty),*) ; $body:block ; $ret:ty ; $($rest:tt)* } => { + wrapped_class_definition! { + $cls ; + $($initbody:tt)* ; + ($($mimpl)* pub fn $name($self_arg, $($arg : $argty),*) -> $ret $body) ; + ($($mdef)* { + extern "C" fn __ruby_method__(rb_self: $crate::sys::VALUE, $($arg : $crate::sys::VALUE),*) -> $crate::sys::VALUE { + let checked = __checked_call__(rb_self, $($arg),*); + match checked { + Ok(val) => $crate::ToRuby::to_ruby(val), + Err(err) => { println!("TYPE ERROR: {:?}", err); unsafe { $crate::sys::Qnil } } + } + } + + fn __checked_call__(rb_self: $crate::sys::VALUE, $($arg : $crate::sys::VALUE),*) -> Result<$ret, ::std::ffi::CString> { + #[allow(unused_imports)] + use $crate::{ToRust}; + + let rust_self = $cls::from_checked_rb_value(&mut rb_self); + + $( + let $arg = try!($crate::UncheckedValue::<$argty>::to_checked($arg)); + )* + + $( + let $arg = $crate::ToRust::to_rust($arg); + )* + + Ok(rust_self.$name($($arg),*)) + } + + let name = stringify!($name); + let arity = method_arity!($($arg),*); + let method = __ruby_method__ as *const $crate::libc::c_void; + + $crate::MethodDefinition::new(name, method, arity) + }) ; + $($rest)* + } + }; + + { $cls:ident ; $($initbody:tt)* ; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt , $($arg:ident : $argty:ty),* ) -> $ret:ty { $($body:tt)* } $($rest:tt)* } => { + wrapped_class_definition! { $cls; $($initbody:tt)* ; ($($mimpl)*) ; ($($mdef)*) ; defn $name ; $self_arg ; ($($arg : $argty),*) ; { $($body)* } ; $ret ; $($rest)* } + }; + + { $cls:ident ; $($initbody:tt)* ; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt , $($arg:ident : $argty:ty),* ) $body:block $($rest:tt)* } => { + wrapped_class_definition! { $cls; $($initbody:tt)* ; ($($mimpl)*) ; ($($mdef)*) ; defn $name ; $self_arg ; ($($arg : $argty),*) ; $body ; () ; $($rest)* } + }; + + { $cls:ident ; $($initbody:tt)* ; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt ) -> $ret:ty { $($body:tt)* } $($rest:tt)* } => { + wrapped_class_definition! { $cls; $($initbody:tt)* ; ($($mimpl)*) ; ($($mdef)*) ; defn $name ; $self_arg ; () ; { $($body)* } ; $ret ; $($rest)* } + }; + + { $cls:ident ; $($initbody:tt)* ; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt ) $body:block $($rest:tt)* } => { + wrapped_class_definition! { $cls; $($initbody:tt)* ; ($($mimpl)*) ; ($($mdef)*) ; defn $name ; $self_arg ; () ; $body ; () ; $($rest)* } + }; + + ( $cls:ident ; $($initbody:tt)* ; ($($mimpl:tt)*) ; ($($mdef:block)*) ; ) => { + item! { + impl $cls { + fn from_checked_rb_value(value: &mut $crate::sys::VALUE) -> &mut $cls { + unsafe { std::mem::transmute($crate::sys::Data_Get_Struct_Value(rb_self)) } + } + + + $($mimpl)* + } + } + + init! { + extern "C" fn __free__(klass: Option>) {} + + extern "C" fn __alloc__(klass: $crate::sys::VALUE) -> $crate::sys::VALUE { + let instance = $crate::sys::Data_Wrap_Struct(klass, ::std::ptr::null(), __free__, ::std::ptr::null()); + + // FIXME: this should really be called during Ruby's initialize, with arguments + __initialize__(instance); + + instance + } + + extern "C" fn __initialize__(rb_self: $crate::sys::VALUE) { + let data = Box::new($cls::___initialize()); + ::std::mem::forget(data); + $crate::sys::Data_Set_Struct_Value(rb_self, ::std::mem::transmute(data)); + } + + + $crate::ClassDefinition::wrapped(stringify!($cls))$(.define_method($mdef))*; + } + }; +} + #[doc(hidden)] #[macro_export] macro_rules! class_definition { @@ -182,7 +294,6 @@ macro_rules! reopen_class_definition { reopen_class_definition! { $cls; ($($mimpl)*) ; ($($mdef)*) ; defn $name ; $self_arg ; () ; $body ; () ; $($rest)* } }; - ( $cls:ident ; ($($mimpl:tt)*) ; ($($mdef:block)*) ; ) => { item! { impl $cls {