mirror of https://github.com/tildeio/helix
Compare commits
101 Commits
Author | SHA1 | Date |
---|---|---|
Godfrey Chan | 33df62d90e | |
Peter Wagenet | dee30aa4fd | |
Peter Wagenet | 8b5b55d9b0 | |
Peter Wagenet | 4a8e52cb56 | |
Peter Wagenet | ed7a3bb730 | |
Peter Wagenet | ca60150948 | |
Yehuda Katz | 73e0534a17 | |
Godfrey Chan | 239f1de54b | |
Godfrey Chan | 750be7995d | |
Godfrey Chan | a8e1f37362 | |
Godfrey Chan | 358f12ae18 | |
Godfrey Chan | af2042154e | |
Godfrey Chan | cdbe494ecf | |
konstin | 151b7ac68f | |
konstin | 538a1c9fa9 | |
konstin | f5840040ef | |
konstin | 6733f74b0d | |
konstin | f148ac0d62 | |
Godfrey Chan | 0baf995e04 | |
Terence Lee | 088a636192 | |
Terence Lee | 7dbc5cf3e9 | |
Godfrey Chan | 68b9daeeee | |
Godfrey Chan | c6c0011b4b | |
Godfrey Chan | 6fa52887c9 | |
Godfrey Chan | 16eb12ca98 | |
Godfrey Chan | 98b003c5bb | |
Godfrey Chan | be0cffa442 | |
Godfrey Chan | b6a3e5acc9 | |
Godfrey Chan | 0dbe9d5b4f | |
Godfrey Chan | faaa6b1b26 | |
Godfrey Chan | 32d6a67b11 | |
Godfrey Chan | fca1620cf8 | |
Godfrey Chan | 7e2ca2f418 | |
Godfrey Chan | bda5141f37 | |
Yehuda Katz | 6265a62c8d | |
Sean Griffin | 3a24bf2124 | |
Yehuda Katz | 07d1e01647 | |
Godfrey Chan | 05e5dda6de | |
Delton Ding | b00d9ad0d4 | |
Sean Griffin | 91fe2c5828 | |
Godfrey Chan | 8ea2f0573a | |
Sean Griffin | b5965c87d1 | |
Yehuda Katz | 60cb64d615 | |
Yehuda Katz | 7436ff6624 | |
Sean Griffin | ca7184e180 | |
Sean Griffin | 0eb962e44a | |
Sean Griffin | 8b97e143ff | |
Godfrey Chan | 4216a3a758 | |
Godfrey Chan | fed2e7889d | |
Peter Wagenet | a7ad6e826e | |
Peter Wagenet | 1880ece06b | |
Peter Wagenet | 48f0349ba1 | |
Peter Wagenet | 17b0fbb15b | |
Godfrey Chan | 263ddf9937 | |
Godfrey Chan | f4dcbf6ea1 | |
Godfrey Chan | 0d94bf2bb2 | |
Godfrey Chan | c485529059 | |
Godfrey Chan | 08cbc9416b | |
Godfrey Chan | 3adc5fd5b7 | |
Godfrey Chan | 0cdd075c22 | |
Godfrey Chan | 65a98c65d1 | |
Godfrey Chan | 8380713764 | |
Godfrey Chan | 5ce16e9550 | |
Godfrey Chan | ff2a9d2a59 | |
Godfrey Chan | f584a08f5b | |
Godfrey Chan | 385c9f1441 | |
Godfrey Chan | 259b556943 | |
Godfrey Chan | dac16373ae | |
Peter Wagenet | ce8f4430bc | |
Terence Lee | 73d3258405 | |
Terence Lee | aec4d726e1 | |
Godfrey Chan | 37b2f2f3e9 | |
mortyccp | e2cf3df819 | |
Godfrey Chan | e6bcbec37b | |
Godfrey Chan | d0a4460dbb | |
Godfrey Chan | cfdf0ce1ea | |
Godfrey Chan | d9ace88cc9 | |
Godfrey Chan | 44c790244a | |
Terence Lee | 6d3675c0f8 | |
Godfrey Chan | b9f090ed28 | |
Godfrey Chan | 9b638ac151 | |
Godfrey Chan | 144157c064 | |
Godfrey Chan | 7ea173ca0a | |
Ashe Connor | 41064303ef | |
Godfrey Chan | ab7ba71b6a | |
Yehuda Katz | 21c6e00802 | |
Godfrey Chan | 58d8da10f2 | |
Yehuda Katz | afb61e9113 | |
Godfrey Chan | f64727dbea | |
Godfrey Chan | eeb4d56b08 | |
Godfrey Chan | 856244698f | |
Terence Lee | 2864f15db3 | |
Terence Lee | 4ff13d2426 | |
Terence Lee | f38bbdbf60 | |
Godfrey Chan | 5e5f2d9f70 | |
Godfrey Chan | cabd43758f | |
Terence Lee | 96dc384f0e | |
Godfrey Chan | e43599a1bf | |
Godfrey Chan | e47b390d97 | |
Peter Wagenet | 44be98056e | |
Ville Lautanala | ba58f81ad8 |
|
@ -3,7 +3,8 @@ cache:
|
|||
- '%HOMEDRIVE%%HOMEPATH%\.multirust -> .appveyor.yml'
|
||||
|
||||
environment:
|
||||
EXAMPLES: duration calculator console membership text_transform turbo_blank
|
||||
# can't run game_of_life since termion doesn't support windows https://github.com/redox-os/termion/issues/103
|
||||
EXAMPLES: unit calculator console duration docopt geometry json_builder membership text_transform turbo_blank
|
||||
VERBOSE: true
|
||||
RUST_BACKTRACE: 1
|
||||
matrix:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
target
|
||||
Cargo.lock
|
||||
Gemfile.lock
|
||||
.vscode
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -21,7 +21,7 @@ cache:
|
|||
|
||||
env:
|
||||
global:
|
||||
- EXAMPLES="calculator console membership turbo_blank duration"
|
||||
- EXAMPLES="unit calculator console duration docopt game_of_life geometry json_builder membership text_transform turbo_blank"
|
||||
- VERBOSE=true
|
||||
- RUST_BACKTRACE=1
|
||||
- RUST_VERSION=stable
|
||||
|
@ -45,6 +45,7 @@ before_install:
|
|||
- if [ ! -e "$HOME/.cargo/bin" ]; then curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain $RUST_VERSION -y; fi
|
||||
- export PATH="$HOME/.cargo/bin:$PATH"
|
||||
- rustup default $RUST_VERSION
|
||||
- gem install bundler
|
||||
|
||||
install:
|
||||
- ./scripts/ci-install
|
||||
|
@ -74,6 +75,9 @@ script: |
|
|||
popd
|
||||
|
||||
echo -e "Publishing libcruby-sys crate...\n"
|
||||
# need to do this since there's a .gitignore with *.lib files
|
||||
# there's a bug in Cargo, where include isn't overriding exclude
|
||||
rm -rf .git
|
||||
pushd crates/libcruby-sys
|
||||
HELIX_LIB_DIR=$PWD cargo publish
|
||||
popd
|
||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,3 +1,47 @@
|
|||
## 0.7.5 (June 4, 2018)
|
||||
|
||||
* [IMPROVEMENT] Add coercion for `usize` and `isize`
|
||||
|
||||
## 0.7.4 (June 2, 2018)
|
||||
|
||||
* [BUGFIX] Ensure classes with a `struct` also defines `initialize`
|
||||
* [BUGFIX] Ensure Ruby Strings have the correct encoding before performing coercion
|
||||
* [BUGFIX] Abort rake task when `cargo build` fails
|
||||
* [IMPROVEMENT] Add coercion for tuples
|
||||
|
||||
## 0.7.3 (March 6, 2018)
|
||||
|
||||
* [IMPROVEMENT] Various improvements to the underlying `libcruby-sys` library
|
||||
|
||||
## 0.7.2 (October 9, 2017)
|
||||
|
||||
* [IMPROVEMENT] Add coercion for Symbols
|
||||
* [IMPROVEMENT] Allow consuming self in methods
|
||||
|
||||
## 0.7.1 (October 6, 2017)
|
||||
|
||||
* [IMPROVEMENT] Add coercion for `Vec` and `HashMap`
|
||||
|
||||
## 0.7.0 (October 3, 2017)
|
||||
|
||||
* [IMPROVEMENT] Add error message for parse errors
|
||||
* [IMPREVEMENT] Support #[ruby_name] remapping for classes
|
||||
* [IMPREVEMENT] Support returning an exception to Ruby (via `Result` coercion)
|
||||
* [BUGFIX] Fix build erros for project names with more than one `-`
|
||||
* [EXPERIMENTAL] Make it possible to implement `FromRuby` and `ToRuby` for custom types
|
||||
|
||||
## 0.6.4 (September 7, 2017)
|
||||
|
||||
* [BUGFIX] Compile 32-bit windows .lib with 32-bit toolchain
|
||||
|
||||
## 0.6.3 (August 30, 2017)
|
||||
|
||||
* [BUGFIX] Include *.lib in libcruby-sys
|
||||
|
||||
## 0.6.2 (August 29, 2017)
|
||||
|
||||
* [DEPRECATION] Deprecate passing project name to `BuildTask`
|
||||
|
||||
## 0.6.1 (May 18, 2017)
|
||||
|
||||
* [BUGFIX] Fix path in copy_dll task for Windows installation
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "helix"
|
||||
version = "0.6.1"
|
||||
version = "0.7.5"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
description = "Embed Rust in your Ruby"
|
||||
documentation = "https://usehelix.com/documentation"
|
||||
|
@ -20,18 +20,14 @@ travis-ci = { repository = "tildeio/helix", branch = "master" }
|
|||
appveyor = { repository = "tildeio/helix", branch = "master", service = "github" }
|
||||
|
||||
[workspace]
|
||||
|
||||
members = ["examples/calculator", "examples/console", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank"]
|
||||
members = ["examples/calculator", "examples/console", "examples/docopt", "examples/duration", "examples/game_of_life", "examples/geometry", "examples/json_builder", "examples/membership", "examples/text_transform", "examples/turbo_blank", "examples/unit"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.0"
|
||||
|
||||
[dependencies.cslice]
|
||||
version = "0.3"
|
||||
|
||||
[dependencies.libcruby-sys]
|
||||
path = "crates/libcruby-sys"
|
||||
version = "0.6.1"
|
||||
version = "0.7.5"
|
||||
|
||||
[dependencies.cstr-macro]
|
||||
path = "crates/cstr-macro"
|
||||
|
|
115
README.md
115
README.md
|
@ -1,7 +1,33 @@
|
|||
[![Travis Build Status](https://travis-ci.org/tildeio/helix.svg?branch=master)](https://travis-ci.org/tildeio/helix)
|
||||
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/tildeio/helix?branch=master&svg=true)](https://ci.appveyor.com/project/wagenet/helix)
|
||||
|
||||
# Helix
|
||||
# ~~Helix~~
|
||||
|
||||
> :warning: **Deprecated** :warning:
|
||||
>
|
||||
> Sadly, we have made the decision to deprecate this project. While we had
|
||||
> hoped to bootstrap the project to a point where it could flourish as a
|
||||
> community project, unfortunately we ran into a number of roadblocks along the
|
||||
> way, along with the need for significant architectural overhaul. While these
|
||||
> issues are solvable on a technical level, doing so correctly requires more
|
||||
> resources than we have been able to provide and progress has stalled.
|
||||
>
|
||||
> One of our goals was also to integrate our own Skylight agent with Helix,
|
||||
> aligning the company's priorities with the project. While the Skylight agent
|
||||
> is still written in Rust with a thin layer of C bindings (which is the part
|
||||
> Helix would replace), we were not able to get the project to the point where
|
||||
> we felt comfortable running it on our customer's servers. We did not identify
|
||||
> any specific blockers that would prevent us from doing this, but ultimate, we
|
||||
> did not have the necessary time and resources to realize this.
|
||||
>
|
||||
> Since we are a small team, it is unlikely that we will be able to provide the
|
||||
> necessary investment in the foreseeable future to achieve our ambitions for
|
||||
> the project. At this point, we believe it is in everyone's best interest to
|
||||
> formally deprecate the project, accurately reflecting its effective state.
|
||||
> Meanwhile, others in the Ruby and Rust communities have continued to explore
|
||||
> in the adjacent research areas. Some of them have made great progress and
|
||||
> brought new ideas and innovations to the table. We look forward to seeing
|
||||
> these new ideas come to fruition and fill the void we are leaving.
|
||||
|
||||
Helix allows you to write Ruby classes in Rust without having to write the glue code yourself.
|
||||
|
||||
|
@ -82,90 +108,3 @@ hello
|
|||
hello world
|
||||
=> nil
|
||||
```
|
||||
|
||||
## 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
|
||||
ruby! {
|
||||
class Console {
|
||||
def log(&self, string: &str) {
|
||||
println!("LOG: {}", string);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ irb
|
||||
>> require "console"
|
||||
>> Console.new.log({})
|
||||
TypeError: No implicit coercion of {} into String
|
||||
from (irb):2:in `log'
|
||||
from (irb):2
|
||||
from /Users/ykatz/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
|
||||
```
|
||||
|
||||
### 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<T> {
|
||||
fn to_rust(self) -> T;
|
||||
}
|
||||
```
|
||||
|
||||
Implementations of these traits use these concrete types:
|
||||
|
||||
```rust
|
||||
pub type CheckResult<T> = Result<CheckedValue<T>, String /* 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 {
|
||||
let val = unsafe { CheckedValue::<String>::new(sys::rb_inspect(self)) };
|
||||
Err(format!("No implicit conversion of {} into String", val.to_rust()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
|
|
6
Rakefile
6
Rakefile
|
@ -1,19 +1,21 @@
|
|||
desc "Test Helix Examples"
|
||||
task :test do
|
||||
cd "ruby" do
|
||||
sh "bundle exec rake"
|
||||
end
|
||||
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console membership text_transform turbo_blank"
|
||||
examples = ENV["EXAMPLES"] || "unit calculator console docopt duration game_of_life geometry json_builder membership text_transform turbo_blank"
|
||||
|
||||
sh "bash ./examples/runner default #{examples}"
|
||||
end
|
||||
|
||||
desc "Install Helix Examples"
|
||||
task :install do
|
||||
cd "ruby" do
|
||||
sh "bundle"
|
||||
end
|
||||
|
||||
examples = ENV["EXAMPLES"] || "duration calculator console membership text_transform turbo_blank"
|
||||
examples = ENV["EXAMPLES"] || "unit calculator console docopt duration game_of_life geometry json_builder membership text_transform turbo_blank"
|
||||
|
||||
sh "bash ./examples/runner install #{examples}"
|
||||
end
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
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
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "libcruby-sys"
|
||||
version = "0.6.1"
|
||||
version = "0.7.5"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
description = "Ruby bindings"
|
||||
repository = "https://github.com/tildeio/helix"
|
||||
|
@ -11,8 +11,8 @@ include = [
|
|||
"Cargo.toml",
|
||||
"ruby_info.rb",
|
||||
# Keep in sync with version
|
||||
"helix-runtime-0-6-1.i386.lib",
|
||||
"helix-runtime-0-6-1.x86_64.lib"
|
||||
"helix-runtime-0-7-5.i386.lib",
|
||||
"helix-runtime-0-7-5.x86_64.lib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pushd ../../ruby
|
||||
bundle install
|
||||
bundle exec rake native_lib_files
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
extern crate libc;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const PKG_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
@ -14,6 +15,10 @@ pub fn check_version() {
|
|||
if PKG_VERSION != version {
|
||||
panic!("libcsys-ruby version ({}) doesn't match helix_runtime version ({}).", PKG_VERSION, version);
|
||||
}
|
||||
|
||||
if size_of::<usize>() != size_of::<u32>() && size_of::<usize>() != size_of::<u64>() {
|
||||
panic!("unsupported architecture, size_of::<usize>() = {}", size_of::<usize>());
|
||||
}
|
||||
}
|
||||
|
||||
pub type void = libc::c_void;
|
||||
|
@ -22,15 +27,17 @@ pub type c_string = *const libc::c_char;
|
|||
// pub type c_func = extern "C" fn(...);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||
pub struct ID(*mut void);
|
||||
|
||||
unsafe impl Sync for ID {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct VALUE(*mut void);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct RubyException(isize);
|
||||
|
||||
impl RubyException {
|
||||
|
@ -49,6 +56,14 @@ impl RubyException {
|
|||
|
||||
pub const EMPTY_EXCEPTION: RubyException = RubyException(0);
|
||||
|
||||
#[repr(C)]
|
||||
pub enum st_retval {
|
||||
ST_CONTINUE,
|
||||
ST_STOP,
|
||||
ST_DELETE,
|
||||
// ST_CHECK
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, link(name="helix-runtime"))]
|
||||
extern "C" {
|
||||
#[link_name = "HELIX_RUNTIME_VERSION"]
|
||||
|
@ -75,6 +90,9 @@ extern "C" {
|
|||
#[link_name = "rb_cObject"]
|
||||
pub static rb_cObject: VALUE;
|
||||
|
||||
#[link_name = "rb_cBasicObject"]
|
||||
pub static rb_cBasicObject: VALUE;
|
||||
|
||||
#[link_name = "rb_eRuntimeError"]
|
||||
pub static rb_eRuntimeError: VALUE;
|
||||
|
||||
|
@ -99,9 +117,18 @@ extern "C" {
|
|||
#[link_name = "HELIX_RARRAY_CONST_PTR"]
|
||||
pub fn RARRAY_CONST_PTR(array: VALUE) -> *const VALUE;
|
||||
|
||||
#[link_name = "HELIX_RHASH_SIZE"]
|
||||
pub fn RHASH_SIZE(hash: VALUE) -> isize;
|
||||
|
||||
#[link_name = "HELIX_RB_TYPE_P"]
|
||||
pub fn RB_TYPE_P(val: VALUE, rb_type: isize) -> bool;
|
||||
|
||||
#[link_name = "HELIX_RB_NIL_P"]
|
||||
pub fn RB_NIL_P(val: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_RTEST"]
|
||||
pub fn RTEST(val: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_TYPE"]
|
||||
pub fn TYPE(val: VALUE) -> isize;
|
||||
|
||||
|
@ -137,18 +164,33 @@ extern "C" {
|
|||
#[link_name = "HELIX_F642NUM"]
|
||||
pub fn F642NUM(num: f64) -> VALUE;
|
||||
|
||||
#[link_name = "HELIX_OBJ_FROZEN"]
|
||||
pub fn OBJ_FROZEN(v: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_CLASS_OF"]
|
||||
pub fn CLASS_OF(v: VALUE) -> VALUE;
|
||||
|
||||
#[link_name = "HELIX_T_OBJECT"]
|
||||
pub static T_OBJECT: isize;
|
||||
|
||||
#[link_name = "HELIX_T_STRING"]
|
||||
pub static T_STRING: isize;
|
||||
|
||||
#[link_name = "HELIX_T_ARRAY"]
|
||||
pub static T_ARRAY: isize;
|
||||
|
||||
#[link_name = "HELIX_T_HASH"]
|
||||
pub static T_HASH: isize;
|
||||
|
||||
#[link_name = "HELIX_T_TRUE"]
|
||||
pub static T_TRUE: isize;
|
||||
|
||||
#[link_name = "HELIX_T_FALSE"]
|
||||
pub static T_FALSE: isize;
|
||||
|
||||
#[link_name = "HELIX_T_SYMBOL"]
|
||||
pub static T_SYMBOL: isize;
|
||||
|
||||
#[link_name = "HELIX_T_FIXNUM"]
|
||||
pub static T_FIXNUM: isize;
|
||||
|
||||
|
@ -158,6 +200,9 @@ extern "C" {
|
|||
#[link_name = "HELIX_T_BIGNUM"]
|
||||
pub static T_BIGNUM: isize;
|
||||
|
||||
#[link_name = "HELIX_T_DATA"]
|
||||
pub static T_DATA: isize;
|
||||
|
||||
// unknown if working?
|
||||
// fn rb_define_variable(name: c_string, value: *const VALUE);
|
||||
pub fn rb_obj_class(obj: VALUE) -> VALUE;
|
||||
|
@ -168,20 +213,49 @@ extern "C" {
|
|||
pub fn rb_define_module_under(namespace: VALUE, name: c_string) -> VALUE;
|
||||
pub fn rb_define_class(name: c_string, superclass: VALUE) -> VALUE;
|
||||
pub fn rb_define_class_under(namespace: VALUE, name: c_string, superclass: VALUE) -> VALUE;
|
||||
pub fn rb_define_alloc_func(klass: VALUE, func: extern "C" fn(klass: VALUE) -> VALUE);
|
||||
pub fn rb_define_alloc_func(class: VALUE, func: extern "C" fn(class: VALUE) -> VALUE);
|
||||
pub fn rb_define_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
pub fn rb_define_singleton_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
pub fn rb_undef_method(class: VALUE, name: c_string);
|
||||
pub fn rb_enc_get_index(obj: VALUE) -> isize;
|
||||
pub fn rb_utf8_encindex() -> isize;
|
||||
pub fn rb_sprintf(specifier: c_string, ...) -> VALUE;
|
||||
pub fn rb_inspect(value: VALUE) -> VALUE;
|
||||
pub fn rb_intern(string: c_string) -> ID;
|
||||
pub fn rb_raise(exc: VALUE, string: c_string, ...) -> !;
|
||||
pub fn rb_intern_str(string: VALUE) -> ID;
|
||||
pub fn rb_sym2id(symbol: VALUE) -> ID;
|
||||
pub fn rb_id2sym(id: ID) -> VALUE;
|
||||
pub fn rb_id2str(id: ID) -> VALUE;
|
||||
pub fn rb_ary_new() -> VALUE;
|
||||
pub fn rb_ary_new_capa(capa: isize) -> VALUE;
|
||||
pub fn rb_ary_entry(ary: VALUE, offset: isize) -> VALUE;
|
||||
pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE;
|
||||
pub fn rb_hash_new() -> VALUE;
|
||||
pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE;
|
||||
pub fn rb_hash_aset(hash: VALUE, key: VALUE, value: VALUE) -> VALUE;
|
||||
pub fn rb_hash_foreach(hash: VALUE, f: extern "C" fn(key: VALUE, value: VALUE, farg: *mut void) -> st_retval, farg: *mut void);
|
||||
pub fn rb_gc_mark(value: VALUE);
|
||||
pub fn rb_funcall(value: VALUE, mid: ID, argc: libc::c_int, ...) -> VALUE;
|
||||
pub fn rb_funcallv(value: VALUE, mid: ID, argc: libc::c_int, argv: *const VALUE) -> VALUE;
|
||||
pub fn rb_scan_args(argc: libc::c_int, argv: *const VALUE, fmt: c_string, ...);
|
||||
pub fn rb_block_given_p() -> bool;
|
||||
pub fn rb_yield(value: VALUE) -> VALUE;
|
||||
pub fn rb_obj_dup(value: VALUE) -> VALUE;
|
||||
pub fn rb_obj_init_copy(value: VALUE, orig: VALUE) -> VALUE;
|
||||
|
||||
pub fn rb_raise(exc: VALUE, string: c_string, ...) -> !;
|
||||
pub fn rb_jump_tag(state: RubyException) -> !;
|
||||
pub fn rb_protect(try: extern "C" fn(v: *mut void) -> VALUE,
|
||||
arg: *mut void,
|
||||
state: *mut RubyException)
|
||||
-> VALUE;
|
||||
|
||||
#[link_name = "HELIX_rb_str_valid_encoding_p"]
|
||||
pub fn rb_str_valid_encoding_p(string: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_rb_str_ascii_only_p"]
|
||||
pub fn rb_str_ascii_only_p(string: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_Data_Wrap_Struct"]
|
||||
pub fn Data_Wrap_Struct(klass: VALUE, mark: extern "C" fn(*mut void), free: extern "C" fn(*mut void), data: *mut void) -> VALUE;
|
||||
|
||||
|
@ -191,3 +265,39 @@ extern "C" {
|
|||
#[link_name = "HELIX_Data_Set_Struct_Value"]
|
||||
pub fn Data_Set_Struct_Value(obj: VALUE, data: *mut void);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn NUM2USIZE(v: VALUE) -> usize {
|
||||
if size_of::<usize>() == size_of::<u32>() {
|
||||
NUM2U32(v) as usize
|
||||
} else {
|
||||
NUM2U64(v) as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn USIZE2NUM(num: usize) -> VALUE {
|
||||
if size_of::<usize>() == size_of::<u32>() {
|
||||
U322NUM(num as u32)
|
||||
} else {
|
||||
U642NUM(num as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn NUM2ISIZE(v: VALUE) -> isize {
|
||||
if size_of::<isize>() == size_of::<i32>() {
|
||||
NUM2I32(v) as isize
|
||||
} else {
|
||||
NUM2I64(v) as isize
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn ISIZE2NUM(num: isize) -> VALUE {
|
||||
if size_of::<isize>() == size_of::<i32>() {
|
||||
I322NUM(num as i32)
|
||||
} else {
|
||||
I642NUM(num as i64)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
||||
gem 'colorize'
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
colorize (0.8.1)
|
||||
diff-lcs (1.3)
|
||||
rake (10.4.2)
|
||||
rspec (3.5.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
thor (0.19.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
colorize
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.4)
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("calculator") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -10,6 +10,12 @@ describe Calculator do
|
|||
expect(Calculator.multiply(1.23, -4.56)).to eq(1.23 * -4.56)
|
||||
expect(Calculator.multiply(3, 5)).to eq(15)
|
||||
end
|
||||
|
||||
it "can divide numbers" do
|
||||
expect(Calculator.divide(1.23, -4.56)).to eq(1.23 / -4.56)
|
||||
expect(Calculator.divide(4, 2)).to eq(2)
|
||||
expect { Calculator.divide(4, 0) }.to raise_error("Division by zero")
|
||||
end
|
||||
end
|
||||
|
||||
describe Adder do
|
||||
|
@ -25,3 +31,11 @@ describe Multiplier do
|
|||
expect(Multiplier.new(3).(5)).to eq(15)
|
||||
end
|
||||
end
|
||||
|
||||
describe Divider do
|
||||
it "can divide numbers" do
|
||||
expect(Divider.new(1.23).(-4.56)).to eq(1.23 / -4.56)
|
||||
expect(Divider.new(4).(2)).to eq(2)
|
||||
expect { Divider.new(4).(0) }.to raise_error("Division by zero")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
|
@ -10,6 +12,10 @@ ruby! {
|
|||
def multiply(lhs: f64, rhs: f64) -> f64 {
|
||||
Multiplier::new(lhs).call(rhs)
|
||||
}
|
||||
|
||||
def divide(lhs: f64, rhs: f64) -> Result<f64, &'static str> {
|
||||
Divider::new(lhs).call(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
class Adder {
|
||||
|
@ -39,4 +45,22 @@ ruby! {
|
|||
self.lhs * rhs
|
||||
}
|
||||
}
|
||||
|
||||
class Divider {
|
||||
struct {
|
||||
lhs: f64
|
||||
}
|
||||
|
||||
def initialize(helix, value: f64) {
|
||||
Divider { helix, lhs: value }
|
||||
}
|
||||
|
||||
def call(&self, rhs: f64) -> Result<f64, &'static str> {
|
||||
if rhs == 0f64 {
|
||||
Err("Division by zero")
|
||||
} else {
|
||||
Ok(self.lhs / rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
||||
gem 'colorize'
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
colorize (0.8.1)
|
||||
diff-lcs (1.3)
|
||||
rake (10.5.0)
|
||||
rspec (3.5.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
thor (0.19.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
colorize
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.4)
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("console") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -8,6 +8,10 @@ describe "Console" do
|
|||
expect { console.log("hello") }.to println("hello")
|
||||
end
|
||||
|
||||
it "can log an array of strings" do
|
||||
expect { console.log_lines(["hello", "world"]) }.to println("hello\nworld")
|
||||
end
|
||||
|
||||
it "can inspect itself" do
|
||||
expect { console.inspect }.to print(/Console { .+ }\n\z/)
|
||||
end
|
||||
|
@ -29,18 +33,36 @@ describe "Console" do
|
|||
expect(console.colorize("hello")).to eq("hello".colorize(:red))
|
||||
end
|
||||
|
||||
it "can return an array of strings" do
|
||||
expect(console.colorize_lines(["hello", "world"])).to eq(["hello".colorize(:red), "world".colorize(:red)])
|
||||
end
|
||||
|
||||
it "can return a boolean" do
|
||||
expect(console.is_red("hello")).to eq(false)
|
||||
expect(console.is_red("hello".colorize(:red))).to eq(true)
|
||||
end
|
||||
|
||||
it "can handle panics" do
|
||||
expect { console.freak_out }.to raise_error(RuntimeError, "Aaaaahhhhh!!!!!")
|
||||
# Do it twice to make sure we cleaned up correctly the first time
|
||||
expect { console.freak_out }.to raise_error(RuntimeError, "Aaaaahhhhh!!!!!")
|
||||
[:raise, :raise_panic, :panic].each do |method|
|
||||
it "can handle #{method}" do
|
||||
expect { console.send(method) }.to raise_error(RuntimeError, "raised from Rust with `#{method}`")
|
||||
# Do it twice to make sure we cleaned up correctly the first time
|
||||
expect { console.send(method) }.to raise_error(RuntimeError, "raised from Rust with `#{method}`")
|
||||
end
|
||||
end
|
||||
|
||||
it "can handle invalid arguments" do
|
||||
expect { console.log(123) }.to raise_error(TypeError, "Expected a UTF-8 String, got 123")
|
||||
describe "invalid arguments" do
|
||||
it "can handle non-strings" do
|
||||
expect { console.log(123) }.to raise_error(TypeError, "Expected a String, got 123")
|
||||
end
|
||||
|
||||
it "raises on non UTF-8 strings" do
|
||||
str = "hello".encode("BIG5")
|
||||
expect { console.log(str) }.to raise_error(TypeError, "Expected an UTF-8 String, got #{str.inspect}")
|
||||
end
|
||||
|
||||
it "raises on invalid UTF-8 strings" do
|
||||
str = "\330"
|
||||
expect { console.log(str) }.to raise_error(TypeError, "Expected a valid UTF-8 String, got #{str.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
extern crate helix;
|
||||
|
||||
ruby! {
|
||||
#[derive(Debug)]
|
||||
class Console {
|
||||
def log(&self, string: String) {
|
||||
println!("{}", string);
|
||||
}
|
||||
|
||||
def log_lines(&self, lines: Vec<String>) {
|
||||
for l in lines { self.log(l) }
|
||||
}
|
||||
|
||||
def inspect(&self) {
|
||||
println!("{:?}", self)
|
||||
}
|
||||
|
@ -29,12 +34,24 @@ ruby! {
|
|||
format!("\x1B[0;31;49m{}\x1B[0m", string)
|
||||
}
|
||||
|
||||
def colorize_lines(&self, lines: Vec<String>) -> Vec<String> {
|
||||
lines.into_iter().map(|l| self.colorize(l) ).collect()
|
||||
}
|
||||
|
||||
def is_red(&self, string: String) -> bool {
|
||||
string.starts_with("\x1B[0;31;49m") && string.ends_with("\x1B[0m")
|
||||
}
|
||||
|
||||
def freak_out(&self) {
|
||||
throw!("Aaaaahhhhh!!!!!");
|
||||
def raise(&self) -> Result<(), helix::Error> {
|
||||
raise!("raised from Rust with `raise`");
|
||||
}
|
||||
|
||||
def raise_panic(&self) {
|
||||
raise_panic!("raised from Rust with `raise_panic`");
|
||||
}
|
||||
|
||||
def panic(&self) {
|
||||
panic!("raised from Rust with `panic`");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "docopt"
|
||||
version = "0.1.0"
|
||||
authors = ["Godhuda <engineering+godhuda@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
||||
|
||||
[dependencies]
|
||||
docopt = "*"
|
|
@ -0,0 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
||||
gem 'colorize'
|
|
@ -0,0 +1,20 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require 'rspec/core/rake_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :spec => :build
|
||||
task :default => :spec
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'docopt/native'
|
|
@ -0,0 +1,95 @@
|
|||
require "spec_helper"
|
||||
USAGE = <<USAGE
|
||||
Naval Fate.
|
||||
|
||||
Usage:
|
||||
naval_fate ship new <name>...
|
||||
naval_fate ship <name> move <x> <y> [--speed=<kn>] [--acc=<kns>]
|
||||
naval_fate ship shoot <x> <y>
|
||||
naval_fate mine (set|remove) <x> <y> [--moored | --drifting]
|
||||
naval_fate (-h | --help)
|
||||
naval_fate --version
|
||||
naval_fate (-o | --option)
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--version Show version.
|
||||
--speed=<kn> Speed in knots [default: 10].
|
||||
--acc=<kns> Speed in knots per second.
|
||||
--moored Moored (anchored) mine.
|
||||
--drifting Drifting mine.
|
||||
-o --option Test long and short option.
|
||||
USAGE
|
||||
|
||||
describe "Docopt" do
|
||||
it "can not be constructed" do
|
||||
expect { Docopt.new }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it "should parse and return bools" do
|
||||
argv = "naval_fate --help".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["--help"]).to be true
|
||||
end
|
||||
|
||||
it "should parse strings" do
|
||||
x = "1"
|
||||
y = "2"
|
||||
argv = "naval_fate ship shoot #{x} #{y}".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["ship"]).to eq(true)
|
||||
expect(options["shoot"]).to eq(true)
|
||||
expect(options["<x>"]).to eq(x)
|
||||
expect(options["<y>"]).to eq(y)
|
||||
end
|
||||
|
||||
it "should set short and long options are when provided" do
|
||||
argv = "naval_fate -o".split
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["--option"]).to eq(true)
|
||||
argv = "naval_fate --option".split
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["--option"]).to eq(true)
|
||||
end
|
||||
|
||||
it "should parse an array of strings" do
|
||||
names = %w(enterprise mission)
|
||||
argv = "naval_fate ship new #{names.join(" ")}".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["ship"]).to eq(true)
|
||||
expect(options["<name>"]).to eq(names)
|
||||
end
|
||||
|
||||
it "should use default values" do
|
||||
argv = "naval_fate ship foo move 0 0".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["--speed"]).to eq("10")
|
||||
end
|
||||
|
||||
it "should return nil for unused options" do
|
||||
argv = "naval_fate ship foo move 0 0".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["--acc"]).to be_nil
|
||||
end
|
||||
|
||||
it "should handle complex cases" do
|
||||
name = "enterprise"
|
||||
x = "1"
|
||||
y = "2"
|
||||
speed = "10"
|
||||
argv = "naval_fate ship #{name} move #{x} #{y} --speed=#{speed}".split
|
||||
|
||||
options = Docopt.parse(USAGE, argv)
|
||||
expect(options["ship"]).to eq(true)
|
||||
expect(options["move"]).to eq(true)
|
||||
expect(options["<name>"]).to eq([name])
|
||||
expect(options["<x>"]).to eq(x)
|
||||
expect(options["<y>"]).to eq(y)
|
||||
expect(options["--speed"]).to eq(speed)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'docopt'
|
||||
|
||||
RSpec.configure do |config|
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
extern crate docopt;
|
||||
|
||||
use docopt::ArgvMap;
|
||||
use helix::{ToRuby, ToRubyResult};
|
||||
|
||||
ruby! {
|
||||
class Docopt {
|
||||
struct {
|
||||
options: ArgvMap,
|
||||
}
|
||||
|
||||
#[ruby_visibility=unexported]
|
||||
def initialize(helix, options: ArgvMap) {
|
||||
Docopt { helix, options }
|
||||
}
|
||||
|
||||
def parse(usage: String, argv: Vec<String>) -> Result<Docopt, String> {
|
||||
let result = docopt::Docopt::new(usage)
|
||||
.and_then(|d| d.help(false).argv(argv.into_iter()).parse());
|
||||
|
||||
match result {
|
||||
Ok(args) => Ok(Docopt::new(args)),
|
||||
Err(error) => match error {
|
||||
docopt::Error::WithProgramUsage(e, msg) => {
|
||||
Err(format!("{}\n\n{}\n", e, msg))
|
||||
},
|
||||
e => {
|
||||
Err(format!("{}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[ruby_name="[]"]
|
||||
def get(&self, key: String) -> ToRubyResult {
|
||||
match self.options.map.find(&key) {
|
||||
None => ().to_ruby(),
|
||||
Some(value) => match *value {
|
||||
docopt::Value::Counted(uint) => uint.to_ruby(),
|
||||
docopt::Value::Plain(None) => ().to_ruby(),
|
||||
ref plain @ docopt::Value::Plain(Some(_)) => plain.as_str().to_ruby(),
|
||||
ref switch @ docopt::Value::Switch(_) => switch.as_bool().to_ruby(),
|
||||
ref list @ docopt::Value::List(_) => list.as_vec().to_ruby()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,4 +3,4 @@ source 'https://rubygems.org'
|
|||
gemspec
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'activesupport', '5.1.0.beta1'
|
||||
gem 'activesupport'
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
duration (0.1.0)
|
||||
helix_runtime
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (5.1.0.beta1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
concurrent-ruby (1.0.5)
|
||||
i18n (0.8.1)
|
||||
minitest (5.8.3)
|
||||
rake (10.5.0)
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (= 5.1.0.beta1)
|
||||
bundler (~> 1.11)
|
||||
duration!
|
||||
helix_runtime!
|
||||
minitest (~> 5.0)
|
||||
rake (~> 10.0)
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("duration") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -26,7 +26,6 @@ Gem::Specification.new do |spec|
|
|||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency "helix_runtime"
|
||||
spec.add_development_dependency "bundler", "~> 1.11"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rake", "~> 12.0"
|
||||
spec.add_development_dependency "minitest", "~> 5.0"
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ case ENV["IMPLEMENTATION"]
|
|||
when "RUST"
|
||||
require "duration/native"
|
||||
|
||||
ActiveSupport::Duration = ::Duration
|
||||
ActiveSupport::Duration = ::RustDuration
|
||||
when "RAILS"
|
||||
require "active_support/duration"
|
||||
when "NONE"
|
||||
|
|
|
@ -14,6 +14,7 @@ const SECONDS_PER_MONTH: i64 = 2629746; // 1/12 of a gregorian year
|
|||
const SECONDS_PER_YEAR: i64 = 31556952; // length of a gregorian year (365.2425 days)
|
||||
|
||||
ruby! {
|
||||
#[ruby_name="RustDuration"]
|
||||
class Duration {
|
||||
struct {
|
||||
seconds: Option<i32>,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "game_of_life"
|
||||
version = "0.1.0"
|
||||
authors = ["Godfrey Chan <godfrey@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
||||
|
||||
[dependencies]
|
||||
termion = "*"
|
||||
rand = "*"
|
|
@ -0,0 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
|
@ -0,0 +1,26 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require 'rspec/core/rake_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :demo => :build do
|
||||
$LOAD_PATH.unshift File.expand_path("./lib", __dir__)
|
||||
require 'game_of_life'
|
||||
GameOfLife.random.play!
|
||||
end
|
||||
|
||||
task :spec => :build
|
||||
task :default => :spec
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'game_of_life/native'
|
|
@ -0,0 +1,401 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe GameOfLife do
|
||||
describe '.parse' do
|
||||
context 'malformed inputs' do
|
||||
it 'should reject zero-sized boards' do
|
||||
expect(->() { GameOfLife.parse(0, 0, '') }).to raise_error(/cannot be 0/)
|
||||
expect(->() { GameOfLife.parse(0, 10, '') }).to raise_error(/cannot be 0/)
|
||||
expect(->() { GameOfLife.parse(10, 0, '') }).to raise_error(/cannot be 0/)
|
||||
end
|
||||
|
||||
it 'should reject lines that are too long' do
|
||||
expect(->() { GameOfLife.parse(2, 2, '...') }).to raise_error(/line 1 is too long/)
|
||||
end
|
||||
|
||||
it 'should reject too many lines' do
|
||||
expect(->() { GameOfLife.parse(2, 2, "\n\n\n") }).to raise_error(/too many lines/)
|
||||
end
|
||||
|
||||
it 'should parse correctly' do
|
||||
pattern = <<~GAME
|
||||
..*..**
|
||||
.......
|
||||
**..*..
|
||||
GAME
|
||||
|
||||
game = GameOfLife.parse(7, 3, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'game rules' do
|
||||
context 'a live cell' do
|
||||
context 'with zero live neighbors' do
|
||||
it 'should die, as if by under population' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
...
|
||||
.*.
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one live neighbor' do
|
||||
it 'should die, as if by under population' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
.*.
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with two live neighbors' do
|
||||
it 'should live on' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
.**
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with three live neighbors' do
|
||||
it 'should live on' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
***
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with four live neighbors' do
|
||||
it 'should die, as if by overpopulation' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
***
|
||||
.*.
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'a dead cell' do
|
||||
context 'with zero live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
...
|
||||
...
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one live neighbor' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
...
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with two live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
..*
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with three live neighbors' do
|
||||
it 'should become a live cell, as if by reproduction' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
*.*
|
||||
...
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with four live neighbors' do
|
||||
it 'should stay dead' do
|
||||
game = GameOfLife.parse 3, 3, <<~GAME
|
||||
..*
|
||||
*.*
|
||||
.*.
|
||||
GAME
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game[1,1]).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'known patterns' do
|
||||
shared_examples 'still life' do
|
||||
it 'should stay still' do
|
||||
game = GameOfLife.parse(width, height, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
10.times { game.advance! }
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'oscillator' do |period:|
|
||||
it 'should oscillate' do
|
||||
game = GameOfLife.parse(width, height, pattern)
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
|
||||
game.advance!
|
||||
|
||||
expect(game.to_s).to_not eq(pattern)
|
||||
|
||||
(period - 1).times { game.advance! }
|
||||
|
||||
expect(game.to_s).to eq(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
context 'Block' do
|
||||
let(:width) { 4 }
|
||||
let(:height) { 4 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
....
|
||||
.**.
|
||||
.**.
|
||||
....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Beehive' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
..**..
|
||||
.*..*.
|
||||
..**..
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Loaf' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
..**..
|
||||
.*..*.
|
||||
..*.*.
|
||||
...*..
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Boat' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
.**..
|
||||
.*.*.
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Tub' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
..*..
|
||||
.*.*.
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'still life'
|
||||
end
|
||||
|
||||
context 'Blinker' do
|
||||
let(:width) { 5 }
|
||||
let(:height) { 5 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.....
|
||||
..*..
|
||||
..*..
|
||||
..*..
|
||||
.....
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Toad' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
......
|
||||
..***.
|
||||
.***..
|
||||
......
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Beacon' do
|
||||
let(:width) { 6 }
|
||||
let(:height) { 6 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
......
|
||||
.**...
|
||||
.**...
|
||||
...**.
|
||||
...**.
|
||||
......
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 2
|
||||
end
|
||||
|
||||
context 'Pulsar' do
|
||||
let(:width) { 17 }
|
||||
let(:height) { 17 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
.................
|
||||
.................
|
||||
....***...***....
|
||||
.................
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
....***...***....
|
||||
.................
|
||||
....***...***....
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
..*....*.*....*..
|
||||
.................
|
||||
....***...***....
|
||||
.................
|
||||
.................
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 3
|
||||
end
|
||||
|
||||
context 'Pentadecathlon' do
|
||||
let(:width) { 11 }
|
||||
let(:height) { 18 }
|
||||
let(:pattern) {
|
||||
<<~PATTERN
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
....***....
|
||||
...*...*...
|
||||
...*...*...
|
||||
....***....
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
....***....
|
||||
...*...*...
|
||||
...*...*...
|
||||
....***....
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
PATTERN
|
||||
}
|
||||
|
||||
it_should_behave_like 'oscillator', period: 15
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'game_of_life'
|
|
@ -0,0 +1,378 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
extern crate rand;
|
||||
extern crate termion;
|
||||
|
||||
ruby! {
|
||||
class GameOfLife {
|
||||
struct {
|
||||
width: usize,
|
||||
height: usize,
|
||||
cells: Vec<bool>,
|
||||
}
|
||||
|
||||
def random() -> GameOfLife {
|
||||
use termion::terminal_size;
|
||||
|
||||
let (width, height) = terminal_size().unwrap_or((100, 100));
|
||||
let mut game = GameOfLife::new(width as usize, height as usize);
|
||||
|
||||
game.randomize();
|
||||
|
||||
game
|
||||
}
|
||||
|
||||
def parse(width: usize, height: usize, input: String) -> GameOfLife {
|
||||
let mut game = GameOfLife::new(width, height);
|
||||
|
||||
for (y, line) in input.lines().enumerate() {
|
||||
assert!(y < height, "invalid input: too many lines (the game has a height of {})", height);
|
||||
|
||||
for (x, c) in line.chars().enumerate() {
|
||||
assert!(x < width, "invalid input: line {} is too long (the game has a width of {})", y+1, width);
|
||||
|
||||
if c == '*' {
|
||||
game.set(x, y, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game
|
||||
}
|
||||
|
||||
def initialize(helix, width: usize, height: usize) {
|
||||
assert!(width > 0, "width cannot be 0");
|
||||
assert!(height > 0, "height cannot be 0");
|
||||
|
||||
let mut cells = vec![];
|
||||
|
||||
cells.resize(width * height, false);
|
||||
|
||||
GameOfLife { helix, width, height, cells }
|
||||
}
|
||||
|
||||
#[ruby_name = "[]"]
|
||||
def get(&self, x: usize, y: usize) -> bool {
|
||||
self.cell_at(x, y).is_alive()
|
||||
}
|
||||
|
||||
#[ruby_name = "[]="]
|
||||
def set(&mut self, x: usize, y: usize, is_alive: bool) {
|
||||
self.mut_cell_at(x, y).set(is_alive)
|
||||
}
|
||||
|
||||
#[ruby_name = "advance!"]
|
||||
def advance(&mut self) {
|
||||
let mut next = self.cells.clone();
|
||||
|
||||
for cell in self.cells() {
|
||||
let index = cell.index();
|
||||
let is_alive = cell.is_alive();
|
||||
let live_neighbors = cell.live_neighbors();
|
||||
|
||||
// Any live cell with fewer than two live neighbors dies, as if
|
||||
// by under population.
|
||||
if is_alive && live_neighbors < 2 {
|
||||
next[index] = false;
|
||||
// Any live cell with more than three live neighbors dies, as
|
||||
// if by overpopulation.
|
||||
} else if is_alive && live_neighbors > 3 {
|
||||
next[index] = false;
|
||||
// Any dead cell with exactly three live neighbors becomes a
|
||||
// live cell, as if by reproduction.
|
||||
} else if !is_alive && live_neighbors == 3 {
|
||||
next[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.cells = next;
|
||||
}
|
||||
|
||||
#[ruby_name = "randomize!"]
|
||||
def randomize(&mut self) {
|
||||
use rand::prelude::random;
|
||||
|
||||
for cell in self.cells.iter_mut() {
|
||||
*cell = random();
|
||||
}
|
||||
}
|
||||
|
||||
#[ruby_name = "play!"]
|
||||
def play(&mut self) {
|
||||
use std::io::Write;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use termion::async_stdin;
|
||||
use termion::clear;
|
||||
use termion::color::*;
|
||||
use termion::cursor::{Goto, Hide, Show};
|
||||
use termion::event::Key::Char;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::terminal_size;
|
||||
|
||||
let mut stdin = async_stdin().keys();
|
||||
let mut stdout = std::io::stdout().into_raw_mode().unwrap();
|
||||
|
||||
let (terminal_width, terminal_height) = terminal_size().unwrap();
|
||||
|
||||
// ~30 FPS
|
||||
let timeout = Duration::from_millis(30);
|
||||
|
||||
print!("{}", clear::All);
|
||||
print!("{}", Hide);
|
||||
|
||||
loop {
|
||||
for cell in self.cells() {
|
||||
let (x, y) = cell.coordinates();
|
||||
|
||||
if x >= terminal_width as usize || y >= terminal_height as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
print!("{}", Goto(x as u16 + 1, y as u16 + 1));
|
||||
|
||||
if cell.is_alive() {
|
||||
print!("{}*{}", Fg(LightYellow), Fg(Reset));
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
|
||||
self.advance();
|
||||
|
||||
print!("{}", Goto(1, terminal_height));
|
||||
print!("{} Press {}r{} to randomize, {}q{} to quit. {}", Bg(Blue), Fg(LightWhite), Fg(Reset), Fg(LightWhite), Fg(Reset), Bg(Reset));
|
||||
|
||||
stdout.flush().unwrap();
|
||||
|
||||
match stdin.next() {
|
||||
Some(Ok(Char('r'))) => {
|
||||
self.randomize();
|
||||
},
|
||||
Some(Ok(Char('q'))) => {
|
||||
print!("{}", clear::All);
|
||||
print!("{}", Goto(1, 1));
|
||||
print!("{}", Show);
|
||||
|
||||
stdout.flush().unwrap();
|
||||
|
||||
return;
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
|
||||
sleep(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
def to_s(&self) -> String {
|
||||
let mut s = String::with_capacity(self.cells.len() + self.height);
|
||||
|
||||
for cell in self.cells() {
|
||||
if cell.is_alive() {
|
||||
s.push('*');
|
||||
} else {
|
||||
s.push('.');
|
||||
}
|
||||
|
||||
if cell.is_last_in_row() {
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These are hidden from Ruby
|
||||
|
||||
impl GameOfLife {
|
||||
fn index_for(&self, x: usize, y: usize) -> usize {
|
||||
assert!(x < self.width && y < self.height, "({}, {}) is out-of-bounds", x, y);
|
||||
y * self.width + x
|
||||
}
|
||||
|
||||
fn cell_at(&self, x: usize, y: usize) -> Cell {
|
||||
let index = self.index_for(x, y);
|
||||
Cell { game: self, index }
|
||||
}
|
||||
|
||||
fn mut_cell_at(&mut self, x: usize, y: usize) -> MutCell {
|
||||
let index = self.index_for(x, y);
|
||||
MutCell { game: self, index }
|
||||
}
|
||||
|
||||
fn cells(&self) -> Cells {
|
||||
Cells { next: Some(self.cell_at(0,0)), size: self.cells.len() }
|
||||
}
|
||||
}
|
||||
|
||||
struct Cells<'a> {
|
||||
next: Option<Cell<'a>>,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Iterator for Cells<'a> {
|
||||
type Item = Cell<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = match self.next {
|
||||
Some(ref cell) => cell.next(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
std::mem::replace(&mut self.next, next)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let remaining = match self.next {
|
||||
Some(ref cell) => self.size - cell.index(),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
(remaining, Some(remaining))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::ExactSizeIterator for Cells<'a> {}
|
||||
impl<'a> std::iter::FusedIterator for Cells<'a> {}
|
||||
|
||||
struct Cell<'a> {
|
||||
game: &'a GameOfLife,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
struct MutCell<'a> {
|
||||
game: &'a mut GameOfLife,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
enum Neighbor {
|
||||
TopLeft,
|
||||
Top,
|
||||
TopRight,
|
||||
Left,
|
||||
Right,
|
||||
BottomLeft,
|
||||
Bottom,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
trait Navigate {
|
||||
fn game(&self) -> &GameOfLife;
|
||||
fn index(&self) -> usize;
|
||||
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
(self.x(), self.y())
|
||||
}
|
||||
|
||||
fn x(&self) -> usize {
|
||||
self.index() % self.game().width
|
||||
}
|
||||
|
||||
fn y(&self) -> usize {
|
||||
self.index() / self.game().width
|
||||
}
|
||||
|
||||
fn is_alive(&self) -> bool {
|
||||
self.game().cells[self.index()]
|
||||
}
|
||||
|
||||
fn is_last_in_row(&self) -> bool {
|
||||
self.neighbor(Neighbor::Right).is_none()
|
||||
}
|
||||
|
||||
fn is_last(&self) -> bool {
|
||||
self.index() == self.game().cells.len() - 1
|
||||
}
|
||||
|
||||
fn live_neighbors(&self) -> usize {
|
||||
use Neighbor::*;
|
||||
|
||||
let neighbors = vec![
|
||||
self.neighbor(TopLeft),
|
||||
self.neighbor(Top),
|
||||
self.neighbor(TopRight),
|
||||
self.neighbor(Left),
|
||||
self.neighbor(Right),
|
||||
self.neighbor(BottomLeft),
|
||||
self.neighbor(Bottom),
|
||||
self.neighbor(BottomRight),
|
||||
];
|
||||
|
||||
neighbors.into_iter()
|
||||
.filter(Option::is_some)
|
||||
.map(Option::unwrap)
|
||||
.map(|cell| if cell.is_alive() { 1 } else { 0 })
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn neighbor(&self, position: Neighbor) -> Option<Cell> {
|
||||
use Neighbor::*;
|
||||
|
||||
let (x, y) = self.coordinates();
|
||||
let game = self.game();
|
||||
|
||||
let mut nx = x;
|
||||
let mut ny = y;
|
||||
|
||||
match position {
|
||||
TopLeft => { nx -= 1; ny -= 1; },
|
||||
Top => { ny -= 1; },
|
||||
TopRight => { nx += 1; ny -= 1; },
|
||||
Left => { nx -= 1; },
|
||||
Right => { nx += 1; },
|
||||
BottomLeft => { nx -= 1; ny += 1; },
|
||||
Bottom => { ny += 1; },
|
||||
BottomRight => { nx += 1; ny += 1; },
|
||||
}
|
||||
|
||||
if nx >= game.width || ny >= game.height {
|
||||
None
|
||||
} else {
|
||||
Some(Cell { game, index: game.index_for(nx, ny) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Navigate for Cell<'a> {
|
||||
fn game(&self) -> &GameOfLife {
|
||||
self.game
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Navigate for MutCell<'a> {
|
||||
fn game(&self) -> &GameOfLife {
|
||||
self.game
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Cell<'a> {
|
||||
// TODO: this should be in Navigate
|
||||
fn next(&self) -> Option<Cell<'a>> {
|
||||
if self.is_last() {
|
||||
None
|
||||
} else {
|
||||
Some(Cell { game: self.game, index: self.index + 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MutCell<'a> {
|
||||
fn set(&mut self, value: bool) {
|
||||
self.game.cells[self.index] = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.bundle
|
||||
*.gem
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "geometry"
|
||||
version = "0.1.0"
|
||||
authors = ["Godfrey Chan <godfrey@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
|
@ -0,0 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
|
@ -0,0 +1,20 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require 'rspec/core/rake_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :spec => :build
|
||||
task :default => :spec
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'geometry/native'
|
|
@ -0,0 +1,35 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe Point do
|
||||
let(:origin) { Point.new(0,0) }
|
||||
|
||||
let(:north) { Point.new(0,1) }
|
||||
let(:east) { Point.new(1,0) }
|
||||
let(:south) { Point.new(0,-1) }
|
||||
let(:west) { Point.new(-1,0) }
|
||||
|
||||
it "has the right coordinates" do
|
||||
expect(origin.x).to eq(0)
|
||||
expect(origin.y).to eq(0)
|
||||
|
||||
expect(north.x).to eq(0)
|
||||
expect(north.y).to eq(1)
|
||||
|
||||
expect(east.x).to eq(1)
|
||||
expect(east.y).to eq(0)
|
||||
|
||||
expect(south.x).to eq(0)
|
||||
expect(south.y).to eq(-1)
|
||||
|
||||
expect(west.x).to eq(-1)
|
||||
expect(west.y).to eq(0)
|
||||
end
|
||||
|
||||
it "can be turned into an array" do
|
||||
expect(origin.to_a).to eq([0,0])
|
||||
expect(north.to_a).to eq([0,1])
|
||||
expect(east.to_a).to eq([1,0])
|
||||
expect(south.to_a).to eq([0,-1])
|
||||
expect(west.to_a).to eq([-1,0])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'geometry'
|
|
@ -0,0 +1,29 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
ruby! {
|
||||
class Point {
|
||||
struct {
|
||||
x: f64,
|
||||
y: f64
|
||||
}
|
||||
|
||||
def initialize(helix, x: f64, y: f64) {
|
||||
Point { helix, x, y }
|
||||
}
|
||||
|
||||
def x(&self) -> f64 {
|
||||
self.x
|
||||
}
|
||||
|
||||
def y(&self) -> f64 {
|
||||
self.y
|
||||
}
|
||||
|
||||
def to_a(&self) -> (f64, f64) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "json_builder"
|
||||
version = "0.1.0"
|
||||
authors = ["Godfrey Chan <godfrey@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
||||
|
||||
[dependencies]
|
||||
serde_json = "*"
|
|
@ -0,0 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
|
@ -0,0 +1,20 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require 'rspec/core/rake_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
task :spec => :build
|
||||
task :default => :spec
|
|
@ -0,0 +1,2 @@
|
|||
require 'helix_runtime'
|
||||
require 'json_builder/native'
|
|
@ -0,0 +1,88 @@
|
|||
require "spec_helper"
|
||||
require "json"
|
||||
|
||||
describe "JsonBuilder" do
|
||||
let(:builder) { JsonBuilder.new }
|
||||
let(:json) { JSON.parse(builder.to_json) }
|
||||
|
||||
it "can add null" do
|
||||
builder["foo"] = nil
|
||||
expect(json).to eq({ "foo" => nil })
|
||||
end
|
||||
|
||||
it "can add booleans" do
|
||||
builder["foo"] = true
|
||||
builder["bar"] = false
|
||||
expect(json).to eq({ "foo" => true, "bar" => false })
|
||||
end
|
||||
|
||||
it "can add integers" do
|
||||
builder["foo"] = 12345
|
||||
builder["bar"] = -1_000_000
|
||||
expect(json).to eq({ "foo" => 12345, "bar" => -1_000_000 })
|
||||
end
|
||||
|
||||
it "can add floats" do
|
||||
builder["foo"] = 1.2345
|
||||
builder["bar"] = -1.0
|
||||
|
||||
expect(->{ builder["baz"] = Float::NAN }).to raise_error(TypeError)
|
||||
expect(->{ builder["baz"] = Float::INFINITY }).to raise_error(TypeError)
|
||||
|
||||
expect(json).to eq({ "foo" => 1.2345, "bar" => -1.0 })
|
||||
end
|
||||
|
||||
it "can add string" do
|
||||
builder["foo"] = "FOO"
|
||||
builder["bar"] = "BAR"
|
||||
expect(json).to eq({ "foo" => "FOO", "bar" => "BAR" })
|
||||
end
|
||||
|
||||
it "can add array" do
|
||||
foo = builder["foo"] = [nil, true, 12345, 1.2345, "FOO"]
|
||||
bar = builder["bar"] = [nil, false, -1_000_000, -1.0, "BAR"]
|
||||
expect(json).to eq({ "foo" => foo, "bar" => bar })
|
||||
end
|
||||
|
||||
it "can add hash" do
|
||||
foo = builder["foo"] = { "nil" => nil, "true" => true, "12345" => 12345, "1.2345" => 1.2345, "FOO" => "FOO" }
|
||||
bar = builder["bar"] = { "nil" => nil, "false" => false, "-1_000_000" => -1_000_000, "-1.0" => -1.0, "BAR" => "BAR" }
|
||||
expect(json).to eq({ "foo" => foo, "bar" => bar })
|
||||
end
|
||||
|
||||
it "can add nested builder" do
|
||||
builder["foo"] = JsonBuilder.new.tap { |inner| inner["foo"] = "FOO" }
|
||||
builder["bar"] = JsonBuilder.new.tap { |inner| inner["bar"] = "BAR" }
|
||||
expect(json).to eq({ "foo" => { "foo" => "FOO" }, "bar" => { "bar" => "BAR" } })
|
||||
end
|
||||
|
||||
it "can convert into a hash" do
|
||||
builder["foo"] = "FOO"
|
||||
builder["bar"] = nil
|
||||
expect(builder.to_h).to eq({ "foo" => "FOO", "bar" => nil })
|
||||
end
|
||||
|
||||
it "cannot be used once to_json is called" do
|
||||
expect(builder.to_json).to eq("{}")
|
||||
|
||||
expect(->{ builder["foo"] = nil }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_json }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_h }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
end
|
||||
|
||||
it "cannot be used once to_h is called" do
|
||||
expect(builder.to_h).to eq({})
|
||||
|
||||
expect(->{ builder["foo"] = nil }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_json }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_h }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
end
|
||||
|
||||
it "cannot be used once added to another builder" do
|
||||
JsonBuilder.new["foo"] = builder
|
||||
|
||||
expect(->{ builder["foo"] = nil }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_json }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
expect(->{ builder.to_h }).to raise_error(RuntimeError, "Uninitialized JsonBuilder")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
||||
require 'json_builder'
|
|
@ -0,0 +1,108 @@
|
|||
extern crate serde_json;
|
||||
|
||||
use super::{JsonValue, JsonBuilder};
|
||||
use helix::{FromRuby, CheckResult, ToRuby, ToRubyResult};
|
||||
use helix::sys::VALUE;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub enum CheckedJsonValue {
|
||||
Null,
|
||||
Boolean(<bool as FromRuby>::Checked),
|
||||
Integer(<i64 as FromRuby>::Checked),
|
||||
Float(f64),
|
||||
String(<String as FromRuby>::Checked),
|
||||
Array(<Vec<JsonValue> as FromRuby>::Checked),
|
||||
Object(<HashMap<String, JsonValue> as FromRuby>::Checked),
|
||||
Nested(<JsonBuilder as FromRuby>::Checked)
|
||||
}
|
||||
|
||||
impl FromRuby for JsonValue {
|
||||
type Checked = CheckedJsonValue;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedJsonValue> {
|
||||
if let Ok(_) = <()>::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Null)
|
||||
} else if let Ok(checked) = bool::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Boolean(checked))
|
||||
} else if let Ok(checked) = i64::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Integer(checked))
|
||||
} else if let Ok(checked) = f64::from_ruby(value) {
|
||||
let float = f64::from_checked(checked);
|
||||
|
||||
if float.is_normal() {
|
||||
Ok(CheckedJsonValue::Float(float))
|
||||
} else {
|
||||
type_error!(format!("Cannot convert {} into a JSON number", float))
|
||||
}
|
||||
} else if let Ok(checked) = String::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::String(checked))
|
||||
} else if let Ok(checked) = Vec::<JsonValue>::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Array(checked))
|
||||
} else if let Ok(checked) = HashMap::<String, JsonValue>::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Object(checked))
|
||||
} else if let Ok(checked) = JsonBuilder::from_ruby(value) {
|
||||
Ok(CheckedJsonValue::Nested(checked))
|
||||
} else {
|
||||
type_error!(value, "a JSON value")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedJsonValue) -> JsonValue {
|
||||
match checked {
|
||||
CheckedJsonValue::Null => JsonValue::Null,
|
||||
CheckedJsonValue::Boolean(c) => JsonValue::Boolean(FromRuby::from_checked(c)),
|
||||
CheckedJsonValue::Integer(c) => JsonValue::Integer(FromRuby::from_checked(c)),
|
||||
CheckedJsonValue::Float(c) => JsonValue::Float(c),
|
||||
CheckedJsonValue::String(c) => JsonValue::String(FromRuby::from_checked(c)),
|
||||
CheckedJsonValue::Array(c) => JsonValue::Array(FromRuby::from_checked(c)),
|
||||
CheckedJsonValue::Object(c) => JsonValue::Object(FromRuby::from_checked(c)),
|
||||
CheckedJsonValue::Nested(c) => JsonValue::Object(JsonBuilder::from_checked(c).to_hash_map())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for JsonValue {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
match self {
|
||||
JsonValue::Null => ().to_ruby(),
|
||||
JsonValue::Boolean(v) => v.to_ruby(),
|
||||
JsonValue::Integer(v) => v.to_ruby(),
|
||||
JsonValue::Float(v) => v.to_ruby(),
|
||||
JsonValue::String(v) => v.to_ruby(),
|
||||
JsonValue::Array(v) => v.to_ruby(),
|
||||
JsonValue::Object(v) => v.to_ruby(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use serde_json::{Value, Number};
|
||||
|
||||
pub trait ToSerde {
|
||||
fn to_serde(self) -> Value;
|
||||
}
|
||||
|
||||
impl ToSerde for JsonValue {
|
||||
fn to_serde(self) -> Value {
|
||||
match self {
|
||||
JsonValue::Null => Value::Null,
|
||||
JsonValue::Boolean(v) => Value::Bool(v),
|
||||
JsonValue::Integer(v) => Value::Number(Number::from(v)),
|
||||
JsonValue::Float(v) => Value::Number(Number::from_f64(v).unwrap()),
|
||||
JsonValue::String(v) => Value::String(v),
|
||||
JsonValue::Array(v) => v.to_serde(),
|
||||
JsonValue::Object(v) => v.to_serde(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSerde for Vec<JsonValue> {
|
||||
fn to_serde(self) -> Value {
|
||||
Value::Array(self.into_iter().map(|v| v.to_serde()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSerde for HashMap<String, JsonValue> {
|
||||
fn to_serde(self) -> Value {
|
||||
Value::Object(self.into_iter().map(|(k,v)| (k, v.to_serde())).collect())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
extern crate serde_json;
|
||||
|
||||
mod coercion;
|
||||
|
||||
use coercion::ToSerde;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JsonValue {
|
||||
Null,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Array(Vec<JsonValue>),
|
||||
Object(HashMap<String, JsonValue>)
|
||||
}
|
||||
|
||||
ruby! {
|
||||
pub class JsonBuilder {
|
||||
struct {
|
||||
entries: HashMap<String, JsonValue>
|
||||
}
|
||||
|
||||
def initialize(helix) {
|
||||
JsonBuilder { helix, entries: HashMap::new() }
|
||||
}
|
||||
|
||||
#[ruby_name="[]="]
|
||||
def put(&mut self, key: String, value: JsonValue) {
|
||||
self.entries.insert(key, value);
|
||||
}
|
||||
|
||||
def to_json(self) -> Result<String, String> {
|
||||
serde_json::to_string(&self.entries.to_serde())
|
||||
.map_err(|e| e.description().to_string())
|
||||
}
|
||||
|
||||
#[ruby_name="to_h"]
|
||||
def to_hash_map(self) -> HashMap<String, JsonValue> {
|
||||
self.entries
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
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)
|
||||
thor (0.19.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.4)
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("membership") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -1,39 +1,36 @@
|
|||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
use helix::{Error, FromRuby};
|
||||
|
||||
ruby! {
|
||||
reopen class Array {
|
||||
def is_superset_of(&self, needle: &[usize]) -> bool {
|
||||
if needle.is_empty() { return true }
|
||||
def is_superset_of(&self, needle: Vec<u64>) -> Result<bool, Error> {
|
||||
if needle.is_empty() { return Ok(true) }
|
||||
|
||||
let haystack = self.as_ref();
|
||||
let haystack = self.as_vec()?;
|
||||
|
||||
if haystack.is_empty() { return false }
|
||||
if haystack.is_empty() { return Ok(false) }
|
||||
|
||||
let mut needle = needle.iter();
|
||||
let mut needle = needle.into_iter();
|
||||
let mut needle_item = needle.next().unwrap();
|
||||
|
||||
for item in haystack {
|
||||
if item == needle_item {
|
||||
match needle.next() {
|
||||
None => return true,
|
||||
None => return Ok(true),
|
||||
Some(next_item) => needle_item = next_item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete me:
|
||||
|
||||
use helix::{UncheckedValue, ToRust};
|
||||
|
||||
impl AsRef<[usize]> for Array {
|
||||
fn as_ref(&self) -> &[usize] {
|
||||
let checked = self.helix.to_checked().unwrap();
|
||||
checked.to_rust()
|
||||
impl Array {
|
||||
fn as_vec(&self) -> Result<Vec<u64>, Error> {
|
||||
Vec::<u64>::from_ruby(self.helix).map(Vec::<u64>::from_checked)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 10.0'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.4'
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.3)
|
||||
rake (10.5.0)
|
||||
rspec (3.5.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
thor (0.19.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
helix_runtime!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.4)
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("text_transform") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -5,11 +5,59 @@ describe "TextTransform" do
|
|||
expect(TextTransform.widen("Hello Aaron (@tenderlove)!")).to eq("Hello Aaron (@tenderlove)!")
|
||||
end
|
||||
|
||||
it "can widen array" do
|
||||
expect(TextTransform.widen_array(%w"Hello Aaron (@tenderlove)!")).to eq(%w"Hello Aaron (@tenderlove)!")
|
||||
end
|
||||
|
||||
it "can widen hash" do
|
||||
expect(TextTransform.widen_hash({
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})).to eq({
|
||||
"message": "Hello",
|
||||
"name": "Aaron",
|
||||
"handle": "@tenderlove"
|
||||
})
|
||||
end
|
||||
|
||||
it "can narrowen text" do
|
||||
expect(TextTransform.narrowen("Hello Aaron (@tenderlove)!")).to eq("Hello Aaron (@tenderlove)!")
|
||||
end
|
||||
|
||||
it "can narrowen array" do
|
||||
expect(TextTransform.narrowen_array(%w"Hello Aaron (@tenderlove)!")).to eq(%w"Hello Aaron (@tenderlove)!")
|
||||
end
|
||||
|
||||
it "can narrowen hash" do
|
||||
expect(TextTransform.narrowen_hash({
|
||||
"message": "Hello",
|
||||
"name": "Aaron",
|
||||
"handle": "@tenderlove"
|
||||
})).to eq({
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})
|
||||
end
|
||||
|
||||
it "can flip text" do
|
||||
expect(TextTransform.flip("Hello Aaron (@tenderlove)!")).to eq("¡(ǝʌolɹǝpuǝʇ@) uoɹɐ∀ ollǝH")
|
||||
end
|
||||
|
||||
it "can flip array" do
|
||||
expect(TextTransform.flip_array(%w"Hello Aaron (@tenderlove)!")).to eq(%w"¡(ǝʌolɹǝpuǝʇ@) uoɹɐ∀ ollǝH")
|
||||
end
|
||||
|
||||
it "can flip hash" do
|
||||
expect(TextTransform.flip_hash({
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})).to eq({
|
||||
"ollǝH": "ǝƃɐssǝɯ",
|
||||
"uoɹɐ∀": "ǝɯɐu",
|
||||
"ǝʌolɹǝpuǝʇ@": "ǝlpuɐɥ"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
#![recursion_limit="1024"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use helix::Symbol;
|
||||
|
||||
ruby! {
|
||||
class TextTransform {
|
||||
def widen(text: String) -> String {
|
||||
|
@ -22,6 +27,14 @@ ruby! {
|
|||
}).collect()
|
||||
}
|
||||
|
||||
def widen_array(text: Vec<String>) -> Vec<String> {
|
||||
text.into_iter().map(TextTransform::widen).collect()
|
||||
}
|
||||
|
||||
def widen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::widen(k.to_string())), TextTransform::widen(v))).collect()
|
||||
}
|
||||
|
||||
def narrowen(text: String) -> String {
|
||||
text.chars().map(|char| {
|
||||
match char {
|
||||
|
@ -41,6 +54,14 @@ ruby! {
|
|||
}).collect()
|
||||
}
|
||||
|
||||
def narrowen_array(text: Vec<String>) -> Vec<String> {
|
||||
text.into_iter().map(TextTransform::narrowen).collect()
|
||||
}
|
||||
|
||||
def narrowen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::narrowen(k.to_string())), TextTransform::narrowen(v))).collect()
|
||||
}
|
||||
|
||||
def flip(text: String) -> String {
|
||||
text.chars().rev().map(|char| {
|
||||
match char {
|
||||
|
@ -58,5 +79,13 @@ ruby! {
|
|||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
def flip_array(text: Vec<String>) -> Vec<String> {
|
||||
text.into_iter().rev().map(TextTransform::flip).collect()
|
||||
}
|
||||
|
||||
def flip_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::flip(v)), TextTransform::flip(k.to_string()))).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
PATH
|
||||
remote: ../../ruby
|
||||
specs:
|
||||
helix_runtime (0.6.0)
|
||||
rake (>= 10.0)
|
||||
thor (~> 0.19.4)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
turbo_blank (0.1.0)
|
||||
helix_runtime
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
minitest (5.8.3)
|
||||
rake (10.5.0)
|
||||
thor (0.19.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
bundler (~> 1.11)
|
||||
helix_runtime!
|
||||
minitest (~> 5.0)
|
||||
rake (~> 10.0)
|
||||
turbo_blank!
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
|
@ -6,7 +6,7 @@ require_relative '../shared.rb'
|
|||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new("turbo_blank") do |t|
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
require "helix_runtime"
|
||||
|
||||
RubyString = String
|
||||
|
||||
require "turbo_blank/native"
|
||||
|
||||
class String
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
extern crate helix;
|
||||
|
||||
ruby! {
|
||||
#[ruby_name = "String"]
|
||||
reopen class RubyString {
|
||||
#[ruby_name = "blank?"]
|
||||
def is_blank(&self) -> bool {
|
||||
|
@ -13,11 +14,10 @@ ruby! {
|
|||
|
||||
// Delete me:
|
||||
|
||||
use helix::{UncheckedValue, ToRust};
|
||||
use helix::{FromRuby};
|
||||
|
||||
impl ToString for RubyString {
|
||||
fn to_string(&self) -> String {
|
||||
let checked = self.helix.to_checked().unwrap();
|
||||
checked.to_rust()
|
||||
String::from_ruby_unwrap(self.helix)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
|
|||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency "helix_runtime"
|
||||
spec.add_development_dependency "bundler", "~> 1.11"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rake", "~> 12.0"
|
||||
spec.add_development_dependency "minitest", "~> 5.0"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
target
|
||||
tmp
|
||||
*.bundle
|
||||
*.so
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "unit"
|
||||
version = "0.1.0"
|
||||
authors = ["Godfrey Chan <godfrey@tilde.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.helix]
|
||||
path = "../.."
|
|
@ -0,0 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'helix_runtime', path: '../../ruby'
|
||||
gem 'rake', '~> 12.0'
|
|
@ -0,0 +1,14 @@
|
|||
require 'bundler/setup'
|
||||
require 'helix_runtime/build_task'
|
||||
require_relative '../shared.rb'
|
||||
|
||||
# For Windows
|
||||
$stdout.sync = true
|
||||
|
||||
HelixRuntime::BuildTask.new do |t|
|
||||
t.build_root = File.expand_path("../..", __dir__)
|
||||
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
|
||||
t.pre_build = HelixRuntime::Tests.pre_build
|
||||
end
|
||||
|
||||
task :default => :build
|
|
@ -0,0 +1,7 @@
|
|||
require "helix_runtime"
|
||||
|
||||
begin
|
||||
require "unit/native"
|
||||
rescue LoadError
|
||||
warn "Unable to load unit/native. Please run `rake build`"
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
#[macro_use]
|
||||
extern crate helix;
|
||||
|
||||
ruby! {
|
||||
#[doc(hidden)]
|
||||
#[ruby_name="AttributesTest"]
|
||||
#[no_mangle]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(not(foo="bar"))]
|
||||
class Attributes {
|
||||
#[doc(hidden)]
|
||||
#[ruby_name="foo"]
|
||||
#[inline]
|
||||
#[cfg(not(foo="bar"))]
|
||||
def bar() {
|
||||
println!("Hello from bar!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,16 +46,16 @@ end
|
|||
|
||||
directory windows_build_dir
|
||||
|
||||
def dlltool
|
||||
def dlltool(arch = nil)
|
||||
# allow override with env vars
|
||||
if dlltool_env = ENV['DLLTOOL']
|
||||
dlltool_env
|
||||
# if on windows
|
||||
elsif RUBY_PLATFORM =~ /mingw/
|
||||
"dlltool.exe"
|
||||
elsif system("x86_64-w64-mingw32-dlltool > /dev/null")
|
||||
elsif system("x86_64-w64-mingw32-dlltool > /dev/null") && arch == "i386:x86-64"
|
||||
"x86_64-w64-mingw32-dlltool"
|
||||
elsif system("i686-w64-mingw32-dlltool > /dev/null")
|
||||
elsif system("i686-w64-mingw32-dlltool > /dev/null") && arch == "i386"
|
||||
"i686-w64-mingw32-dlltool"
|
||||
else
|
||||
"dlltool"
|
||||
|
@ -66,7 +66,7 @@ def build_native_lib(arch, dll_name, native_def_file, target)
|
|||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
# Generate and then move. Symbols include generated file name, so avoid long path.
|
||||
cmd = [dlltool]
|
||||
cmd = [dlltool(arch)]
|
||||
cmd << "-D #{dll_name}"
|
||||
cmd << "-m #{arch}" if arch
|
||||
cmd << "-d #{native_def_file}"
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
#include <ruby.h>
|
||||
#include <ruby/intern.h>
|
||||
#include <ruby/encoding.h>
|
||||
#include <stdbool.h>
|
||||
#include <helix_runtime.h>
|
||||
|
||||
// Update with version.rb
|
||||
const char* HELIX_RUNTIME_VERSION = "0.6.1";
|
||||
const char* HELIX_RUNTIME_VERSION = "0.7.5";
|
||||
|
||||
const char* HELIX_PRIsVALUE = PRIsVALUE;
|
||||
const char* HELIX_SPRINTF_TO_S = "%" PRIsVALUE;
|
||||
|
@ -36,10 +37,22 @@ const void* HELIX_RARRAY_CONST_PTR(VALUE array) {
|
|||
return RARRAY_CONST_PTR(array);
|
||||
}
|
||||
|
||||
long HELIX_RHASH_SIZE(VALUE hash) {
|
||||
return RHASH_SIZE(hash);
|
||||
}
|
||||
|
||||
bool HELIX_RB_TYPE_P(VALUE v, int type) {
|
||||
return RB_TYPE_P(v, type);
|
||||
}
|
||||
|
||||
bool HELIX_RB_NIL_P(VALUE v) {
|
||||
return NIL_P(v);
|
||||
}
|
||||
|
||||
bool HELIX_RTEST(VALUE v) {
|
||||
return RTEST(v);
|
||||
}
|
||||
|
||||
VALUE HELIX_INT2FIX(int c_int) {
|
||||
return INT2FIX(c_int);
|
||||
}
|
||||
|
@ -52,6 +65,18 @@ VALUE HELIX_rb_utf8_str_new(const char* str, long len) {
|
|||
return rb_utf8_str_new(str, len);
|
||||
}
|
||||
|
||||
bool HELIX_rb_str_valid_encoding_p(VALUE str) {
|
||||
return rb_enc_str_coderange(str) != ENC_CODERANGE_BROKEN;
|
||||
}
|
||||
|
||||
bool HELIX_rb_str_ascii_only_p(VALUE str) {
|
||||
return rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT;
|
||||
}
|
||||
|
||||
VALUE HELIX_CLASS_OF(VALUE v) {
|
||||
return CLASS_OF(v);
|
||||
}
|
||||
|
||||
VALUE HELIX_Data_Wrap_Struct(VALUE klass, HELIX_RUBY_DATA_FUNC mark, HELIX_RUBY_DATA_FUNC free, void* data) {
|
||||
return Data_Wrap_Struct(klass, mark, free, data);
|
||||
}
|
||||
|
@ -96,6 +121,10 @@ VALUE HELIX_F642NUM(RUST_F64 num) {
|
|||
return DBL2NUM(num);
|
||||
}
|
||||
|
||||
bool HELIX_OBJ_FROZEN(VALUE obj) {
|
||||
return OBJ_FROZEN(obj);
|
||||
}
|
||||
|
||||
void* HELIX_Data_Get_Struct_Value(VALUE obj) {
|
||||
void* data;
|
||||
Data_Get_Struct(obj, void*, data);
|
||||
|
|
|
@ -44,6 +44,8 @@ HELIX_EXTERN VALUE HELIX_I322NUM(RUST_I32);
|
|||
HELIX_EXTERN RUST_F64 HELIX_NUM2F64(VALUE);
|
||||
HELIX_EXTERN VALUE HELIX_F642NUM(RUST_F64);
|
||||
|
||||
HELIX_EXTERN bool HELIX_OBJ_FROZEN(VALUE obj);
|
||||
|
||||
HELIX_EXTERN VALUE HELIX_Qtrue;
|
||||
HELIX_EXTERN VALUE HELIX_Qfalse;
|
||||
HELIX_EXTERN VALUE HELIX_Qnil;
|
||||
|
@ -55,14 +57,23 @@ HELIX_EXTERN long HELIX_RARRAY_LEN(VALUE array);
|
|||
HELIX_EXTERN void* HELIX_RARRAY_PTR(VALUE array);
|
||||
HELIX_EXTERN const void* HELIX_RARRAY_CONST_PTR(VALUE array);
|
||||
|
||||
HELIX_EXTERN long HELIX_RHASH_SIZE(VALUE hash);
|
||||
|
||||
HELIX_EXTERN bool HELIX_RB_TYPE_P(VALUE v, int type);
|
||||
HELIX_EXTERN bool HELIX_RB_NIL_P(VALUE v);
|
||||
HELIX_EXTERN int HELIX_TYPE(VALUE v);
|
||||
HELIX_EXTERN bool HELIX_RTEST(VALUE v);
|
||||
|
||||
HELIX_EXTERN VALUE HELIX_INT2FIX(int c_int);
|
||||
HELIX_EXTERN VALUE HELIX_FIX2INT(VALUE fix);
|
||||
|
||||
HELIX_EXTERN VALUE HELIX_rb_utf8_str_new(const char* str, long len);
|
||||
|
||||
HELIX_EXTERN bool HELIX_rb_str_valid_encoding_p(VALUE str);
|
||||
HELIX_EXTERN bool HELIX_rb_str_ascii_only_p(VALUE str);
|
||||
|
||||
HELIX_EXTERN VALUE HELIX_CLASS_OF(VALUE v);
|
||||
|
||||
// typedef VALUE (*HELIX_rb_alloc_func_t)(VALUE);
|
||||
// void HELIX_rb_define_alloc_func(VALUE klass, HELIX_rb_alloc_func_t func);
|
||||
|
||||
|
|
|
@ -24,10 +24,11 @@ Gem::Specification.new do |spec|
|
|||
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_dependency "rake", ">= 10.0"
|
||||
spec.add_dependency "thor", "~> 0.19.4"
|
||||
spec.add_dependency "rake", ">= 10.0"
|
||||
spec.add_dependency "thor", [">= 0.19.4", "< 2.0"]
|
||||
spec.add_dependency "tomlrb", "~> 1.2.4"
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.10"
|
||||
spec.add_development_dependency "bundler", "~> 2.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.4"
|
||||
spec.add_development_dependency "rake-compiler", "~> 0.9.7"
|
||||
spec.add_development_dependency "rake-compiler", "~> 1.0.7"
|
||||
end
|
||||
|
|
|
@ -14,27 +14,26 @@ module HelixRuntime
|
|||
@project ||= Project.new(Dir.pwd)
|
||||
end
|
||||
|
||||
delegate_attr :name, to: :project
|
||||
delegate_attr :helix_lib_dir, to: :project
|
||||
delegate_attr :debug_rust, to: :project
|
||||
delegate_attr :build_root, to: :project
|
||||
|
||||
attr_accessor :pre_build
|
||||
|
||||
def initialize(name = nil, gem_spec = nil)
|
||||
init(name, gem_spec)
|
||||
def initialize(deprecated_name = nil)
|
||||
yield self if block_given?
|
||||
|
||||
if deprecated_name
|
||||
warn "DEPRECATION WARNING: Passing a project name to the Helix build " \
|
||||
"task (`HelixRuntime::BuildTask.new(#{deprecated_name.inspect})`) " \
|
||||
"is unnecessary, as we now automatically detect the project name " \
|
||||
"from your `Cargo.toml`.\n\n"
|
||||
end
|
||||
|
||||
define
|
||||
end
|
||||
|
||||
def init(name = nil, gem_spec = nil)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def define
|
||||
fail "Extension name must be provided." if @name.nil?
|
||||
@name = @name.to_s
|
||||
|
||||
task "helix:pre_build" do
|
||||
pre_build.call if pre_build
|
||||
end
|
||||
|
@ -53,16 +52,16 @@ module HelixRuntime
|
|||
end
|
||||
|
||||
task "cargo:build" => ["helix:pre_build", "helix:check_path"] do
|
||||
project.cargo_build
|
||||
project.cargo_build || abort
|
||||
end
|
||||
|
||||
task "cargo:clean" do
|
||||
project.cargo_clean
|
||||
end
|
||||
|
||||
desc "Build #{name}"
|
||||
desc "Build #{project.name}"
|
||||
task :build => ["helix:pre_build", "helix:check_path"] do
|
||||
project.build
|
||||
project.build || abort
|
||||
end
|
||||
|
||||
desc "Remove build artifacts"
|
||||
|
@ -70,9 +69,9 @@ module HelixRuntime
|
|||
project.clobber
|
||||
end
|
||||
|
||||
desc "Launch an IRB console for #{name}"
|
||||
desc "Launch an IRB console for #{project.name}"
|
||||
task :irb => :build do
|
||||
exec "bundle exec irb -r#{name} -Ilib"
|
||||
exec "bundle exec irb -r#{project.name} -Ilib"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,9 +8,13 @@ module HelixRuntime
|
|||
register CLI::Bootstrap, "bootstrap", "bootstrap PATH [NAME]", "Bootstrap Helix"
|
||||
|
||||
desc "crate NAME", "Generate a Helix crate"
|
||||
option :skip_bundle, type: :boolean, default: false
|
||||
def crate(name)
|
||||
bootstrap("crates/#{name}", name)
|
||||
invoke CLI::Bootstrap, ["crates/#{name}", name], skip_bundle: true
|
||||
|
||||
append_to_file "Gemfile", "gem '#{name}', path: 'crates/#{name}'\n"
|
||||
|
||||
run "bundle" unless options.skip_bundle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
require 'helix_runtime/build_task'
|
||||
|
||||
HelixRuntime::BuildTask.new("<%= app_name %>")
|
||||
HelixRuntime::BuildTask.new
|
||||
|
||||
task :default => :build
|
||||
|
|
|
@ -37,4 +37,4 @@ module HelixRuntime
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
require 'tomlrb'
|
||||
require 'json'
|
||||
|
||||
module HelixRuntime
|
||||
class Project
|
||||
|
||||
|
@ -8,14 +11,12 @@ module HelixRuntime
|
|||
end
|
||||
|
||||
attr_accessor :root
|
||||
attr_accessor :name
|
||||
attr_accessor :helix_lib_dir
|
||||
attr_accessor :debug_rust
|
||||
attr_accessor :build_root
|
||||
|
||||
def initialize(root)
|
||||
@root = find_root(root)
|
||||
@name = File.basename(@root)
|
||||
@debug_rust = ENV['DEBUG_RUST']
|
||||
@build_root = @root
|
||||
end
|
||||
|
@ -24,8 +25,18 @@ module HelixRuntime
|
|||
!!debug_rust
|
||||
end
|
||||
|
||||
def name
|
||||
@name ||= Tomlrb.load_file(cargo_toml_path)["package"]["name"]
|
||||
end
|
||||
|
||||
def cargo_toml_path
|
||||
"#{root}/Cargo.toml"
|
||||
end
|
||||
|
||||
def build_path
|
||||
File.expand_path(debug_rust? ? 'target/debug' : 'target/release', build_root)
|
||||
metadata = %x[cargo metadata --format-version 1]
|
||||
target_directory = JSON.parse(metadata)["target_directory"]
|
||||
File.expand_path(debug_rust? ? 'debug' : 'release', target_directory)
|
||||
end
|
||||
|
||||
def lib_path
|
||||
|
@ -41,7 +52,7 @@ module HelixRuntime
|
|||
end
|
||||
|
||||
def native_lib
|
||||
"#{libfile_prefix}#{name.sub('-', '_')}.#{Platform.libext}"
|
||||
"#{libfile_prefix}#{name.gsub('-', '_')}.#{Platform.libext}"
|
||||
end
|
||||
|
||||
def outdated_build?
|
||||
|
@ -109,11 +120,11 @@ module HelixRuntime
|
|||
raise "native source doesn't exist, run `cargo_build` first; source=#{source}" unless File.exist?(source)
|
||||
FileUtils.mkdir_p(File.dirname(native_path))
|
||||
FileUtils.cp source, native_path
|
||||
true
|
||||
end
|
||||
|
||||
def build
|
||||
cargo_build
|
||||
copy_native
|
||||
cargo_build && copy_native
|
||||
end
|
||||
|
||||
def clobber
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module HelixRuntime
|
||||
# Also update helix_runtime.c
|
||||
VERSION = "0.6.1"
|
||||
VERSION = "0.7.5"
|
||||
GEM_VERSION = VERSION.gsub("-", ".")
|
||||
end
|
||||
|
|
|
@ -76,6 +76,27 @@ describe HelixRuntime do
|
|||
expect(Dummy.RARRAY_CONST_PTR(arr)).to eq(Dummy::RARRAY_CONST_PTR(arr))
|
||||
end
|
||||
|
||||
it 'exports the RHASH_SIZE macro' do
|
||||
expect(Dummy.RHASH_SIZE({a: 1, b: 2, c: 3, d: 4, e: 5})).to equal(5)
|
||||
end
|
||||
|
||||
it 'exports the RB_NIL_P macro' do
|
||||
expect(Dummy.RB_NIL_P(nil)).to eq(true)
|
||||
expect(Dummy.RB_NIL_P(:foo)).to eq(false)
|
||||
end
|
||||
|
||||
it 'exports the RTEST macro' do
|
||||
expect(Dummy.RTEST(true)).to eq(true)
|
||||
expect(Dummy.RTEST(:foo)).to eq(true)
|
||||
expect(Dummy.RTEST(false)).to eq(false)
|
||||
expect(Dummy.RTEST(nil)).to eq(false)
|
||||
end
|
||||
|
||||
it 'exports the OBJ_FROZEN macro' do
|
||||
expect(Dummy.OBJ_FROZEN(Object.new)).to eq(false)
|
||||
expect(Dummy.OBJ_FROZEN(Object.new.freeze)).to eq(true)
|
||||
end
|
||||
|
||||
describe 'coercions' do
|
||||
it "(INT2FIX)" do
|
||||
expect(Dummy.INT2FIX(10)).to eq(10)
|
||||
|
@ -183,6 +204,56 @@ describe HelixRuntime do
|
|||
end
|
||||
end
|
||||
|
||||
describe "HELIX_rb_str_valid_encoding_p" do
|
||||
it "matches #valid_encoding?" do
|
||||
str = "hello world"
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "hello world".encode("BIG5")
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "hello world".force_encoding("BIG5")
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "hello"
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "hello".encode("BIG5")
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "hello".force_encoding("BIG5")
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
|
||||
str = "\330"
|
||||
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
|
||||
end
|
||||
end
|
||||
|
||||
describe "HELIX_rb_str_ascii_only_p" do
|
||||
it "matches #ascii_only?" do
|
||||
str = "hello world"
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "hello world".encode("BIG5")
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "hello world".force_encoding("BIG5")
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "hello"
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "hello".encode("BIG5")
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "hello".force_encoding("BIG5")
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
|
||||
str = "\330"
|
||||
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Data_{Wrap,Get,Set}_Struct" do
|
||||
it "can allocate then change the data" do
|
||||
wrapper = Dummy::Wrapper.new
|
||||
|
|
|
@ -39,11 +39,30 @@ static VALUE TEST_RARRAY_CONST_PTR(VALUE _self, VALUE val) {
|
|||
return SIZET2NUM((uintptr_t)HELIX_RARRAY_CONST_PTR(val));
|
||||
}
|
||||
|
||||
static VALUE TEST_RHASH_SIZE(VALUE _self, VALUE val) {
|
||||
return LONG2NUM(HELIX_RHASH_SIZE(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_RB_NIL_P(VALUE _self, VALUE val) {
|
||||
int result = HELIX_RB_NIL_P(val);
|
||||
return result ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_RTEST(VALUE _self, VALUE val) {
|
||||
int result = HELIX_RTEST(val);
|
||||
return result ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_OBJ_FROZEN(VALUE _self, VALUE val) {
|
||||
int result = HELIX_OBJ_FROZEN(val);
|
||||
return result ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_TYPE(VALUE _self, VALUE val) {
|
||||
return INT2FIX(HELIX_TYPE(val));
|
||||
}
|
||||
|
@ -112,6 +131,14 @@ VALUE allocate_wrapper(VALUE klass) {
|
|||
return HELIX_Data_Wrap_Struct(klass, NULL, deallocate_wrapper, num);
|
||||
}
|
||||
|
||||
static VALUE TEST_valid_encoding_p(VALUE _self, VALUE str) {
|
||||
return HELIX_rb_str_valid_encoding_p(str) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_ascii_only_p(VALUE _self, VALUE str) {
|
||||
return HELIX_rb_str_ascii_only_p(str) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE TEST_get_data(VALUE _self, VALUE wrapped) {
|
||||
int* num = HELIX_Data_Get_Struct_Value(wrapped);
|
||||
return INT2FIX(*num);
|
||||
|
@ -186,10 +213,14 @@ void Init_dummy() {
|
|||
EXPORT_RUBY_FUNC(RARRAY_PTR, 1);
|
||||
EXPORT_FUNC(RARRAY_CONST_PTR, 1);
|
||||
EXPORT_RUBY_FUNC(RARRAY_CONST_PTR, 1);
|
||||
EXPORT_FUNC(RHASH_SIZE, 1);
|
||||
EXPORT_FUNC(RB_TYPE_P, 2);
|
||||
EXPORT_FUNC(RB_NIL_P, 1);
|
||||
EXPORT_FUNC(OBJ_FROZEN, 1);
|
||||
EXPORT_FUNC(TYPE, 1);
|
||||
EXPORT_FUNC(INT2FIX, 1);
|
||||
EXPORT_FUNC(FIX2INT, 1);
|
||||
EXPORT_FUNC(RTEST, 1);
|
||||
|
||||
EXPORT_FUNC(NUM2U64, 1);
|
||||
EXPORT_FUNC(U642NUM, 1);
|
||||
|
@ -204,6 +235,9 @@ void Init_dummy() {
|
|||
|
||||
EXPORT_FUNC(STR2STR, 2);
|
||||
|
||||
EXPORT_FUNC(valid_encoding_p, 1);
|
||||
EXPORT_FUNC(ascii_only_p, 1);
|
||||
|
||||
EXPORT_FUNC(get_data, 1);
|
||||
EXPORT_FUNC(get_data_ptr, 1);
|
||||
EXPORT_FUNC(set_data, 2);
|
||||
|
|
|
@ -6,6 +6,7 @@ sudo apt-get install mingw-w64 -y
|
|||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y
|
||||
source $HOME/.cargo/env
|
||||
|
||||
gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
||||
\curl -sSL https://get.rvm.io | bash -s stable --ruby
|
||||
source $HOME/.rvm/scripts/rvm
|
||||
|
||||
|
|
|
@ -70,4 +70,10 @@ impl ClassDefinition {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undefine_class_method(&self, name: c_string) {
|
||||
unsafe {
|
||||
sys::rb_undef_method(sys::CLASS_OF(self.class.0), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
use sys::{self, VALUE, Qtrue, Qfalse};
|
||||
use super::{FromRuby, CheckResult, ToRuby, ToRubyResult};
|
||||
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby};
|
||||
impl FromRuby for bool {
|
||||
type Checked = bool;
|
||||
|
||||
impl UncheckedValue<bool> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<bool> {
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_TRUE) || sys::RB_TYPE_P(self, sys::T_FALSE) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
fn from_ruby(value: VALUE) -> CheckResult<bool> {
|
||||
if unsafe { sys::RB_TYPE_P(value, sys::T_TRUE) } {
|
||||
Ok(true)
|
||||
} else if unsafe { sys::RB_TYPE_P(value, sys::T_FALSE) } {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(::invalid(self, "true or false"))
|
||||
type_error!(value, "a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<bool> for CheckedValue<bool> {
|
||||
fn to_rust(self) -> bool {
|
||||
self.inner == unsafe { Qtrue }
|
||||
fn from_checked(checked: bool) -> bool {
|
||||
checked
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for bool {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
if self {
|
||||
unsafe { Qtrue }
|
||||
Ok(unsafe { Qtrue })
|
||||
} else {
|
||||
unsafe { Qfalse }
|
||||
Ok(unsafe { Qfalse })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
use sys::{self, VALUE, T_FLOAT, T_FIXNUM, T_BIGNUM};
|
||||
use super::{FromRuby, CheckResult, CheckedValue, ToRuby, ToRubyResult};
|
||||
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby};
|
||||
impl FromRuby for f64 {
|
||||
type Checked = CheckedValue<f64>;
|
||||
|
||||
impl UncheckedValue<f64> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<f64> {
|
||||
if unsafe { sys::RB_TYPE_P(self, T_FLOAT) || sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<f64>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FLOAT) || sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else {
|
||||
Err(::invalid(self, "a 64-bit float"))
|
||||
type_error!(value, "a 64-bit float")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<f64> for CheckedValue<f64> {
|
||||
fn to_rust(self) -> f64 {
|
||||
unsafe { sys::NUM2F64(self.inner) }
|
||||
fn from_checked(checked: CheckedValue<f64>) -> f64 {
|
||||
unsafe { sys::NUM2F64(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for f64 {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
unsafe { sys::F642NUM(self) }
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::F642NUM(self) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
use sys::{VALUE, RB_TYPE_P, T_HASH, RHASH_SIZE, rb_hash_foreach, rb_hash_new, rb_hash_aset, void, st_retval};
|
||||
use super::{FromRuby, CheckResult, ToRuby, ToRubyResult};
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::hash::Hash;
|
||||
use ::std::mem::transmute;
|
||||
|
||||
extern "C" fn rb_hash_collect(key: VALUE, value: VALUE, vec: *mut void) -> st_retval {
|
||||
let vec: &mut Vec<(VALUE, VALUE)> = unsafe { transmute(vec) };
|
||||
vec.push((key, value));
|
||||
st_retval::ST_CONTINUE
|
||||
}
|
||||
|
||||
impl<K: FromRuby + Eq + Hash, V: FromRuby> FromRuby for HashMap<K, V> {
|
||||
type Checked = Vec<(K::Checked, V::Checked)>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<Self::Checked> {
|
||||
if unsafe { RB_TYPE_P(value, T_HASH) } {
|
||||
let len = unsafe { RHASH_SIZE(value) };
|
||||
|
||||
let mut pairs = Vec::<(VALUE, VALUE)>::with_capacity(len as usize);
|
||||
unsafe { rb_hash_foreach(value, rb_hash_collect, transmute(&mut pairs)) };
|
||||
|
||||
let mut checked = Vec::<(K::Checked, V::Checked)>::with_capacity(len as usize);
|
||||
|
||||
for (k, v) in pairs.into_iter() {
|
||||
let k = K::from_ruby(k)?;
|
||||
let v = V::from_ruby(v)?;
|
||||
|
||||
checked.push((k, v));
|
||||
}
|
||||
|
||||
Ok(checked)
|
||||
} else {
|
||||
type_error!("a hash");
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: Self::Checked) -> HashMap<K, V> {
|
||||
checked.into_iter().map(|(k, v)| (K::from_checked(k), V::from_checked(v))).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: ToRuby + Eq + Hash, V: ToRuby> ToRuby for HashMap<K, V> {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
let hash = unsafe { rb_hash_new() };
|
||||
|
||||
for (k,v) in self.into_iter() {
|
||||
unsafe { rb_hash_aset(hash, k.to_ruby()?, v.to_ruby()?) };
|
||||
}
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
|
@ -1,91 +1,139 @@
|
|||
use std::mem::size_of;
|
||||
use sys::{self, VALUE, T_FIXNUM, T_BIGNUM};
|
||||
use super::{FromRuby, CheckResult, CheckedValue, ToRuby, ToRubyResult};
|
||||
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby};
|
||||
impl FromRuby for usize {
|
||||
type Checked = CheckedValue<usize>;
|
||||
|
||||
impl UncheckedValue<u64> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<u64> {
|
||||
if unsafe { sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<usize>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else if size_of::<usize>() == size_of::<u32>() {
|
||||
type_error!(value, "a 32-bit unsigned integer")
|
||||
} else {
|
||||
Err(::invalid(self, "a 64-bit unsigned integer"))
|
||||
type_error!(value, "a 64-bit unsigned integer")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<usize>) -> usize {
|
||||
unsafe { sys::NUM2USIZE(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<u64> for CheckedValue<u64> {
|
||||
fn to_rust(self) -> u64 {
|
||||
unsafe { sys::NUM2U64(self.inner) }
|
||||
impl ToRuby for usize {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::USIZE2NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRuby for isize {
|
||||
type Checked = CheckedValue<isize>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<isize>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else if size_of::<isize>() == size_of::<i32>() {
|
||||
type_error!(value, "a 32-bit signed integer")
|
||||
} else {
|
||||
type_error!(value, "a 64-bit signed integer")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<isize>) -> isize {
|
||||
unsafe { sys::NUM2ISIZE(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for isize {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::ISIZE2NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRuby for u64 {
|
||||
type Checked = CheckedValue<u64>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<u64>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else {
|
||||
type_error!(value, "a 64-bit unsigned integer")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<u64>) -> u64 {
|
||||
unsafe { sys::NUM2U64(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for u64 {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
unsafe { sys::U642NUM(self) }
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::U642NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl UncheckedValue<i64> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<i64> {
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_FIXNUM) || sys::RB_TYPE_P(self, sys::T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
impl FromRuby for i64 {
|
||||
type Checked = CheckedValue<i64>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<i64>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else {
|
||||
Err(::invalid(self, "a 64-bit signed integer"))
|
||||
type_error!(value, "a 64-bit signed integer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<i64> for CheckedValue<i64> {
|
||||
fn to_rust(self) -> i64 {
|
||||
unsafe { sys::NUM2I64(self.inner) }
|
||||
fn from_checked(checked: CheckedValue<i64>) -> i64 {
|
||||
unsafe { sys::NUM2I64(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for i64 {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
unsafe { sys::I642NUM(self) }
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::I642NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl UncheckedValue<u32> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<u32> {
|
||||
if unsafe { sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
impl FromRuby for u32 {
|
||||
type Checked = CheckedValue<u32>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<u32>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else {
|
||||
Err(::invalid(self, "a 32-bit unsigned integer"))
|
||||
type_error!(value, "a 32-bit unsigned integer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<u32> for CheckedValue<u32> {
|
||||
fn to_rust(self) -> u32 {
|
||||
unsafe { sys::NUM2U32(self.inner) }
|
||||
fn from_checked(checked: CheckedValue<u32>) -> u32 {
|
||||
unsafe { sys::NUM2U32(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for u32 {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
unsafe { sys::U322NUM(self) }
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::U322NUM(self) })
|
||||
}
|
||||
}
|
||||
|
||||
impl UncheckedValue<i32> for VALUE {
|
||||
fn to_checked(self) -> CheckResult<i32> {
|
||||
if unsafe { sys::RB_TYPE_P(self, sys::T_FIXNUM) || sys::RB_TYPE_P(self, sys::T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
impl FromRuby for i32 {
|
||||
type Checked = CheckedValue<i32>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<i32>> {
|
||||
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
|
||||
Ok(unsafe { CheckedValue::new(value) })
|
||||
} else {
|
||||
Err(::invalid(self, "a 32-bit signed integer"))
|
||||
type_error!(value, "a 32-bit signed integer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRust<i32> for CheckedValue<i32> {
|
||||
fn to_rust(self) -> i32 {
|
||||
unsafe { sys::NUM2I32(self.inner) }
|
||||
fn from_checked(checked: CheckedValue<i32>) -> i32 {
|
||||
unsafe { sys::NUM2I32(checked.to_value()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for i32 {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
unsafe { sys::I322NUM(self) }
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { sys::I322NUM(self) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,51 @@
|
|||
mod slice;
|
||||
mod string;
|
||||
mod value;
|
||||
mod unit;
|
||||
mod bool;
|
||||
mod integers;
|
||||
mod float;
|
||||
mod symbol;
|
||||
mod string;
|
||||
mod tuples;
|
||||
mod option;
|
||||
mod result;
|
||||
mod slice;
|
||||
mod vec;
|
||||
mod hash;
|
||||
|
||||
use sys::{VALUE};
|
||||
use std::marker::PhantomData;
|
||||
use super::{Error, ToError};
|
||||
use std::marker::{PhantomData, Sized};
|
||||
|
||||
pub trait FromRuby : Sized {
|
||||
type Checked /* = CheckedValue<Self> */;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<Self::Checked>;
|
||||
fn from_checked(checked: Self::Checked) -> Self;
|
||||
|
||||
fn from_ruby_unwrap(value: VALUE) -> Self {
|
||||
Self::from_checked(Self::from_ruby(value).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub type CheckResult<T> = Result<T, Error>;
|
||||
|
||||
pub struct CheckedValue<T> {
|
||||
pub inner: VALUE,
|
||||
marker: PhantomData<T>
|
||||
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>, String>;
|
||||
|
||||
pub trait UncheckedValue<T> {
|
||||
fn to_checked(self) -> CheckResult<T>;
|
||||
}
|
||||
|
||||
pub trait ToRust<T> {
|
||||
fn to_rust(self) -> T;
|
||||
}
|
||||
|
||||
pub trait ToRuby {
|
||||
fn to_ruby(self) -> VALUE;
|
||||
}
|
||||
|
||||
impl ToRuby for VALUE {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
self
|
||||
pub fn to_value(self) -> VALUE {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub type ToRubyResult = Result<VALUE, Error>;
|
||||
|
||||
pub trait ToRuby {
|
||||
fn to_ruby(self) -> ToRubyResult;
|
||||
}
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
use sys::{VALUE, Qnil};
|
||||
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby};
|
||||
use super::{FromRuby, CheckResult, ToRuby, ToRubyResult};
|
||||
|
||||
impl<T> UncheckedValue<Option<T>> for VALUE where VALUE: UncheckedValue<T> {
|
||||
fn to_checked(self) -> CheckResult<Option<T>> {
|
||||
if unsafe { self == Qnil } {
|
||||
Ok(unsafe { CheckedValue::new(self) })
|
||||
impl<T: FromRuby> FromRuby for Option<T> {
|
||||
type Checked = Option<T::Checked>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<Option<T::Checked>> {
|
||||
if unsafe { value == Qnil } {
|
||||
Ok(None)
|
||||
} else {
|
||||
UncheckedValue::<T>::to_checked(self)
|
||||
.map(|_| unsafe { CheckedValue::new(self) })
|
||||
T::from_ruby(value).map(|c| Some(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToRust<Option<T>> for CheckedValue<Option<T>> where CheckedValue<T>: ToRust<T> {
|
||||
fn to_rust(self) -> Option<T> {
|
||||
if unsafe { self.inner == Qnil } {
|
||||
None
|
||||
} else {
|
||||
let checked: CheckedValue<T> = unsafe { CheckedValue::new(self.inner) };
|
||||
Some(checked.to_rust())
|
||||
}
|
||||
fn from_checked(checked: Option<T::Checked>) -> Option<T> {
|
||||
checked.map(T::from_checked)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToRuby for Option<T> where T: ToRuby {
|
||||
fn to_ruby(self) -> VALUE {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
match self {
|
||||
Some(value) => value.to_ruby(),
|
||||
None => unsafe { Qnil }
|
||||
None => Ok(unsafe { Qnil })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use super::{ToRuby, ToRubyResult, ToError};
|
||||
|
||||
impl<T, U> ToRuby for Result<T, U> where T: ToRuby, U: ToError {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
match self {
|
||||
Ok(value) => value.to_ruby(),
|
||||
Err(message) => raise!(message)
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue