Initial commit

This commit is contained in:
Godhuda 2016-04-08 18:27:05 -07:00 committed by Yehuda Katz
commit a19b5df18a
51 changed files with 1889 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
// Place your settings in this file to overwrite default and user settings.
{
"rust.formatOnSave": false
}

16
Cargo.toml Normal file
View File

@ -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"

129
README.md Normal file
View File

@ -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`.

13
console.d.ts vendored Normal file
View File

@ -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

View File

@ -0,0 +1,8 @@
[package]
name = "libcruby-sys"
version = "0.1.0"
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
[dependencies]
libc = "*"

View File

@ -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;
}

3
examples/console/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
*.bundle
*.gem

35
examples/console/Cargo.lock generated Normal file
View File

@ -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)",
]

View File

@ -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

4
examples/console/Gemfile Normal file
View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'helix_runtime', path: '../../ruby'
gem 'rake', '~> 10.0'

View File

@ -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

21
examples/console/Rakefile Executable file
View File

@ -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

View File

@ -0,0 +1,2 @@
require 'helix_runtime'
require 'console/native'

144
examples/console/src/lib.rs Normal file
View File

@ -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);
// }
// }

3
examples/membership/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
*.bundle
*.gem

View File

@ -0,0 +1,2 @@
--color
--require spec_helper

View File

@ -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

View File

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'helix_runtime', path: '../../ruby'
gem 'rake', '~> 10.0'
gem 'rspec', '~> 3.4'

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'membership'

View File

@ -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()
}
}

12
ruby/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.gem
Makefile

2
ruby/.rspec Normal file
View File

@ -0,0 +1,2 @@
--format documentation
--color

4
ruby/.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: ruby
rvm:
- 2.2.2
before_install: gem install bundler -v 1.10.6

6
ruby/Gemfile Normal file
View File

@ -0,0 +1,6 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in ruby.gemspec
gemspec
gem 'neversaydie', github: 'tenderlove/neversaydie'

1
ruby/README.md Normal file
View File

@ -0,0 +1 @@
# Helix Runtime

24
ruby/Rakefile Normal file
View File

@ -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

View File

View File

@ -0,0 +1,2 @@
require "mkmf"
create_makefile "helix_runtime/native"

View File

@ -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);
}

View File

@ -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 */

View File

@ -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

View File

@ -0,0 +1,2 @@
require "helix_runtime/version"
require "helix_runtime/native"

View File

@ -0,0 +1,3 @@
module HelixRuntime
VERSION = "0.5.0"
end

View File

@ -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

34
ruby/spec/spec_helper.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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"

46
src/class_definition.rs Normal file
View File

@ -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
}
}

27
src/coercions/mod.rs Normal file
View File

@ -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;
}

26
src/coercions/slice.rs Normal file
View File

@ -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) }
}
}

27
src/coercions/string.rs Normal file
View File

@ -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()
}
}

96
src/lib.rs Normal file
View File

@ -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);
}
}

208
src/macros.rs Normal file
View File

@ -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))* }
}
}