mirror of https://github.com/tildeio/helix
Initial commit
This commit is contained in:
commit
a19b5df18a
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
|
@ -0,0 +1,4 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"rust.formatOnSave": false
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "helix"
|
||||
version = "0.1.0"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
libc = "*"
|
||||
|
||||
[dependencies.cslice]
|
||||
version = "*"
|
||||
git = "https://github.com/rustbridge/neon"
|
||||
|
||||
[dependencies.libcruby-sys]
|
||||
|
||||
path = "crates/libcruby-sys"
|
|
@ -0,0 +1,129 @@
|
|||
WARNING: This repository is still in active development. **The vast majority of important Ruby
|
||||
APIs are not yet supported**, because we are still in the process of formulating the rules for
|
||||
binding Ruby APIs (so that we can make things ergonomic and provide **safety guarantees**).
|
||||
|
||||
Short-term TODOs:
|
||||
|
||||
- [ ] Rust return types coerce into Ruby values
|
||||
- [ ] Defined coercions for all of the main Ruby types
|
||||
- [ ] Calling Ruby methods on Ruby objects
|
||||
- [ ] Propagating Ruby exceptions through Rust
|
||||
- [ ] Converting type check errors into exceptions (currently they're just logged)
|
||||
- [ ] `struct { }` fields inside of wrapped classes (not-reopened), using `Data_Wrap_Struct` under the hood
|
||||
- [ ] Dynamically enforced ownership for wrapped classes
|
||||
- [ ] `self` types in reopened classes to simple coercsions into Rust types (Ruby String -> &str)
|
||||
- [ ] Locking against re-entering Ruby when coercing String/Array into &str / &[T]
|
||||
|
||||
What follows is an aspirational README :wink:
|
||||
|
||||
# Helix
|
||||
|
||||
Helix allows you to write Ruby classes in Rust without having to write the glue code yourself.
|
||||
|
||||
```rust
|
||||
declare_types! {
|
||||
class Console {
|
||||
def log(self, string: &str) {
|
||||
println!("LOG: {}", string);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ irb
|
||||
>> require "console/native"
|
||||
>> Console.new.log("I'm in your rust")
|
||||
LOG: I'm in your Rust
|
||||
```
|
||||
|
||||
> STATUS: The main thing missing from the current implementation is coercing Rust return types in Ruby. Today, you would need to add `Qnil` to the bottom of `def log`, which we hope to eliminate soon.
|
||||
|
||||
## Coercions
|
||||
|
||||
When you define a method in Helix using `def`, you can specify any Rust type in its type signature.
|
||||
|
||||
Under the hood, Helix will automatically coerce the Ruby type to the specified Rust type, doing appropriate type checks before passing the values into Rust.
|
||||
|
||||
```rust
|
||||
declare_types! {
|
||||
class Console {
|
||||
def log(string: &str) {
|
||||
println!("LOG: {}", string);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ irb
|
||||
>> require "console/native"
|
||||
>> Console.new.log({})
|
||||
TypeError: no implicit coercion of Hash into Rust &str
|
||||
from (irb):2:in `log'
|
||||
from (irb):2
|
||||
from /Users/ykatz/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
|
||||
```
|
||||
|
||||
> STATUS: This protocol already works now and is implemented for `String` and `&[u8]`
|
||||
|
||||
### The Helix Coercion Protocol
|
||||
|
||||
Under the hood, Helix does not hardcode all possible coercions from Ruby into Rust. Instead, it defines a two-part protocol that any crate can implement to define coercions from Ruby values into their types.
|
||||
|
||||
```rust
|
||||
pub trait UncheckedValue<T> {
|
||||
fn to_checked(self) -> CheckResult<T>;
|
||||
}
|
||||
|
||||
pub trait ToRust<U, T: CheckedValue<U>> {
|
||||
fn to_rust(self) -> T;
|
||||
}
|
||||
```
|
||||
|
||||
Implementations of these traits use these concrete types:
|
||||
|
||||
```rust
|
||||
pub type CheckResult<T> = Result<CheckedValue<T>, CString /* error */>;
|
||||
|
||||
pub struct CheckedValue<T> {
|
||||
pub inner: VALUE;
|
||||
// other private fields
|
||||
}
|
||||
|
||||
impl<T> CheckedValue<T> {
|
||||
// instantiating a CheckedValue<T> is an assertion that the follow-up
|
||||
// call to `to_rust` is safe.
|
||||
pub unsafe fn new(inner: VALUE) -> CheckedValue<T>;
|
||||
}
|
||||
```
|
||||
|
||||
For reference, here is the implementation of the coercion from a Ruby `String` to Rust `String`.
|
||||
|
||||
```rust
|
||||
|
||||
impl UncheckedValue<String> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<String> {
|
||||
// check whether the VALUE is actually a String
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_STRING) } {
|
||||
// assert that we can guarantee that to_rust() can return a Rust String safely
|
||||
Ok(unsafe { CheckedValue::<String>::new(self) })
|
||||
} else {
|
||||
Err(CString::new(format!("No implicit conversion from {} to Rust String", "?")).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<String> for CheckedValue<String> {
|
||||
fn to_rust(self) -> String {
|
||||
// we're sure that these calls are safe, because we already went through the type
|
||||
// checking protocol in VALUE.to_checked().
|
||||
let size = unsafe { sys::RSTRING_LEN(self.inner) };
|
||||
let ptr = unsafe { sys::RSTRING_PTR(self.inner) };
|
||||
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size as usize) };
|
||||
unsafe { std::str::from_utf8_unchecked(slice) }.to_string()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This protocol allows us to fully type check a method's arguments before starting any of the coercions. It happens automatically based on the type signature you use in your Rust method `def`.
|
|
@ -0,0 +1,13 @@
|
|||
interface ConsoleClass {
|
||||
new(): Console;
|
||||
}
|
||||
|
||||
interface Console {
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
// 1. Get init running
|
||||
// 2. Export a global variable (number)
|
||||
// 3. Make a module / class
|
||||
// 4. Create a method that expects a simple type and does work (log(RubyString))
|
||||
// 5. Have the Rust code (safely) call back into Ruby to get the type it needs
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "libcruby-sys"
|
||||
version = "0.1.0"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
libc = "*"
|
|
@ -0,0 +1,89 @@
|
|||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
extern crate libc;
|
||||
|
||||
pub type void_ptr = *const libc::c_void;
|
||||
pub type c_string = *const libc::c_char;
|
||||
// pub type c_func = extern "C" fn(...);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ID(void_ptr);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct VALUE(void_ptr);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct RubyException(isize);
|
||||
|
||||
impl RubyException {
|
||||
pub fn new() -> RubyException {
|
||||
RubyException(0)
|
||||
}
|
||||
|
||||
pub fn empty() -> RubyException {
|
||||
RubyException(0)
|
||||
}
|
||||
|
||||
pub fn for_tag(tag: isize) -> RubyException {
|
||||
RubyException(tag)
|
||||
}
|
||||
}
|
||||
|
||||
pub const EMPTY_EXCEPTION: RubyException = RubyException(0);
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "HELIX_Qfalse"]
|
||||
pub static Qfalse: VALUE;
|
||||
|
||||
#[link_name = "HELIX_Qtrue"]
|
||||
pub static Qtrue: VALUE;
|
||||
|
||||
#[link_name = "HELIX_Qnil"]
|
||||
pub static Qnil: VALUE;
|
||||
|
||||
#[link_name = "rb_cObject"]
|
||||
pub static rb_cObject: VALUE;
|
||||
|
||||
#[link_name = "HELIX_RSTRING_LEN"]
|
||||
pub fn RSTRING_LEN(string: VALUE) -> isize;
|
||||
|
||||
#[link_name = "HELIX_RSTRING_PTR"]
|
||||
pub fn RSTRING_PTR(string: VALUE) -> c_string;
|
||||
|
||||
#[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_TYPE_P"]
|
||||
pub fn RB_TYPE_P(val: VALUE, rb_type: isize) -> bool;
|
||||
|
||||
pub fn rb_check_type(v: VALUE, rb_type: isize);
|
||||
|
||||
#[link_name = "HELIX_T_STRING"]
|
||||
pub static T_STRING: isize;
|
||||
|
||||
#[link_name = "HELIX_T_ARRAY"]
|
||||
pub static T_ARRAY: isize;
|
||||
|
||||
// unknown if working?
|
||||
// fn rb_define_variable(name: c_string, value: *const VALUE);
|
||||
pub fn rb_const_get(class: VALUE, name: ID) -> VALUE;
|
||||
pub fn rb_define_global_const(name: c_string, value: VALUE);
|
||||
pub fn rb_define_module(name: c_string) -> VALUE;
|
||||
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_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) -> !;
|
||||
pub fn rb_protect(try: extern "C" fn(v: void_ptr) -> VALUE,
|
||||
arg: void_ptr,
|
||||
state: *mut RubyException)
|
||||
-> VALUE;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,35 @@
|
|||
[root]
|
||||
name = "console"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"helix 0.1.0",
|
||||
"libc 0.2.9 (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"
|
||||
|
||||
[[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)",
|
||||
"libcruby-sys 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.9"
|
||||
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)",
|
||||
]
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "console"
|
||||
version = "0.1.0"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
libc = "*"
|
||||
|
||||
[dependencies.libcruby-sys]
|
||||
|
||||
path = "../../crates/libcruby-sys"
|
||||
|
||||
[dependencies.helix]
|
||||
|
||||
path = "../.."
|
||||
|
||||
[profile.release]
|
||||
|
||||
lto = true
|
|
@ -0,0 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
|
@ -0,0 +1,19 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.5.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
rake (10.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
|
@ -0,0 +1,21 @@
|
|||
require 'rake/clean'
|
||||
require 'bundler/setup'
|
||||
|
||||
task :default => :irb
|
||||
|
||||
directory 'target'
|
||||
directory 'lib/console'
|
||||
|
||||
task :cargo_build do
|
||||
sh "cargo build --release"
|
||||
end
|
||||
CLEAN.include('target')
|
||||
|
||||
file "lib/console/native.bundle" => ['lib/console', :cargo_build] do
|
||||
sh "gcc -Wl,-force_load,target/release/libconsole.a --shared -Wl,-undefined,dynamic_lookup -o lib/console/native.bundle"
|
||||
end
|
||||
CLOBBER.include('lib/console/native.bundle')
|
||||
|
||||
task :irb => "lib/console/native.bundle" do
|
||||
exec "irb -Ilib -rconsole"
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'console/native'
|
|
@ -0,0 +1,144 @@
|
|||
#[macro_use]
|
||||
extern crate helix;
|
||||
use helix::sys::{Qnil};
|
||||
|
||||
declare_types! {
|
||||
class Console {
|
||||
def log(self, string: String) {
|
||||
println!("{:?}", string);
|
||||
Qnil
|
||||
}
|
||||
|
||||
def print_self(self) {
|
||||
println!("{:?}", self);
|
||||
Qnil
|
||||
}
|
||||
|
||||
def hello(self) {
|
||||
println!("hello");
|
||||
Qnil
|
||||
}
|
||||
|
||||
def loglog(self, string1: String, string2: String) {
|
||||
println!("{:?} {:?}", string1, string2);
|
||||
Qnil
|
||||
}
|
||||
|
||||
def lololol(self) {
|
||||
self.hello();
|
||||
Qnil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// macro_rules! cstr {
|
||||
// ( $x: expr ) => { $x.as_ptr() as *const i8 }
|
||||
// }
|
||||
|
||||
|
||||
// extern "C" fn log(_: VALUE, message: VALUE) -> VALUE {
|
||||
// #[repr(C)]
|
||||
// struct CheckTypeArgs {
|
||||
// value: VALUE,
|
||||
// rb_type: isize,
|
||||
// }
|
||||
|
||||
// extern "C" fn CheckType(args: &CheckTypeArgs) -> VALUE {
|
||||
// unsafe { rb_check_type(args.value, args.rb_type) };
|
||||
// Qnil
|
||||
// }
|
||||
|
||||
// let result = std::panic::catch_unwind(|| {
|
||||
// with_protect(CheckType,
|
||||
// &CheckTypeArgs {
|
||||
// value: message,
|
||||
// rb_type: T_STRING,
|
||||
// });
|
||||
// });
|
||||
|
||||
// if let Err(state) = result {
|
||||
// let state = state.downcast_ref::<RubyException>().unwrap();
|
||||
// unsafe { rb_jump_tag(*state) };
|
||||
// } else {
|
||||
// if unsafe { RB_TYPE_P(message, T_STRING) } {
|
||||
// let size = unsafe { RSTRING_LEN(message) };
|
||||
// let ptr = unsafe { RSTRING_PTR(message) };
|
||||
// let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size as usize) };
|
||||
// let string = unsafe { std::str::from_utf8_unchecked(slice) };
|
||||
// println!("size: {}", size);
|
||||
// println!("ptr: {:?}", ptr);
|
||||
// println!("string: {}", string);
|
||||
// Qtrue
|
||||
// } else {
|
||||
// Qfalse
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// fn with_protect<T>(func: extern "C" fn(&T) -> VALUE, arg: &T) {
|
||||
// let mut state: RubyException = RubyException::new();
|
||||
// let arg: void_ptr = unsafe { mem::transmute(arg) };
|
||||
// let func: extern "C" fn(void_ptr) -> VALUE = unsafe { mem::transmute(func) };
|
||||
|
||||
// unsafe { rb_protect(func, arg, &mut state as *mut RubyException) };
|
||||
|
||||
// if state == RubyException::new() {
|
||||
// println!("IT WORKED");
|
||||
// } else {
|
||||
// panic!(state);
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,2 @@
|
|||
--color
|
||||
--require spec_helper
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "membership"
|
||||
version = "0.1.0"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
libc = "*"
|
||||
|
||||
[dependencies.libcruby-sys]
|
||||
|
||||
path = "../../crates/libcruby-sys"
|
||||
|
||||
[dependencies.helix]
|
||||
|
||||
path = "../.."
|
||||
|
||||
[profile.release]
|
||||
|
||||
lto = true
|
|
@ -0,0 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rspec', '~> 3.4'
|
|
@ -0,0 +1,34 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.5.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.2.5)
|
||||
rake (10.4.2)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.1)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.4)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
|
@ -0,0 +1,31 @@
|
|||
require 'rake/clean'
|
||||
require 'rspec/core/rake_task'
|
||||
require 'bundler/setup'
|
||||
|
||||
directory 'target'
|
||||
directory 'lib/membership'
|
||||
|
||||
task :cargo_build do
|
||||
sh "cargo build --release"
|
||||
end
|
||||
CLEAN.include('target')
|
||||
|
||||
file "lib/membership/native.bundle" => ['lib/membership', :cargo_build] do
|
||||
sh "gcc -Wl,-force_load,target/release/libmembership.a --shared -Wl,-undefined,dynamic_lookup -o lib/membership/native.bundle"
|
||||
end
|
||||
CLOBBER.include('lib/membership/native.bundle')
|
||||
|
||||
task :irb => "lib/membership/native.bundle" do
|
||||
exec "irb -Ilib -rmembership"
|
||||
end
|
||||
|
||||
task :benchmark => "lib/membership/native.bundle" do
|
||||
exec "ruby -Ilib benchmark.rb"
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:rspec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :rspec => "lib/membership/native.bundle"
|
||||
task :default => :rspec
|
|
@ -0,0 +1,193 @@
|
|||
require 'bundler/setup'
|
||||
require 'benchmark'
|
||||
|
||||
ENV['IMPLEMENTATION'] = 'NONE'
|
||||
|
||||
require 'membership'
|
||||
|
||||
WARMUP_ITER = 10_000
|
||||
BENCHMARK_ITER = 2_000_000
|
||||
|
||||
puts "Benchmarking at #{BENCHMARK_ITER} iterations..."
|
||||
|
||||
def scenario(title, haystack, needle, expected)
|
||||
title = " #{title} ".inspect
|
||||
haystack = "haystack_#{haystack}"
|
||||
needle = "needle_#{needle}"
|
||||
|
||||
program = <<-RUBY_CODEZ
|
||||
haystack_empty = []
|
||||
haystack_small = [1,2,3]
|
||||
haystack_medium = [1,2,3,4,5,6]
|
||||
haystack_large = (1..15).to_a
|
||||
haystack_mega = (1..100).to_a
|
||||
|
||||
needle_empty = []
|
||||
needle_tiny_1 = [1]
|
||||
needle_tiny_2 = [3]
|
||||
needle_tiny_3 = [6]
|
||||
needle_tiny_4 = [15]
|
||||
needle_tiny_5 = [50]
|
||||
needle_tiny_6 = [100]
|
||||
needle_tiny_7 = [101]
|
||||
needle_small_1 = [1,2,3]
|
||||
needle_small_2 = [2,3,4]
|
||||
needle_small_3 = [4,5,6]
|
||||
needle_small_4 = [5,6,7]
|
||||
needle_small_5 = [13,14,15]
|
||||
needle_small_6 = [14,15,16]
|
||||
needle_small_7 = [49,50,51]
|
||||
needle_small_8 = [98,99,100]
|
||||
needle_small_9 = [99,100,101]
|
||||
needle_medium_1 = [1,2,3,4,5,6]
|
||||
needle_medium_2 = [2,3,4,5,6,7]
|
||||
needle_medium_3 = [6,7,8,9,10,11]
|
||||
needle_medium_4 = [2,4,6,8,10,12]
|
||||
needle_medium_5 = [10,11,12,13,14,15]
|
||||
needle_medium_6 = [11,12,13,14,15,16]
|
||||
needle_medium_7 = [48,49,50,51,52,53]
|
||||
needle_medium_8 = [25,35,45,55,65,75]
|
||||
needle_medium_9 = [95,96,97,98,99,100]
|
||||
needle_medium_10 = [96,97,98,99,100,101]
|
||||
needle_large_1 = (1..15).to_a
|
||||
needle_large_2 = (2..16).to_a
|
||||
needle_large_3 = (43..57).to_a
|
||||
needle_large_4 = [15,20,25,30,35,40,45,50,55,60,65,70,75,80,85]
|
||||
needle_large_5 = (86..100).to_a
|
||||
needle_large_6 = (87..101).to_a
|
||||
needle_mega_1 = (1..100).to_a
|
||||
needle_mega_2 = (2..101).to_a
|
||||
|
||||
puts
|
||||
puts #{title}.center(80, '-')
|
||||
puts
|
||||
|
||||
# smoke test
|
||||
|
||||
unless #{haystack}.naive_superset_of?(#{needle}) == #{expected}
|
||||
raise "Expected #{haystack}.naive_superset_of?(#{needle}) to be #{expected}"
|
||||
end
|
||||
|
||||
unless #{haystack}.fast_superset_of?(#{needle}) == #{expected}
|
||||
raise "Expected #{haystack}.fast_superset_of?(#{needle}) to be #{expected}"
|
||||
end
|
||||
|
||||
unless #{haystack}.is_superset_of(#{needle}) == #{expected}
|
||||
raise "Expected #{haystack}.is_superset_of(#{needle}) to be #{expected}"
|
||||
end
|
||||
|
||||
# warmup
|
||||
i = 0
|
||||
|
||||
while i < #{WARMUP_ITER}
|
||||
#{haystack}.naive_superset_of?(#{needle})
|
||||
#{haystack}.fast_superset_of?(#{needle})
|
||||
#{haystack}.is_superset_of(#{needle})
|
||||
i += 1
|
||||
end
|
||||
|
||||
# for realz
|
||||
GC.start
|
||||
|
||||
naive_result = Benchmark.measure {
|
||||
i = 0
|
||||
|
||||
while i < #{BENCHMARK_ITER}
|
||||
#{haystack}.naive_superset_of?(#{needle})
|
||||
i += 1
|
||||
end
|
||||
}
|
||||
|
||||
GC.start
|
||||
|
||||
fast_result = Benchmark.measure {
|
||||
i = 0
|
||||
|
||||
while i < #{BENCHMARK_ITER}
|
||||
#{haystack}.fast_superset_of?(#{needle})
|
||||
i += 1
|
||||
end
|
||||
}
|
||||
|
||||
GC.start
|
||||
|
||||
rust_result = Benchmark.measure {
|
||||
i = 0
|
||||
|
||||
while i < #{BENCHMARK_ITER}
|
||||
#{haystack}.is_superset_of(#{needle})
|
||||
i += 1
|
||||
end
|
||||
}
|
||||
|
||||
# Should I use real time or...?
|
||||
puts "Ruby (Naive) " + naive_result.real.round(5).to_s.ljust(7,"0") + " sec"
|
||||
puts "Ruby (Fast) " + fast_result.real.round(5).to_s.ljust(7,"0") + " sec (" + (#{BENCHMARK_ITER}/fast_result.real).round.to_s.rjust(8) + " ops/sec) - " + (naive_result.real / fast_result.real).round(2).to_s.rjust(5) + "x faster"
|
||||
puts "Rust " + rust_result.real.round(5).to_s.ljust(7,"0") + " sec (" + (#{BENCHMARK_ITER}/rust_result.real).round.to_s.rjust(8) + " ops/sec) - " + (naive_result.real / rust_result.real).round(2).to_s.rjust(5) + "x faster"
|
||||
RUBY_CODEZ
|
||||
|
||||
eval program
|
||||
end
|
||||
|
||||
scenario("Empty haystack (exact match)", "empty", "empty", true)
|
||||
scenario("Empty haystack (not found)", "empty", "tiny_1", false)
|
||||
|
||||
scenario("Small haystack, tiny needle (found at front)", "small", "tiny_1", true)
|
||||
scenario("Small haystack, tiny needle (found at back)", "small", "tiny_2", true)
|
||||
scenario("Small haystack, tiny needle (not found)", "small", "tiny_3", false)
|
||||
|
||||
scenario("Small haystack, small needle (exact match)", "small", "small_1", true)
|
||||
scenario("Small haystack, small needle (not found)", "small", "small_2", false)
|
||||
|
||||
scenario("Medium haystack, tiny needle (found at front)", "medium", "tiny_1", true)
|
||||
scenario("Medium haystack, tiny needle (found at back)", "medium", "tiny_3", true)
|
||||
scenario("Medium haystack, tiny needle (not found)", "medium", "tiny_4", false)
|
||||
|
||||
scenario("Medium haystack, small needle (found at front)", "medium", "small_1", true)
|
||||
scenario("Medium haystack, small needle (found at back)", "medium", "small_3", true)
|
||||
scenario("Medium haystack, small needle (not found)", "medium", "small_4", false)
|
||||
|
||||
scenario("Medium haystack, medium needle (exact match)", "medium", "medium_1", true)
|
||||
scenario("Medium haystack, medium needle (not found)", "medium", "medium_2", false)
|
||||
|
||||
scenario("Large haystack, tiny needle (found at front)", "large", "tiny_1", true)
|
||||
scenario("Large haystack, tiny needle (found at back)", "large", "tiny_4", true)
|
||||
scenario("Large haystack, tiny needle (not found)", "large", "tiny_5", false)
|
||||
|
||||
scenario("Large haystack, small needle (found at front)", "large", "small_1", true)
|
||||
scenario("Large haystack, small needle (found at back)", "large", "small_5", true)
|
||||
scenario("Large haystack, small needle (not found)", "large", "small_6", false)
|
||||
|
||||
scenario("Large haystack, medium needle (found at front)", "large", "medium_1", true)
|
||||
scenario("Large haystack, medium needle (found in middle)", "large", "medium_3", true)
|
||||
scenario("Large haystack, medium needle (spread out)", "large", "medium_4", true)
|
||||
scenario("Large haystack, medium needle (found at back)", "large", "medium_5", true)
|
||||
scenario("Large haystack, medium needle (not found)", "large", "medium_6", false)
|
||||
|
||||
scenario("Large haystack, large needle (exact match)", "large", "large_1", true)
|
||||
scenario("Large haystack, large needle (not found)", "large", "large_2", false)
|
||||
|
||||
scenario("Mega haystack, tiny needle (found at front)", "mega", "tiny_1", true)
|
||||
scenario("Mega haystack, tiny needle (found in middle)", "mega", "tiny_5", true)
|
||||
scenario("Mega haystack, tiny needle (found at back)", "mega", "tiny_6", true)
|
||||
scenario("Mega haystack, tiny needle (not found)", "mega", "tiny_7", false)
|
||||
|
||||
scenario("Mega haystack, small needle (found at front)", "mega", "small_1", true)
|
||||
scenario("Mega haystack, small needle (found in middle)", "mega", "small_7", true)
|
||||
scenario("Mega haystack, small needle (found at back)", "mega", "small_8", true)
|
||||
scenario("Mega haystack, small needle (not found)", "mega", "small_9", false)
|
||||
|
||||
scenario("Mega haystack, medium needle (found at front)", "mega", "medium_1", true)
|
||||
scenario("Mega haystack, medium needle (found in middle)", "mega", "medium_7", true)
|
||||
scenario("Mega haystack, medium needle (spread out)", "mega", "medium_8", true)
|
||||
scenario("Mega haystack, medium needle (found at back)", "mega", "medium_9", true)
|
||||
scenario("Mega haystack, medium needle (not found)", "mega", "medium_10", false)
|
||||
|
||||
scenario("Mega haystack, large needle (found at front)", "mega", "large_1", true)
|
||||
scenario("Mega haystack, large needle (found in middle)", "mega", "large_3", true)
|
||||
scenario("Mega haystack, large needle (spread out)", "mega", "large_4", true)
|
||||
scenario("Mega haystack, large needle (found at back)", "mega", "large_5", true)
|
||||
scenario("Mega haystack, large needle (not found)", "mega", "large_6", false)
|
||||
|
||||
scenario("Mega haystack, mega needle (exact match)", "mega", "mega_1", true)
|
||||
scenario("Mega haystack, mega needle (not found)", "mega", "mega_2", false)
|
|
@ -0,0 +1,29 @@
|
|||
function supersetOf(source, needle) {
|
||||
if (needle.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let needlePosition = 0;
|
||||
let needleItem = needle[0];
|
||||
let needleLength = needle.length;
|
||||
|
||||
for (let sourceIndex=0; sourceIndex<source.length; sourceIndex++) {
|
||||
let sourceItem = source[sourceIndex];
|
||||
|
||||
if (sourceItem === needleItem) {
|
||||
needlePosition++;
|
||||
|
||||
if (needlePosition >= needleLength) {
|
||||
return true;
|
||||
} else {
|
||||
needleItem = needle[needlePosition];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
require 'helix_runtime'
|
||||
require 'membership/native'
|
||||
|
||||
class Array
|
||||
def naive_superset_of?(needle)
|
||||
self & needle == needle
|
||||
end
|
||||
|
||||
def fast_superset_of?(needle)
|
||||
return true if needle.empty?
|
||||
return false if self.empty?
|
||||
|
||||
needle_length = needle.length
|
||||
|
||||
return false if needle_length > self.length
|
||||
|
||||
needle_position = 0
|
||||
needle_item = needle[needle_position]
|
||||
|
||||
self.each do |item|
|
||||
if item == needle_item
|
||||
needle_position += 1
|
||||
|
||||
if needle_position >= needle_length
|
||||
return true
|
||||
else
|
||||
needle_item = needle[needle_position]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
case ENV["IMPLEMENTATION"]
|
||||
when "RUST"
|
||||
alias superset_of? is_superset_of
|
||||
when "FAST_RUBY"
|
||||
alias superset_of? fast_superset_of?
|
||||
when "NAIVE_RUBY"
|
||||
alias superset_of? naive_superset_of?
|
||||
when "NONE"
|
||||
else
|
||||
puts "\nPlease specify an IMPLEMENTATION: RUST, FAST_RUBY, NAIVE_RUBY or NONE"
|
||||
exit!
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Array do
|
||||
describe '#is_superset' do
|
||||
it 'should return true if the needle is empty' do
|
||||
expect([1,2,3,4,5]).to be_superset_of([])
|
||||
end
|
||||
|
||||
it 'should return false if the source is empty' do
|
||||
expect([]).to_not be_superset_of([1,2,3,4,5])
|
||||
end
|
||||
|
||||
it 'should return true if both the source and needle are empty' do
|
||||
expect([]).to be_superset_of([])
|
||||
end
|
||||
|
||||
it 'should return true if the needle is fully contained in the source' do
|
||||
expect([1,2,3,4,5]).to be_superset_of([1])
|
||||
expect([1,2,3,4,5]).to be_superset_of([2])
|
||||
expect([1,2,3,4,5]).to be_superset_of([5])
|
||||
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,2])
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,4])
|
||||
expect([1,2,3,4,5]).to be_superset_of([2,5])
|
||||
expect([1,2,3,4,5]).to be_superset_of([4,5])
|
||||
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,2,3])
|
||||
expect([1,2,3,4,5]).to be_superset_of([2,3,5])
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,4,5])
|
||||
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,2,3,4])
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,2,4,5])
|
||||
expect([1,2,3,4,5]).to be_superset_of([2,3,4,5])
|
||||
|
||||
expect([1,2,3,4,5]).to be_superset_of([1,2,3,4,5])
|
||||
end
|
||||
|
||||
it 'should return false if the needle is not fully contained in the source' do
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([0])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([6])
|
||||
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([0,1])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([2,6])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([6,7])
|
||||
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([1,2,6])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([0,2,3])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([7,8,9])
|
||||
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([0,1,2,3])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([3,4,5,6])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([6,7,8,9])
|
||||
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([0,1,2,3,4])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([2,3,4,5,6])
|
||||
expect([1,2,3,4,5]).to_not be_superset_of([6,7,8,9,10])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'membership'
|
|
@ -0,0 +1,38 @@
|
|||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
use helix::sys::{Qtrue,Qfalse};
|
||||
use helix::{UncheckedValue, ToRust};
|
||||
|
||||
declare_types! {
|
||||
reopen class Array {
|
||||
def is_superset_of(self, needle: &[usize]) {
|
||||
if needle.is_empty() { return Qtrue }
|
||||
|
||||
let haystack = self.as_ref();
|
||||
|
||||
if haystack.is_empty() { return Qfalse }
|
||||
|
||||
let mut needle = needle.iter();
|
||||
let mut needle_item = needle.next().unwrap();
|
||||
|
||||
for item in haystack {
|
||||
if item == needle_item {
|
||||
match needle.next() {
|
||||
None => return Qtrue,
|
||||
Some(next_item) => needle_item = next_item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Qfalse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[usize]> for Array {
|
||||
fn as_ref(&self) -> &[usize] {
|
||||
let checked = self.0.to_checked().unwrap();
|
||||
checked.to_rust()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/.bundle/
|
||||
/.yardoc
|
||||
/Gemfile.lock
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
*.bundle
|
||||
*.gem
|
||||
Makefile
|
|
@ -0,0 +1,2 @@
|
|||
--format documentation
|
||||
--color
|
|
@ -0,0 +1,4 @@
|
|||
language: ruby
|
||||
rvm:
|
||||
- 2.2.2
|
||||
before_install: gem install bundler -v 1.10.6
|
|
@ -0,0 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in ruby.gemspec
|
||||
gemspec
|
||||
|
||||
gem 'neversaydie', github: 'tenderlove/neversaydie'
|
|
@ -0,0 +1 @@
|
|||
# Helix Runtime
|
|
@ -0,0 +1,24 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
require "rake/extensiontask"
|
||||
|
||||
verbose(false)
|
||||
|
||||
Rake::ExtensionTask.new do |ext|
|
||||
ext.name = "native"
|
||||
ext.ext_dir = "ext/helix_runtime/native"
|
||||
ext.lib_dir = "lib/helix_runtime"
|
||||
end
|
||||
|
||||
Rake::ExtensionTask.new do |ext|
|
||||
ext.name = "dummy"
|
||||
ext.ext_dir = "spec/support/dummy/ext/dummy"
|
||||
ext.lib_dir = "spec/support/dummy/lib"
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:rspec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :rspec => :compile
|
||||
task :default => :rspec
|
|
@ -0,0 +1,2 @@
|
|||
require "mkmf"
|
||||
create_makefile "helix_runtime/native"
|
|
@ -0,0 +1,76 @@
|
|||
#include <ruby.h>
|
||||
#include <ruby/intern.h>
|
||||
#include <stdbool.h>
|
||||
#include <helix_runtime.h>
|
||||
|
||||
VALUE HELIX_Qtrue = Qtrue;
|
||||
VALUE HELIX_Qfalse = Qfalse;
|
||||
VALUE HELIX_Qnil = Qnil;
|
||||
|
||||
long HELIX_RSTRING_LEN(VALUE string) {
|
||||
return RSTRING_LEN(string);
|
||||
}
|
||||
|
||||
const char* HELIX_RSTRING_PTR(VALUE string) {
|
||||
return RSTRING_PTR(string);
|
||||
}
|
||||
|
||||
long HELIX_RARRAY_LEN(VALUE array) {
|
||||
return RARRAY_LEN(array);
|
||||
}
|
||||
|
||||
void* HELIX_RARRAY_PTR(VALUE array) {
|
||||
return RARRAY_PTR(array);
|
||||
}
|
||||
|
||||
bool HELIX_RB_TYPE_P(VALUE v, int type) {
|
||||
return RB_TYPE_P(v, type);
|
||||
}
|
||||
|
||||
VALUE HELIX_INT2FIX(int c_int) {
|
||||
return INT2FIX(c_int);
|
||||
}
|
||||
|
||||
VALUE HELIX_FIX2INT(VALUE v) {
|
||||
return FIX2INT(v);
|
||||
}
|
||||
|
||||
int HELIX_TYPE(VALUE v) {
|
||||
return TYPE(v);
|
||||
}
|
||||
|
||||
int HELIX_T_NONE = T_NONE;
|
||||
int HELIX_T_NIL = T_NIL;
|
||||
int HELIX_T_OBJECT = T_OBJECT;
|
||||
int HELIX_T_CLASS = T_CLASS;
|
||||
int HELIX_T_ICLASS = T_ICLASS;
|
||||
int HELIX_T_MODULE = T_MODULE;
|
||||
int HELIX_T_FLOAT = T_FLOAT;
|
||||
int HELIX_T_STRING = T_STRING;
|
||||
int HELIX_T_REGEXP = T_REGEXP;
|
||||
int HELIX_T_ARRAY = T_ARRAY;
|
||||
int HELIX_T_HASH = T_HASH;
|
||||
int HELIX_T_STRUCT = T_STRUCT;
|
||||
int HELIX_T_BIGNUM = T_BIGNUM;
|
||||
int HELIX_T_FILE = T_FILE;
|
||||
int HELIX_T_FIXNUM = T_FIXNUM;
|
||||
int HELIX_T_TRUE = T_TRUE;
|
||||
int HELIX_T_FALSE = T_FALSE;
|
||||
int HELIX_T_DATA = T_DATA;
|
||||
int HELIX_T_MATCH = T_MATCH;
|
||||
int HELIX_T_SYMBOL = T_SYMBOL;
|
||||
int HELIX_T_RATIONAL = T_RATIONAL;
|
||||
int HELIX_T_COMPLEX = T_COMPLEX;
|
||||
int HELIX_T_UNDEF = T_UNDEF;
|
||||
int HELIX_T_NODE = T_NODE;
|
||||
int HELIX_T_ZOMBIE = T_ZOMBIE;
|
||||
int HELIX_T_MASK = T_MASK;
|
||||
// int HELIX_T_IMEMO = T_IMEMO;
|
||||
|
||||
void Init_native() {}
|
||||
|
||||
void helix_inspect(void* ptr) {
|
||||
printf("ptr: %p\n", ptr);
|
||||
printf("str: %s\n", ptr);
|
||||
printf("hex: %x\n", ptr);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#include <ruby.h>
|
||||
#include <ruby/intern.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef HELIXRUNTIME_H
|
||||
#define HELIXRUNTIME_H
|
||||
|
||||
extern VALUE HELIX_Qtrue;
|
||||
extern VALUE HELIX_Qfalse;
|
||||
extern VALUE HELIX_Qnil;
|
||||
|
||||
long HELIX_RSTRING_LEN(VALUE string);
|
||||
const char* HELIX_RSTRING_PTR(VALUE string);
|
||||
|
||||
long HELIX_RARRAY_LEN(VALUE array);
|
||||
void* HELIX_RARRAY_PTR(VALUE array);
|
||||
|
||||
bool HELIX_RB_TYPE_P(VALUE v, int type);
|
||||
int HELIX_TYPE(VALUE v);
|
||||
|
||||
VALUE HELIX_INT2FIX(int c_int);
|
||||
VALUE HELIX_FIX2INT(VALUE fix);
|
||||
|
||||
extern int HELIX_T_NONE;
|
||||
extern int HELIX_T_NIL;
|
||||
extern int HELIX_T_OBJECT;
|
||||
extern int HELIX_T_CLASS;
|
||||
extern int HELIX_T_ICLASS;
|
||||
extern int HELIX_T_MODULE;
|
||||
extern int HELIX_T_FLOAT;
|
||||
extern int HELIX_T_STRING;
|
||||
extern int HELIX_T_REGEXP;
|
||||
extern int HELIX_T_ARRAY;
|
||||
extern int HELIX_T_HASH;
|
||||
extern int HELIX_T_STRUCT;
|
||||
extern int HELIX_T_BIGNUM;
|
||||
extern int HELIX_T_FILE;
|
||||
extern int HELIX_T_FIXNUM;
|
||||
extern int HELIX_T_TRUE;
|
||||
extern int HELIX_T_FALSE;
|
||||
extern int HELIX_T_DATA;
|
||||
extern int HELIX_T_MATCH;
|
||||
extern int HELIX_T_SYMBOL;
|
||||
extern int HELIX_T_RATIONAL;
|
||||
extern int HELIX_T_COMPLEX;
|
||||
extern int HELIX_T_UNDEF;
|
||||
extern int HELIX_T_NODE;
|
||||
extern int HELIX_T_ZOMBIE;
|
||||
extern int HELIX_T_MASK;
|
||||
// extern int HELIX_T_IMEMO = T_IMEMO;
|
||||
|
||||
#endif /* HELIXRUNTIME_H */
|
|
@ -0,0 +1,27 @@
|
|||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'helix_runtime/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "helix_runtime"
|
||||
spec.version = HelixRuntime::VERSION
|
||||
spec.authors = ["Yehuda Katz", "Godfrey Chan"]
|
||||
spec.email = ["wycats@gmail.com", "godfreykfc@gmail.com"]
|
||||
|
||||
spec.summary = %q{The Helix Runtime}
|
||||
# spec.description = %q{TODO: Write a longer description or delete this line.}
|
||||
spec.homepage = "TODO: Put your gem's website or public repo URL here."
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = "exe"
|
||||
spec.extensions = ["ext/helix_runtime/native/extconf.rb"]
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.10"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.4"
|
||||
spec.add_development_dependency "rake-compiler", "~> 0.9.7"
|
||||
spec.add_development_dependency "neversaydie", "~> 1.0"
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require "helix_runtime/version"
|
||||
require "helix_runtime/native"
|
|
@ -0,0 +1,3 @@
|
|||
module HelixRuntime
|
||||
VERSION = "0.5.0"
|
||||
end
|
|
@ -0,0 +1,117 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe HelixRuntime do
|
||||
|
||||
module TYPES
|
||||
UNTESTED = {}
|
||||
|
||||
CASES = {
|
||||
T_NONE: UNTESTED,
|
||||
T_NIL: nil,
|
||||
T_OBJECT: Object.new,
|
||||
T_CLASS: Class.new,
|
||||
T_ICLASS: UNTESTED,
|
||||
T_MODULE: Module.new,
|
||||
T_FLOAT: 1.5,
|
||||
T_STRING: "hello",
|
||||
T_REGEXP: /hello/,
|
||||
T_ARRAY: [],
|
||||
T_HASH: {},
|
||||
T_STRUCT: Struct.new(:hello).new,
|
||||
T_BIGNUM: 2 ** 65,
|
||||
T_FILE: File.open(__FILE__),
|
||||
T_FIXNUM: 2,
|
||||
T_TRUE: true,
|
||||
T_FALSE: false,
|
||||
T_DATA: UNTESTED,
|
||||
T_MATCH: "hello".match(/hello/),
|
||||
T_SYMBOL: :hello,
|
||||
T_RATIONAL: Rational(1, 2),
|
||||
T_COMPLEX: Complex(1, 2),
|
||||
T_UNDEF: UNTESTED,
|
||||
T_NODE: UNTESTED,
|
||||
T_ZOMBIE: UNTESTED,
|
||||
T_MASK: UNTESTED
|
||||
}
|
||||
end
|
||||
|
||||
it 'has a version number' do
|
||||
expect(HelixRuntime::VERSION).not_to be nil
|
||||
end
|
||||
|
||||
it 'exports the Qtrue macro' do
|
||||
expect(Dummy::Qtrue).to equal(true)
|
||||
end
|
||||
|
||||
it 'exports the Qfalse macro' do
|
||||
expect(Dummy::Qfalse).to equal(false)
|
||||
end
|
||||
|
||||
it 'exports the Qnil macro' do
|
||||
expect(Dummy::Qnil).to equal(nil)
|
||||
end
|
||||
|
||||
it 'exports the RSTRING_LEN macro' do
|
||||
expect(Dummy.RSTRING_LEN('hello')).to equal(5)
|
||||
expect { Dummy.RSTRING_LEN(1) }.to segv
|
||||
end
|
||||
|
||||
it 'exports the RSTRING_PTR macro' do
|
||||
expect(Dummy.RSTRING_PTR('hello')).to_not eq(Dummy::RSTRING_PTR('hello'))
|
||||
expect(Dummy.RSTRING_PTR('hello'.freeze)).to eq(Dummy::RSTRING_PTR('hello'.freeze))
|
||||
expect { Dummy.RSTRING_PTR(1) }.to segv
|
||||
end
|
||||
|
||||
it 'exports the RARRAY_LEN macro' do
|
||||
expect(Dummy.RARRAY_LEN([1,2,3,4,5])).to equal(5)
|
||||
expect { Dummy.RARRAY_LEN(1) }.to segv
|
||||
end
|
||||
|
||||
it 'exports the RARRAY_PTR macro' do
|
||||
arr = [1,2,3,4,5]
|
||||
expect(Dummy.RARRAY_PTR([1,2,3,4,5])).to_not eq(Dummy::RARRAY_PTR([1,2,3,4,5]))
|
||||
expect(Dummy.RARRAY_PTR(arr)).to eq(Dummy::RARRAY_PTR(arr))
|
||||
expect { Dummy.RARRAY_PTR(1) }.to segv
|
||||
end
|
||||
|
||||
describe 'coercions' do
|
||||
it "(INT2FIX)" do
|
||||
expect(Dummy.INT2FIX(10)).to eq(10)
|
||||
end
|
||||
|
||||
it "(FIX2INT)" do
|
||||
expect(Dummy.FIX2INT(10)).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
describe "exports T_* constants:" do
|
||||
TYPES::CASES.each do |type_name, obj|
|
||||
next if obj == TYPES::UNTESTED
|
||||
type = Dummy.const_get(type_name)
|
||||
|
||||
describe "#{obj.class} is #{type_name}" do
|
||||
it "(RB_TYPE_P)" do
|
||||
expect(Dummy.RB_TYPE_P(obj, type)).to be(true)
|
||||
expect(Dummy.RB_TYPE_P(obj, Dummy::T_NONE)).to be(false)
|
||||
end
|
||||
|
||||
it "(TYPE)" do
|
||||
expect(Dummy.TYPE(obj)).to be(type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# it 'exports the RB_TYPE_P macro and T_*' do
|
||||
# expect(Dummy.RB_TYPE_P("hello", Dummy::T_STRING)).to be(true)
|
||||
# expect(Dummy.RB_TYPE_P({}, Dummy::T_HASH)).to be(true)
|
||||
# expect(Dummy.RB_TYPE_P([], Dummy::T_OBJECT)).to be(false)
|
||||
# end
|
||||
|
||||
# it 'exports the TYPE macro' do
|
||||
# expect(Dummy.TYPE("hello")).to eq(Dummy::T_STRING)
|
||||
# expect(Dummy.TYPE({})).to eq(Dummy::T_HASH)
|
||||
# expect(Dummy.TYPE([])).to_not eq(Dummy::T_OBJECT)
|
||||
# end
|
||||
end
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
$LOAD_PATH.unshift File.expand_path('../support/dummy/lib', __FILE__)
|
||||
require 'helix_runtime'
|
||||
require 'neversaydie' unless ENV["ALWAYS_SAY_DIE"]
|
||||
require 'dummy'
|
||||
|
||||
module HelixRuntime
|
||||
module SpecHelpers
|
||||
extend RSpec::Matchers::DSL
|
||||
SEGV = NeverSayDie
|
||||
|
||||
matcher :segv do
|
||||
match do |l|
|
||||
expect(l).to raise_error(SEGV)
|
||||
end
|
||||
|
||||
failure_message do
|
||||
'Expected block to SEGV'
|
||||
end
|
||||
|
||||
failure_message_when_negated do
|
||||
''
|
||||
end
|
||||
|
||||
def supports_block_expectations?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |c|
|
||||
c.include HelixRuntime::SpecHelpers
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# coding: utf-8
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "dummy"
|
||||
spec.version = "1.0.0"
|
||||
spec.authors = ["Yehuda Katz", "Godfrey Chan"]
|
||||
spec.email = ["wycats@gmail.com", "godfreykfc@gmail.com"]
|
||||
|
||||
spec.summary = "A dummy gem that re-export the C variables and functions to Ruby-land."
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = "exe"
|
||||
spec.extensions = ["ext/dummy/extconf.rb"]
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
spec.add_runtime_dependency "helix_runtime", "*"
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
#include <ruby.h>
|
||||
#include <ruby/intern.h>
|
||||
#include <helix_runtime.h>
|
||||
|
||||
#define EXPORT_VALUE(name) (rb_define_const(mDummy, #name, HELIX_ ## name))
|
||||
#define EXPORT_INT(name) (rb_define_const(mDummy, #name, INT2FIX(HELIX_ ##name)))
|
||||
#define EXPORT_FUNC(name, arity) (rb_define_singleton_method(mDummy, #name, TEST_ ## name, arity))
|
||||
#define EXPORT_RUBY_FUNC(name, arity) (rb_define_singleton_method(mRuby, #name, TEST_RB_ ## name, arity))
|
||||
|
||||
static VALUE TEST_RSTRING_LEN(VALUE _self, VALUE val) {
|
||||
return LONG2NUM(HELIX_RSTRING_LEN(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RB_RSTRING_PTR(VALUE _self, VALUE val) {
|
||||
return LONG2NUM((long)RSTRING_PTR(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RSTRING_PTR(VALUE _self, VALUE val) {
|
||||
return LONG2NUM((long)HELIX_RSTRING_PTR(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RARRAY_LEN(VALUE _self, VALUE val) {
|
||||
return LONG2NUM(HELIX_RARRAY_LEN(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RB_RARRAY_PTR(VALUE _self, VALUE val) {
|
||||
return LONG2NUM((long)RARRAY_PTR(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RARRAY_PTR(VALUE _self, VALUE val) {
|
||||
return LONG2NUM((long)HELIX_RARRAY_PTR(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RB_TYPE_P(VALUE _self, VALUE val, VALUE type) {
|
||||
int result = HELIX_RB_TYPE_P(val, FIX2INT(type));
|
||||
return result ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_TYPE(VALUE _self, VALUE val) {
|
||||
return INT2FIX(HELIX_TYPE(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_INT2FIX(VALUE _self, VALUE val) {
|
||||
return HELIX_INT2FIX(FIX2INT(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_FIX2INT(VALUE _self, VALUE val) {
|
||||
return INT2FIX(HELIX_FIX2INT(val));
|
||||
}
|
||||
|
||||
void Init_dummy() {
|
||||
VALUE mDummy = rb_define_module("Dummy");
|
||||
VALUE mRuby = rb_define_module_under(mDummy, "Ruby");
|
||||
|
||||
EXPORT_VALUE(Qtrue);
|
||||
EXPORT_VALUE(Qfalse);
|
||||
EXPORT_VALUE(Qnil);
|
||||
|
||||
EXPORT_INT(T_NONE);
|
||||
EXPORT_INT(T_NIL);
|
||||
EXPORT_INT(T_OBJECT);
|
||||
EXPORT_INT(T_CLASS);
|
||||
EXPORT_INT(T_ICLASS);
|
||||
EXPORT_INT(T_MODULE);
|
||||
EXPORT_INT(T_FLOAT);
|
||||
EXPORT_INT(T_STRING);
|
||||
EXPORT_INT(T_REGEXP);
|
||||
EXPORT_INT(T_ARRAY);
|
||||
EXPORT_INT(T_HASH);
|
||||
EXPORT_INT(T_STRUCT);
|
||||
EXPORT_INT(T_BIGNUM);
|
||||
EXPORT_INT(T_FILE);
|
||||
EXPORT_INT(T_FIXNUM);
|
||||
EXPORT_INT(T_TRUE);
|
||||
EXPORT_INT(T_FALSE);
|
||||
EXPORT_INT(T_DATA);
|
||||
EXPORT_INT(T_MATCH);
|
||||
EXPORT_INT(T_SYMBOL);
|
||||
EXPORT_INT(T_RATIONAL);
|
||||
EXPORT_INT(T_COMPLEX);
|
||||
EXPORT_INT(T_UNDEF);
|
||||
EXPORT_INT(T_NODE);
|
||||
EXPORT_INT(T_ZOMBIE);
|
||||
EXPORT_INT(T_MASK);
|
||||
|
||||
EXPORT_FUNC(RSTRING_LEN, 1);
|
||||
EXPORT_FUNC(RSTRING_PTR, 1);
|
||||
EXPORT_RUBY_FUNC(RSTRING_PTR, 1);
|
||||
EXPORT_FUNC(RARRAY_LEN, 1);
|
||||
EXPORT_FUNC(RARRAY_PTR, 1);
|
||||
EXPORT_RUBY_FUNC(RARRAY_PTR, 1);
|
||||
EXPORT_FUNC(RB_TYPE_P, 2);
|
||||
EXPORT_FUNC(TYPE, 1);
|
||||
EXPORT_FUNC(INT2FIX, 1);
|
||||
EXPORT_FUNC(FIX2INT, 1);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
require "mkmf"
|
||||
dir_config "dummy"
|
||||
find_header "helix_runtime.h", File.expand_path("../../../../../../ext/helix_runtime/native", __FILE__)
|
||||
create_makefile "dummy"
|
|
@ -0,0 +1,46 @@
|
|||
use libc;
|
||||
use std::ffi::CString;
|
||||
use { Class, sys };
|
||||
|
||||
pub struct MethodDefinition<'a> {
|
||||
name: &'a str,
|
||||
function: *const libc::c_void,
|
||||
arity: isize,
|
||||
}
|
||||
|
||||
impl<'a> MethodDefinition<'a> {
|
||||
pub fn new(name: &str, function: *const libc::c_void, arity: isize) -> MethodDefinition {
|
||||
MethodDefinition { name: name, function: function, arity: arity }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClassDefinition {
|
||||
class: Class,
|
||||
}
|
||||
|
||||
impl ClassDefinition {
|
||||
pub fn new(name: &str) -> ClassDefinition {
|
||||
let raw_class = unsafe { sys::rb_define_class(CString::new(name).unwrap().as_ptr(), sys::rb_cObject) };
|
||||
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());
|
||||
sys::rb_const_get(sys::rb_cObject, class_id)
|
||||
};
|
||||
ClassDefinition { class: Class(raw_class) }
|
||||
}
|
||||
|
||||
pub fn define_method(self, def: MethodDefinition) -> ClassDefinition {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
self.class.0,
|
||||
CString::new(def.name).unwrap().as_ptr(),
|
||||
def.function,
|
||||
def.arity
|
||||
);
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
mod slice;
|
||||
mod string;
|
||||
|
||||
use sys::{VALUE};
|
||||
use std::ffi::CString;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct CheckedValue<T> {
|
||||
pub inner: VALUE,
|
||||
marker: PhantomData<T>
|
||||
}
|
||||
|
||||
impl<T> CheckedValue<T> {
|
||||
pub unsafe fn new(inner: VALUE) -> CheckedValue<T> {
|
||||
CheckedValue { inner: inner, marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
pub type CheckResult<T> = Result<CheckedValue<T>, CString>;
|
||||
|
||||
pub trait UncheckedValue<T> {
|
||||
fn to_checked(self) -> CheckResult<T>;
|
||||
}
|
||||
|
||||
pub trait ToRust<T> {
|
||||
fn to_rust(self) -> T;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
use std;
|
||||
use sys;
|
||||
use sys::{VALUE};
|
||||
use std::ffi::CString;
|
||||
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust};
|
||||
|
||||
// VALUE -> to_coercible_rust<String> -> CheckResult<String> -> unwrap() -> Coercible<String> -> to_rust() -> String
|
||||
|
||||
impl<'a> UncheckedValue<&'a[usize]> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<&'a[usize]> {
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_ARRAY) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
} else {
|
||||
Err(CString::new(format!("No implicit conversion from {} to String", "?")).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToRust<&'a[usize]> for CheckedValue<&'a[usize]> {
|
||||
fn to_rust(self) -> &'a[usize] {
|
||||
let size = unsafe { sys::RARRAY_LEN(self.inner) };
|
||||
let ptr = unsafe { sys::RARRAY_PTR(self.inner) };
|
||||
unsafe { std::slice::from_raw_parts(ptr as *const usize, size as usize) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
use std;
|
||||
use sys;
|
||||
use sys::{VALUE};
|
||||
use std::ffi::CString;
|
||||
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust};
|
||||
|
||||
// VALUE -> to_coercible_rust<String> -> CheckResult<String> -> unwrap() -> Coercible<String> -> to_rust() -> String
|
||||
|
||||
impl UncheckedValue<String> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<String> {
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_STRING) } {
|
||||
Ok(unsafe { CheckedValue::<String>::new(self) })
|
||||
} else {
|
||||
Err(CString::new(format!("No implicit conversion from {} to String", "?")).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<String> for CheckedValue<String> {
|
||||
fn to_rust(self) -> String {
|
||||
let size = unsafe { sys::RSTRING_LEN(self.inner) };
|
||||
let ptr = unsafe { sys::RSTRING_PTR(self.inner) };
|
||||
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size as usize) };
|
||||
unsafe { std::str::from_utf8_unchecked(slice) }.to_string()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
extern crate cslice;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub extern crate libc;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub extern crate libcruby_sys as sys;
|
||||
// pub use rb;
|
||||
|
||||
use std::ffi::CString;
|
||||
use sys::VALUE;
|
||||
|
||||
mod macros;
|
||||
mod class_definition;
|
||||
mod coercions;
|
||||
|
||||
pub use coercions::*;
|
||||
|
||||
pub use class_definition::{ClassDefinition, MethodDefinition};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! init {
|
||||
( $($block:stmt;)* ) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Init_native() { $($block;)* }
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! method {
|
||||
( $name:ident( $($args:ident),* ) { $($block:stmt;)* } ) => {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn $name(rb_self: $crate::sys::VALUE, $($args : $crate::sys::VALUE),*) -> $crate::sys::VALUE {
|
||||
$($block;)*
|
||||
$crate::sys::Qnil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Class(sys::VALUE);
|
||||
|
||||
pub trait RubyMethod {
|
||||
fn install(self, class: VALUE, name: &str);
|
||||
}
|
||||
|
||||
impl RubyMethod for extern "C" fn(VALUE) -> VALUE {
|
||||
fn install(self, class: VALUE, name: &str) {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
class,
|
||||
CString::new(name).unwrap().as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RubyMethod for extern "C" fn(VALUE, VALUE) -> VALUE {
|
||||
fn install(self, class: VALUE, name: &str) {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
class,
|
||||
CString::new(name).unwrap().as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[inline]
|
||||
fn ObjectClass() -> Class {
|
||||
Class(sys::rb_cObject)
|
||||
}
|
||||
|
||||
impl Class {
|
||||
pub fn new(name: &str) -> Class {
|
||||
ObjectClass().subclass(name)
|
||||
}
|
||||
|
||||
pub fn subclass(&self, name: &str) -> Class {
|
||||
unsafe {
|
||||
Class(sys::rb_define_class(CString::new(name).unwrap().as_ptr(), self.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn define_method<T: RubyMethod>(&self, name: &str, method: T) {
|
||||
method.install(self.0, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
#[macro_export]
|
||||
macro_rules! declare_types {
|
||||
{ $(#[$attr:meta])* pub class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
define_class! { $(#[$attr])* pub class $cls { $($body)* } $($rest)* }
|
||||
};
|
||||
|
||||
{ $(#[$attr:meta])* class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
define_class! { $(#[$attr])* class $cls { $($body)* } $($rest)* }
|
||||
};
|
||||
|
||||
{ $(#[$attr:meta])* pub reopen class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
reopen_class! { $(#[$attr])* pub class $cls { $($body)* } $($rest)* }
|
||||
};
|
||||
|
||||
{ $(#[$attr:meta])* reopen class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
reopen_class! { $(#[$attr])* class $cls { $($body)* } $($rest)* }
|
||||
};
|
||||
|
||||
{ } => { };
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! define_class {
|
||||
{ $(#[$attr:meta])* class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
$(#[$attr])*
|
||||
struct $cls($crate::sys::VALUE);
|
||||
|
||||
class_definition! { $cls ; () ; () ; $($body)* }
|
||||
|
||||
declare_types! { $($rest)* }
|
||||
};
|
||||
|
||||
{ $(#[$attr:meta])* pub class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
$(#[$attr])*
|
||||
pub struct $cls($crate::sys::VALUE);
|
||||
|
||||
class_definition! { $cls ; () ; (); $($body)* }
|
||||
|
||||
declare_types! { $($rest)* }
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! class_definition {
|
||||
( $cls:ident; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt , $($arg:ident : $argty:ty),* ) $body:block $($rest:tt)* ) => {
|
||||
class_definition! {
|
||||
$cls ;
|
||||
($($mimpl)* pub fn $name($self_arg, $($arg : $argty),*) -> $crate::sys::VALUE $body) ;
|
||||
($($mdef)* {
|
||||
extern "C" fn __ruby_method__(rb_self: $cls, $($arg : $crate::sys::VALUE),*) -> $crate::sys::VALUE {
|
||||
let checked = __checked_call__(rb_self, $($arg),*);
|
||||
match checked {
|
||||
Ok(val) => val,
|
||||
Err(err) => { println!("TYPE ERROR: {:?}", err); $crate::sys::Qnil }
|
||||
}
|
||||
}
|
||||
|
||||
fn __checked_call__(rb_self: $cls, $($arg : $crate::sys::VALUE),*) -> Result<$crate::sys::VALUE, ::std::ffi::CString> {
|
||||
#[allow(unused_imports)]
|
||||
use $crate::{ToRust};
|
||||
|
||||
$(
|
||||
let $arg = try!($crate::UncheckedValue::<$argty>::to_checked($arg));
|
||||
)*
|
||||
|
||||
$(
|
||||
let $arg = $arg.to_rust();
|
||||
)*
|
||||
|
||||
Ok(rb_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; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt ) $body:block $($rest:tt)* ) => {
|
||||
class_definition! { $cls ; ($($mimpl)*); ($($mdef)*); def $name( $self_arg, ) $body $($rest)* }
|
||||
};
|
||||
|
||||
( $cls:ident ; ($($mimpl:tt)*) ; ($($mdef:block)*) ; ) => {
|
||||
item! {
|
||||
impl $cls {
|
||||
$($mimpl)*
|
||||
}
|
||||
}
|
||||
|
||||
init! {
|
||||
$crate::ClassDefinition::new(stringify!($cls))$(.define_method($mdef))*;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! reopen_class {
|
||||
{ $(#[$attr:meta])* class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
$(#[$attr])*
|
||||
struct $cls($crate::sys::VALUE);
|
||||
|
||||
reopen_class_definition! { $cls ; () ; () ; $($body)* }
|
||||
|
||||
declare_types! { $($rest)* }
|
||||
};
|
||||
|
||||
{ $(#[$attr:meta])* pub class $cls:ident { $($body:tt)* } $($rest:tt)* } => {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
$(#[$attr])*
|
||||
pub struct $cls($crate::sys::VALUE);
|
||||
|
||||
reopen_class_definition! { $cls ; () ; (); $($body)* }
|
||||
|
||||
declare_types! { $($rest)* }
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! reopen_class_definition {
|
||||
( $cls:ident; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt , $($arg:ident : $argty:ty),* ) $body:block $($rest:tt)* ) => {
|
||||
reopen_class_definition! {
|
||||
$cls ;
|
||||
($($mimpl)* pub fn $name($self_arg, $($arg : $argty),*) -> $crate::sys::VALUE $body) ;
|
||||
($($mdef)* {
|
||||
extern "C" fn __ruby_method__(rb_self: $cls, $($arg : $crate::sys::VALUE),*) -> $crate::sys::VALUE {
|
||||
let checked = __checked_call__(rb_self, $($arg),*);
|
||||
match checked {
|
||||
Ok(val) => val,
|
||||
Err(err) => { println!("TYPE ERROR: {:?}", err); $crate::sys::Qnil }
|
||||
}
|
||||
}
|
||||
|
||||
fn __checked_call__(rb_self: $cls, $($arg : $crate::sys::VALUE),*) -> Result<$crate::sys::VALUE, ::std::ffi::CString> {
|
||||
#[allow(unused_imports)]
|
||||
use $crate::{ToRust};
|
||||
|
||||
$(
|
||||
let $arg = try!($crate::UncheckedValue::<$argty>::to_checked($arg));
|
||||
)*
|
||||
|
||||
$(
|
||||
let $arg = $arg.to_rust();
|
||||
)*
|
||||
|
||||
Ok(rb_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; ($($mimpl:tt)*) ; ($($mdef:tt)*) ; def $name:ident( $self_arg:tt ) $body:block $($rest:tt)* ) => {
|
||||
reopen_class_definition! { $cls ; ($($mimpl)*); ($($mdef)*); def $name( $self_arg, ) $body $($rest)* }
|
||||
};
|
||||
|
||||
( $cls:ident ; ($($mimpl:tt)*) ; ($($mdef:block)*) ; ) => {
|
||||
item! {
|
||||
impl $cls {
|
||||
$($mimpl)*
|
||||
}
|
||||
}
|
||||
|
||||
init! {
|
||||
$crate::ClassDefinition::reopen(stringify!($cls))$(.define_method($mdef))*;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! item {
|
||||
($it: item) => { $it }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! replace_expr {
|
||||
($_t:tt $sub:expr) => {$sub};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! method_arity {
|
||||
( $($id:pat ),* ) => {
|
||||
{ 0isize $(+ replace_expr!($id 1isize))* }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue