|
require "http/client" |
|
|
|
lib LibRuby |
|
type VALUE = Void* |
|
type ID = Void* |
|
type RUBY_DATA_FUNC = Void* -> Void |
|
|
|
$rb_cObject : VALUE |
|
|
|
struct RBasic |
|
flags : VALUE |
|
klass : VALUE |
|
end |
|
|
|
struct RData |
|
basic : RBasic |
|
mark : RUBY_DATA_FUNC |
|
free : RUBY_DATA_FUNC |
|
data : Void* |
|
end |
|
|
|
fun rb_define_global_function(name : UInt8*, f : Void*, args : Int32) |
|
fun rb_eval_string(str : UInt8*) : VALUE |
|
fun rb_str_new_cstr(str : UInt8*) : VALUE |
|
fun rb_define_class(name : UInt8*, parent : VALUE) : VALUE |
|
fun rb_define_method(clazz : VALUE, name : UInt8*, f : Void*, args : Int32) |
|
fun rb_any_to_s(v : VALUE) : VALUE |
|
fun rb_string_value_cstr(v : VALUE*) : UInt8* |
|
fun rb_intern(name : UInt8*) : ID |
|
fun rb_funcall(obj : VALUE, func : ID, args : Int32, ...) : VALUE |
|
fun rb_iv_set(obj : VALUE, name : UInt8*, value : VALUE) : VALUE |
|
fun rb_iv_get(obj : VALUE, name : UInt8*) : VALUE |
|
fun rb_data_object_wrap(klass : VALUE, ptr : Void*, mark : RUBY_DATA_FUNC, free : RUBY_DATA_FUNC) : VALUE |
|
fun rb_define_alloc_func(klass : VALUE, alloc : VALUE -> VALUE) |
|
end |
|
|
|
|
|
struct Int |
|
def to_ruby |
|
Pointer(Void).new((self << 1) | 1).as(LibRuby::VALUE) |
|
end |
|
end |
|
|
|
struct Nil |
|
def to_ruby |
|
Pointer(Void).new(8_u64).as(LibRuby::VALUE) |
|
end |
|
end |
|
|
|
struct Bool |
|
def to_ruby |
|
Pointer(Void).new(self ? 20_u64 : 0_u64).as(LibRuby::VALUE) |
|
end |
|
end |
|
|
|
class String |
|
def to_ruby |
|
LibRuby.rb_str_new_cstr(self) |
|
end |
|
|
|
def self.from_ruby(str : LibRuby::VALUE) |
|
Ruby::Value.new(str).to_s |
|
end |
|
end |
|
|
|
module Ruby |
|
ID_TO_S = LibRuby.rb_intern("to_s") |
|
|
|
struct Class |
|
def initialize(name) |
|
@class = LibRuby.rb_define_class(name, LibRuby.rb_cObject) |
|
end |
|
|
|
def allocate(f) |
|
LibRuby.rb_define_alloc_func(@class, f) |
|
end |
|
|
|
def def(name, args, f) |
|
LibRuby.rb_define_method(@class, name, f.pointer, args) |
|
end |
|
|
|
def to_unsafe |
|
@class |
|
end |
|
|
|
def to_ruby |
|
@class |
|
end |
|
end |
|
|
|
struct Value |
|
@value : LibRuby::VALUE |
|
|
|
def initialize(@value : LibRuby::VALUE) |
|
end |
|
|
|
def to_unsafe |
|
@value |
|
end |
|
|
|
def to_s |
|
str = LibRuby.rb_funcall(self, ID_TO_S, 0) |
|
String.new(LibRuby.rb_string_value_cstr(pointerof(str))) |
|
end |
|
end |
|
|
|
def self.global_def(name, args, f) |
|
LibRuby.rb_define_global_function(name, f.pointer, args) |
|
end |
|
end |
|
|
|
# Keep references to a set of objects sent to Ruby, to prevent them from being GC'ed |
|
Roots = Set(Void*).new |
|
|
|
# Generic wrapper for sending Crystal objects to Ruby |
|
CrystalObject = Ruby::Class.new("CrystalObject") |
|
|
|
|
|
Ruby::Class.new("Foo").tap do |c| |
|
c.def "bar", 1, ->(self : LibRuby::VALUE, a : LibRuby::VALUE) do |
|
a = Ruby::Value.new(a) |
|
"From Crystal!! #{a.to_s}".to_ruby |
|
end |
|
end |
|
|
|
# Methods for wrapping/unwrapping Crystal references as Ruby Values |
|
class Reference |
|
def wrap(t = CrystalObject) |
|
LibRuby.rb_data_object_wrap(t, self.as(Void*), nil, ->crystal_object_free) |
|
end |
|
|
|
def self.unwrap(value : LibRuby::VALUE) |
|
value.as(LibRuby::RData*).value.data.as(self) |
|
end |
|
|
|
def wrap(value : LibRuby::VALUE) |
|
value.as(LibRuby::RData*).value.data = self.as(Void*) |
|
end |
|
end |
|
|
|
# When Ruby signals that an object can be GC'ed, removed it from Roots |
|
fun crystal_object_free(obj : Void*) |
|
Roots.delete obj |
|
end |
|
|
|
# Sample Crystal class to be wrapped as a Ruby class |
|
class Coco |
|
@data = {"foo" => "1", "bar" => "2"} |
|
|
|
def initialize(x) |
|
@data["baz"] = x |
|
end |
|
|
|
def size |
|
@data.size |
|
end |
|
end |
|
|
|
Ruby::Class.new("Coco").tap do |c| |
|
c.allocate ->(c : LibRuby::VALUE) do |
|
# We send a nil reference to data_object_wrap, which we will overwite later once we initialize the object |
|
# Alternatively, we could call `Coco.allocate` here, wrap it, and call the `initialize` method on the Ruby initialize |
|
LibRuby.rb_data_object_wrap(c, nil, nil, ->crystal_object_free) |
|
end |
|
|
|
c.def "initialize", 1, ->(self : LibRuby::VALUE, x : LibRuby::VALUE) do |
|
# Do create an instance of Coco here, using the args provided, wrap it and return it |
|
# Alternatively, we could run `Coco.unwrap(self).initialize("lala")` if we had allocated it in the Ruby `allocate method` |
|
arg = String.from_ruby(x) |
|
coco = Coco.new(arg) |
|
Roots << coco.as(Void*) |
|
coco.wrap(self) |
|
end |
|
|
|
c.def "size", 0, ->(self : LibRuby::VALUE) do |
|
# Delegate a simple method to Coco, and wrap the return value as a Ruby value |
|
Coco.unwrap(self).size.to_ruby |
|
end |
|
end |
|
|
|
# Sample of a global method with an argument that uses Fibers |
|
Ruby.global_def "get", 1, ->(obj : LibRuby::VALUE, rburl : LibRuby::VALUE) do |
|
begin |
|
url = String.from_ruby(rburl) |
|
HTTP::Client.get(url).body.to_ruby |
|
rescue ex |
|
# We should be wrapping the Crystal exception in a Ruby via `rb_raise` |
|
puts "Exception in get: #{ex}" |
|
"".to_ruby |
|
end |
|
end |
|
|
|
# Wrap Crystal main |
|
fun init = "Init_testruby"(argc : Int32, argv : UInt8**) |
|
GC.init |
|
begin |
|
LibCrystalMain.__crystal_main(argc, argv) |
|
rescue ex |
|
ex.inspect_with_backtrace STDERR |
|
end |
|
end |
Suggestion: change
so use it at your own risk.
withUse it at your own risk, but do let us know what you use it for and what problems you stumble upon.
.We should probably either add this to crystal-ruby or start a new repo to work seriously on this :).