Compare commits

...

91 Commits

Author SHA1 Message Date
Godfrey Chan 33df62d90e
[RIP] Add deprecation message 2020-10-23 15:27:47 -07:00
Peter Wagenet dee30aa4fd
Merge pull request #168 from tildeio/upgrade-dependencies
Update dependencies
2019-07-30 12:17:52 -07:00
Peter Wagenet 8b5b55d9b0 Update dependencies 2019-07-30 11:25:43 -07:00
Peter Wagenet 4a8e52cb56
Merge pull request #165 from tildeio/revert-148-split_out_init
Revert "Allow calling init seperately"
2019-07-30 11:15:00 -07:00
Peter Wagenet ed7a3bb730 Bump Bundler version 2019-07-30 10:52:19 -07:00
Peter Wagenet ca60150948
Revert "Allow calling init seperately" 2019-07-29 13:12:56 -07:00
Yehuda Katz 73e0534a17
Merge pull request #148 from konstin/split_out_init
Allow calling init seperately
2019-07-29 11:17:25 -07:00
Godfrey Chan 239f1de54b
Merge pull request #155 from tildeio/spike
Various improvements
2018-06-19 15:44:31 -04:00
Godfrey Chan 750be7995d [BREAKING] Allow arbitrary attributes for classes
We no longer automatically derive Clone and Debug on the structs.

Fixes #143
2018-06-16 09:24:04 -04:00
Godfrey Chan a8e1f37362 Allow arbitrary attributes for methods 2018-06-16 09:23:54 -04:00
Godfrey Chan 358f12ae18 Remove unused file 2018-06-16 09:00:44 -04:00
Godfrey Chan af2042154e give better error messages
Fixes #144 (somewhat)
2018-06-16 09:00:35 -04:00
Godfrey Chan cdbe494ecf
Merge pull request #154 from konstin/fix_workspaces
Fix cargo workspaces
2018-06-15 19:41:09 -04:00
konstin 151b7ac68f Use cargo metadata to determine the target directory
The command otherwise failed when used with cargo workspaces
2018-06-14 20:35:37 +02:00
konstin 538a1c9fa9 Add an option to pass extra arguments to cargo
This is e.g. useful for conditional compilation with features
2018-06-14 17:43:32 +02:00
konstin f5840040ef Fixes for the the examples 2018-06-14 17:43:32 +02:00
konstin 6733f74b0d Use cargo metadata to determine the target directory
The command otherwise failed when used with cargo workspaces
2018-06-14 17:43:32 +02:00
konstin f148ac0d62 Move class intialization to method
This allows generating the init function from class names alone
2018-06-14 17:43:32 +02:00
Godfrey Chan 0baf995e04
Merge pull request #153 from tildeio/ruby_visibility
Ruby visibility (part 1)
2018-06-14 03:19:23 -04:00
Terence Lee 088a636192 allow private constructor. also add docopt example
You can do this by setting:

```
def initialize(helix, ...) {
}
```

Right now this only works on the constructor and only unexported is
supported.
2018-06-14 00:09:09 -07:00
Terence Lee 7dbc5cf3e9 refactor parser to support more method attributes 2018-06-14 00:01:46 -07:00
Godfrey Chan 68b9daeeee v0.7.5 2018-06-04 16:15:00 -04:00
Godfrey Chan c6c0011b4b
Merge pull request #152 from tildeio/usize_isize_coercions
Add missing coercions for `usize` and `isize`
2018-06-04 06:43:19 -07:00
Godfrey Chan 6fa52887c9 Add game of life example 2018-06-04 09:22:38 -04:00
Godfrey Chan 16eb12ca98 Add missing coercions for `usize` and `isize` 2018-06-03 10:32:52 +09:00
Godfrey Chan 98b003c5bb v0.7.4 2018-06-02 10:43:45 +09:00
Godfrey Chan be0cffa442
Merge pull request #151 from tildeio/fix-build
Don't run tests (etc) if the build fails
2018-06-01 18:03:31 -07:00
Godfrey Chan b6a3e5acc9
Merge pull request #150 from tildeio/safer-strings
Safer string coercions
2018-06-01 17:13:19 -07:00
Godfrey Chan 0dbe9d5b4f Don't run tests (etc) if the build fails 2018-06-02 09:02:10 +09:00
Godfrey Chan faaa6b1b26 Safer string coercions
Previously, we blindly assume Ruby strings are UTF-8 and turn them
into Rust Strings (which *are* assumed to be UTF-8). This is clearly
unsafe so this commit adds some checks to cofirm that and generate
type errors appropiately.
2018-06-02 08:42:12 +09:00
Godfrey Chan 32d6a67b11
Merge pull request #149 from tildeio/tuples
Add coercions for tuples
2018-05-11 16:27:23 -07:00
Godfrey Chan fca1620cf8 Add coercions for tuples 2018-04-26 17:57:56 -07:00
Godfrey Chan 7e2ca2f418 Ensure classes with a struct implements initialize 2018-04-25 13:45:04 -07:00
Godfrey Chan bda5141f37 v0.7.3 2018-03-06 11:38:04 -08:00
Yehuda Katz 6265a62c8d
Merge pull request #139 from sgrif/sg-more-random-functions
Export another smattering of random functions/constants
2018-02-04 22:05:37 -06:00
Sean Griffin 3a24bf2124 Export another smattering of random functions/constants 2018-01-25 16:27:09 -07:00
Yehuda Katz 07d1e01647
Merge pull request #136 from sgrif/sg-nil-p
Expose `RB_NIL_P` and `RTEST` in libcruby
2018-01-10 18:48:20 -08:00
Godfrey Chan 05e5dda6de
Merge pull request #141 from dsh0416/dsh0416/fix-typo
Fix typo
2017-12-31 23:58:24 -08:00
Delton Ding b00d9ad0d4 Fix typo 2018-01-01 00:22:53 +08:00
Sean Griffin 91fe2c5828 Use `NIL_P` instead of `RB_NIL_P`
`RB_NIL_P` is new in Ruby 2.4. Even though we're linking to the older
name, I think it still makes sense to expose the newer name to Rust.
2017-12-04 13:30:38 -07:00
Godfrey Chan 8ea2f0573a
Merge pull request #135 from sgrif/sg-sync-id
impl `Sync` for `ID`
2017-12-04 10:39:14 -08:00
Sean Griffin b5965c87d1 Expose `RB_NIL_P` and `RTEST` in libcruby
These seem to be the appropriate way to check for `nil` and truthiness
of an object using the C API. I would assume that additional changes
have to happen since this touches the runtime extension, but I'm not
sure what to do.
2017-11-30 06:19:27 -07:00
Yehuda Katz 60cb64d615
Merge pull request #138 from sgrif/sg-random-functions
Expose a smattering of functions in libcruby
2017-11-28 10:22:41 -05:00
Yehuda Katz 7436ff6624
Merge pull request #137 from sgrif/sg-t-data
Expose `T_DATA` in libcruby
2017-11-28 10:21:17 -05:00
Sean Griffin ca7184e180 Expose a smattering of functions in libcruby
There's no real logical grouping to these additions, they just happen to
be functions I needed in my library which uses libcruby
2017-11-08 11:10:50 -07:00
Sean Griffin 0eb962e44a Expose `T_DATA` in libcruby
This is already re-exported by the runtime, but was missing a Rust
binding.
2017-11-08 11:10:15 -07:00
Sean Griffin 8b97e143ff impl `Sync` for `ID`
While we may be treating the representation of this type as opaque, I
think we can reasonably assume that it will never change to anything
that isn't `Sync`.

I think it may be worth considering making `VALUE` be `Sync` as well.
Even though it is effectively a pointer, it's a pointer to a *Ruby*
object, and therefore subject to the GVL.
2017-11-08 11:08:46 -07:00
Godfrey Chan 4216a3a758 Remove coercion docs for now
I'll try to bring this back ASAP. However they are currently quite outdated and causing confusion.
2017-10-17 23:33:44 -07:00
Godfrey Chan fed2e7889d Merge pull request #129 from tildeio/upgrade-thor
Upgrade thor and other cleanup
2017-10-11 10:36:51 -07:00
Peter Wagenet a7ad6e826e Improve Vagrant release tests 2017-10-10 20:02:36 -07:00
Peter Wagenet 1880ece06b Looser version requirement for Thor
This matches how Rails does it.
2017-10-10 16:17:20 -07:00
Peter Wagenet 48f0349ba1 Add descriptions for base Rake tasks 2017-10-10 16:17:05 -07:00
Peter Wagenet 17b0fbb15b Don't lock Gemfiles 2017-10-10 16:16:48 -07:00
Godfrey Chan 263ddf9937 Remove unused cslice dependency 2017-10-09 23:35:10 -07:00
Godfrey Chan f4dcbf6ea1 v0.7.2 2017-10-09 22:53:01 -07:00
Godfrey Chan 0d94bf2bb2 Add json_builder to CI 2017-10-09 22:40:47 -07:00
Godfrey Chan c485529059 Merge pull request #128 from tildeio/consume-self
Allow consuming self in methods
2017-10-09 22:18:30 -07:00
Godfrey Chan 08cbc9416b Merge pull request #127 from tildeio/symbol
Add Symbol coercion
2017-10-09 22:18:18 -07:00
Godfrey Chan 3adc5fd5b7 Allow consuming self in methods
Using the object (calling Rust methods from Ruby) after consuming
would raise a RuntimeError.

Also fixes some parser bugs that previously allowed invalid syntax
through in the arguments position.
2017-10-09 11:20:42 -07:00
Godfrey Chan 0cdd075c22 Add Symbol coercion
The main use case for symbols is to use them as a HashMap key. However,
this introduces a GC problem – we cannot store Ruby values in the heap
without properly marking/registering them, otherwise they might get
GC'ed by Ruby unexpectedly. (In fact, this is already a problem if you
have a `Vec<VALUE>`.)

I tried to avoid introducing additional problems by pinning down any
symbols that goes thought the coercion protocol. This is probably
overly aggressive, as it would cause any dynamic symbols (e.g.
`String#to_sym`) to become un-GC-able. We can revisit this once we
have a more general-purpose system to encode pinning semantics in
the type system.
2017-10-07 19:29:33 -07:00
Godfrey Chan 65a98c65d1 v0.7.1 2017-10-06 15:51:12 -07:00
Godfrey Chan 8380713764 Merge pull request #126 from tildeio/hone-array-coercions
Array coercions (actually rebased)
2017-10-06 15:46:27 -07:00
Godfrey Chan 5ce16e9550 Add hash <-> HashMap coercion 2017-10-06 14:54:11 -07:00
Godfrey Chan ff2a9d2a59 Add slice -> Ruby array 2017-10-05 10:47:42 -07:00
Godfrey Chan f584a08f5b Some coercion house-keeping 2017-10-05 09:58:42 -07:00
Godfrey Chan 385c9f1441 Port membership example to use new vec coercion 2017-10-05 09:52:27 -07:00
Godfrey Chan 259b556943 Improve error message 2017-10-05 09:44:48 -07:00
Godfrey Chan dac16373ae Rename to match Rust types 2017-10-05 09:37:45 -07:00
Peter Wagenet ce8f4430bc
Add Array coercions 2017-10-05 00:12:42 -05:00
Terence Lee 73d3258405
update examples lockfiles 2017-10-05 00:12:25 -05:00
Terence Lee aec4d726e1
implement fmt::Display for Error 2017-10-05 00:11:19 -05:00
Godfrey Chan 37b2f2f3e9 v0.7.0 2017-10-03 19:30:50 -05:00
mortyccp e2cf3df819 Fix incorrect native_lib name format bug
For project name with more then one '-' using `sub` will only replace
the first occurence. This cause `copy_native` to be failed due to
`File.exist?` check failed. Use `gsub` instead to fix.
2017-09-28 20:17:12 -07:00
Godfrey Chan e6bcbec37b Merge pull request #122 from tildeio/from_ruby
change UncheckedValue::to_checked to FromRuby::from_ruby
2017-09-26 22:45:03 -04:00
Godfrey Chan d0a4460dbb Don't panic! 2017-09-26 21:42:02 -04:00
Godfrey Chan cfdf0ce1ea Refactor `FromRuby`
- Make `Checked` an associated type. This allows `from_ruby` to carry
  over additional information (useful for `Option` etc)

- Move `ToRust` into `FromRuby` to ensure both get implemented together
2017-09-26 21:36:28 -04:00
Godfrey Chan d9ace88cc9 Remove unused UncheckedValue 2017-09-26 16:40:13 -04:00
Godfrey Chan 44c790244a Fix membership test 2017-09-26 16:37:37 -04:00
Terence Lee 6d3675c0f8 change UncheckedValue::to_checked to FromRuby::from_ruby
This allows helix gems to create type coercions
2017-09-26 16:35:02 -04:00
Godfrey Chan b9f090ed28 Cleanup downcast code 2017-09-26 14:27:39 -04:00
Godfrey Chan 9b638ac151 Test new error macros 2017-09-26 12:52:26 -04:00
Godfrey Chan 144157c064 [WIP] Exceptions cleanup 2017-09-26 11:03:39 -04:00
Godfrey Chan 7ea173ca0a Merge pull request #120 from kivikakk/tomlrb
Use 'tomlrb' instead of 'toml' gem
2017-09-21 23:10:54 +09:00
Ashe Connor 41064303ef
Use 'tomlrb' instead of 'toml' gem 2017-09-21 12:38:04 +10:00
Godfrey Chan ab7ba71b6a Allow `to_ruby` to fail
This switches the `to_ruby` trait method to return a `Result`, which
allows the coercion to fail.

The most obvious use case for this is to implement coercion for the
`Result` type in Rust (included in this PR), but there could be other
reasons why a coercion might fail. For example, if we were to implement
a coercion between a Rust and Ruby regular expression, the coercion
could fail if the Rust regular expression uses some Rust-specific
features that are not supported by the Ruby regular expression
implementation.
2017-09-20 18:50:09 +09:00
Yehuda Katz 21c6e00802 Merge pull request #118 from tildeio/compile_error
Use `compile_errors!` to report parse errors
2017-09-13 10:01:45 -07:00
Godfrey Chan 58d8da10f2 Use `compile_errors!` to report parse errors
Unfortunately, we will lose the span for these errors.

(See rust-lang/rust#44535)
2017-09-13 02:50:56 -07:00
Yehuda Katz afb61e9113 Merge pull request #117 from tildeio/ruby-name-for-classes
Ruby name for classes
2017-09-11 12:34:11 -07:00
Godfrey Chan f64727dbea Support #[ruby_name] remapping for classes 2017-09-11 02:01:02 -07:00
Godfrey Chan eeb4d56b08 Remove outdated/incorrect expansion rule in parser
Class names are not passed as meta in the AST, so this rule
wouldn't have matched anything.
2017-09-11 01:39:26 -07:00
Godfrey Chan 856244698f Use cstr-macro crate 2017-09-11 00:57:44 -07:00
106 changed files with 3624 additions and 1168 deletions

View File

@ -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
.gitignore vendored
View File

@ -1,5 +1,6 @@
target
Cargo.lock
Gemfile.lock
.vscode
*.o
*.a

View File

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

View File

@ -1,3 +1,35 @@
## 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

View File

@ -1,6 +1,6 @@
[package]
name = "helix"
version = "0.6.4"
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.4"
version = "0.7.5"
[dependencies.cstr-macro]
path = "crates/cstr-macro"

115
README.md
View File

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

View File

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

13
console.d.ts vendored
View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "libcruby-sys"
version = "0.6.4"
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-4.i386.lib",
"helix-runtime-0-6-4.x86_64.lib"
"helix-runtime-0-7-5.i386.lib",
"helix-runtime-0-7-5.x86_64.lib"
]
[dependencies]

View File

@ -1,5 +1,8 @@
#!/bin/bash
set -e
set -x
pushd ../../ruby
bundle install
bundle exec rake native_lib_files

View File

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

View File

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

View File

@ -1,47 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
colorize (0.8.1)
diff-lcs (1.3)
parslet (1.5.0)
blankslate (~> 2.0)
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)
toml (0.1.2)
parslet (~> 1.5.0)
PLATFORMS
ruby
x64-mingw32
x86-mingw32
DEPENDENCIES
colorize
helix_runtime!
rake (~> 10.0)
rspec (~> 3.4)
BUNDLED WITH
1.14.6

View File

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

View File

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

View File

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

View File

@ -1,47 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
colorize (0.8.1)
diff-lcs (1.3)
parslet (1.5.0)
blankslate (~> 2.0)
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)
toml (0.1.2)
parslet (~> 1.5.0)
PLATFORMS
ruby
x64-mingw32
x86-mingw32
DEPENDENCIES
colorize
helix_runtime!
rake (~> 10.0)
rspec (~> 3.4)
BUNDLED WITH
1.14.6

View File

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

View File

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

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

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

View File

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

6
examples/docopt/Gemfile Normal file
View File

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

20
examples/docopt/Rakefile Executable file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'docopt'
RSpec.configure do |config|
end

View File

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

View File

@ -3,4 +3,4 @@ source 'https://rubygems.org'
gemspec
gem 'helix_runtime', path: '../../ruby'
gem 'activesupport', '5.1.0.beta1'
gem 'activesupport'

View File

@ -1,51 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
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)
blankslate (2.1.2.4)
concurrent-ruby (1.0.5)
i18n (0.8.1)
minitest (5.8.3)
parslet (1.5.0)
blankslate (~> 2.0)
rake (10.5.0)
thor (0.19.4)
thread_safe (0.3.6)
toml (0.1.2)
parslet (~> 1.5.0)
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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

26
examples/game_of_life/Rakefile Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -0,0 +1,11 @@
[package]
name = "geometry"
version = "0.1.0"
authors = ["Godfrey Chan <godfrey@tilde.io>"]
[lib]
crate-type = ["cdylib"]
[dependencies.helix]
path = "../.."

View File

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

20
examples/geometry/Rakefile Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

20
examples/json_builder/Rakefile Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,45 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
diff-lcs (1.2.5)
parslet (1.5.0)
blankslate (~> 2.0)
rake (10.5.0)
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)
toml (0.1.2)
parslet (~> 1.5.0)
PLATFORMS
ruby
x64-mingw32
x86-mingw32
DEPENDENCIES
helix_runtime!
rake (~> 10.0)
rspec (~> 3.4)
BUNDLED WITH
1.14.6

View File

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

View File

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

View File

@ -1,43 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
diff-lcs (1.3)
parslet (1.5.0)
blankslate (~> 2.0)
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)
toml (0.1.2)
parslet (~> 1.5.0)
PLATFORMS
ruby
DEPENDENCIES
helix_runtime!
rake (~> 10.0)
rspec (~> 3.4)
BUNDLED WITH
1.14.6

View File

@ -5,11 +5,59 @@ describe "TextTransform" do
expect(TextTransform.widen("Hello Aaron (@tenderlove)!")).to eq("  ")
end
it "can widen array" do
expect(TextTransform.widen_array(%w"Hello Aaron (@tenderlove)!")).to eq(%w" ")
end
it "can widen hash" do
expect(TextTransform.widen_hash({
message: "Hello",
name: "Aaron",
handle: "@tenderlove"
})).to eq({
"": "",
"": "",
"": ""
})
end
it "can narrowen text" do
expect(TextTransform.narrowen("  ")).to eq("Hello Aaron (@tenderlove)!")
end
it "can narrowen array" do
expect(TextTransform.narrowen_array(%w" ")).to eq(%w"Hello Aaron (@tenderlove)!")
end
it "can narrowen hash" do
expect(TextTransform.narrowen_hash({
"": "",
"": "",
"": ""
})).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

View File

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

View File

@ -1,40 +0,0 @@
PATH
remote: ../../ruby
specs:
helix_runtime (0.6.4)
rake (>= 10.0)
thor (~> 0.19.4)
toml (~> 0.1.2)
PATH
remote: .
specs:
turbo_blank (0.1.0)
helix_runtime
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
minitest (5.8.3)
parslet (1.5.0)
blankslate (~> 2.0)
rake (10.5.0)
thor (0.19.4)
toml (0.1.2)
parslet (~> 1.5.0)
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

View File

@ -1,7 +1,4 @@
require "helix_runtime"
RubyString = String
require "turbo_blank/native"
class String

View File

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

View File

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

4
examples/unit/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target
tmp
*.bundle
*.so

11
examples/unit/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "unit"
version = "0.1.0"
authors = ["Godfrey Chan <godfrey@tilde.io>"]
[lib]
crate-type = ["cdylib"]
[dependencies.helix]
path = "../.."

4
examples/unit/Gemfile Normal file
View File

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

14
examples/unit/Rakefile Normal file
View File

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

View File

@ -0,0 +1,7 @@
require "helix_runtime"
begin
require "unit/native"
rescue LoadError
warn "Unable to load unit/native. Please run `rake build`"
end

19
examples/unit/src/lib.rs Normal file
View File

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

View 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.4";
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);

View File

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

View File

@ -24,11 +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 "toml", "~> 0.1.2"
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

View File

@ -52,7 +52,7 @@ 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
@ -61,7 +61,7 @@ module HelixRuntime
desc "Build #{project.name}"
task :build => ["helix:pre_build", "helix:check_path"] do
project.build
project.build || abort
end
desc "Remove build artifacts"

View File

@ -1,4 +1,5 @@
require 'toml'
require 'tomlrb'
require 'json'
module HelixRuntime
class Project
@ -25,7 +26,7 @@ module HelixRuntime
end
def name
@name ||= TOML.load_file(cargo_toml_path)["package"]["name"]
@name ||= Tomlrb.load_file(cargo_toml_path)["package"]["name"]
end
def cargo_toml_path
@ -33,7 +34,9 @@ module HelixRuntime
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
@ -49,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?
@ -117,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

View File

@ -1,5 +1,5 @@
module HelixRuntime
# Also update helix_runtime.c
VERSION = "0.6.4"
VERSION = "0.7.5"
GEM_VERSION = VERSION.gsub("-", ".")
end

View File

@ -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 = ""
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
str = "".encode("BIG5")
expect(Dummy.valid_encoding_p(str)).to eq(str.valid_encoding?)
str = "".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 = ""
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
str = "".encode("BIG5")
expect(Dummy.ascii_only_p(str)).to eq(str.ascii_only?)
str = "".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

View File

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

View File

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

View File

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

View File

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

View File

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

53
src/coercions/hash.rs Normal file
View File

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

View File

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

View File

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

View File

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

10
src/coercions/result.rs Normal file
View File

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

View File

@ -1,23 +1,12 @@
use std;
use sys;
use sys::{VALUE};
use super::{ToRuby, ToRubyResult};
use sys::{rb_ary_new_capa, rb_ary_push};
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust};
impl<'a> UncheckedValue<&'a[usize]> for VALUE {
fn to_checked(self) -> CheckResult<&'a[usize]> {
if unsafe { sys::RB_TYPE_P(self, sys::T_ARRAY) } {
Ok(unsafe { CheckedValue::new(self) })
} else {
Err(::invalid(self, "an Array of unsigned pointer-sized integers"))
impl<'a, T> ToRuby for &'a [T] where &'a T: ToRuby {
fn to_ruby(self) -> ToRubyResult {
let ary = unsafe { rb_ary_new_capa(self.len() as isize) };
for item in self {
unsafe { rb_ary_push(ary, item.to_ruby()?); }
}
}
}
impl<'a> ToRust<&'a[usize]> for CheckedValue<&'a[usize]> {
fn to_rust(self) -> &'a[usize] {
let size = unsafe { sys::RARRAY_LEN(self.inner) };
let ptr = unsafe { sys::RARRAY_PTR(self.inner) };
unsafe { std::slice::from_raw_parts(ptr as *const usize, size as usize) }
Ok(ary)
}
}

View File

@ -2,42 +2,52 @@ use libc;
use std;
use sys;
use sys::{VALUE};
use super::{FromRuby, CheckResult, CheckedValue, ToRuby, ToRubyResult};
use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby};
impl FromRuby for String {
type Checked = CheckedValue<String>;
// VALUE -> to_coercible_rust<String> -> CheckResult<String> -> unwrap() -> Coercible<String> -> to_rust() -> String
impl UncheckedValue<String> for VALUE {
fn to_checked(self) -> CheckResult<String> {
if unsafe { sys::RB_TYPE_P(self, sys::T_STRING) } {
Ok(unsafe { CheckedValue::<String>::new(self) })
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<String>> {
if unsafe { sys::RB_TYPE_P(value, sys::T_STRING) } {
if unsafe { sys::rb_enc_get_index(value) == sys::rb_utf8_encindex() } {
if unsafe { sys::rb_str_valid_encoding_p(value) } {
unsafe { Ok(CheckedValue::new(value)) }
} else {
type_error!(value, "a valid UTF-8 String")
}
} else {
if unsafe { sys::rb_str_ascii_only_p(value) } {
unsafe { Ok(CheckedValue::new(value)) }
} else {
type_error!(value, "an UTF-8 String")
}
}
} else {
Err(::invalid(self, "a UTF-8 String"))
type_error!(value, "a String")
}
}
}
impl ToRust<String> for CheckedValue<String> {
fn to_rust(self) -> String {
let size = unsafe { sys::RSTRING_LEN(self.inner) };
let ptr = unsafe { sys::RSTRING_PTR(self.inner) };
fn from_checked(checked: CheckedValue<String>) -> String {
let value = checked.to_value();
let size = unsafe { sys::RSTRING_LEN(value) };
let ptr = unsafe { sys::RSTRING_PTR(value) };
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size as usize) };
unsafe { std::str::from_utf8_unchecked(slice) }.to_string()
}
}
impl ToRuby for String {
fn to_ruby(self) -> VALUE {
fn to_ruby(self) -> ToRubyResult {
let ptr = self.as_ptr();
let len = self.len();
unsafe { sys::rb_utf8_str_new(ptr as *const libc::c_char, len as libc::c_long) }
Ok(unsafe { sys::rb_utf8_str_new(ptr as *const libc::c_char, len as libc::c_long) })
}
}
impl<'a> ToRuby for &'a str {
fn to_ruby(self) -> VALUE {
fn to_ruby(self) -> ToRubyResult {
let ptr = self.as_ptr();
let len = self.len();
unsafe { sys::rb_utf8_str_new(ptr as *const libc::c_char, len as libc::c_long) }
Ok(unsafe { sys::rb_utf8_str_new(ptr as *const libc::c_char, len as libc::c_long) })
}
}

25
src/coercions/symbol.rs Normal file
View File

@ -0,0 +1,25 @@
use sys::{VALUE, RB_TYPE_P, T_SYMBOL, rb_sym2id, rb_id2sym};
use super::{FromRuby, CheckedValue, CheckResult, ToRuby, ToRubyResult};
use super::super::{Symbol};
impl FromRuby for Symbol {
type Checked = CheckedValue<Symbol>;
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<Symbol>> {
if unsafe { RB_TYPE_P(value, T_SYMBOL) } {
unsafe { Ok(CheckedValue::new(value)) }
} else {
type_error!(value, "a symbol")
}
}
fn from_checked(checked: CheckedValue<Symbol>) -> Symbol {
Symbol::from_id(unsafe { rb_sym2id(checked.to_value()) })
}
}
impl ToRuby for Symbol {
fn to_ruby(self) -> ToRubyResult {
Ok(unsafe { rb_id2sym(self.to_id()) })
}
}

121
src/coercions/tuples.rs Normal file
View File

@ -0,0 +1,121 @@
use sys::{self, VALUE};
use super::{CheckResult, FromRuby, ToRuby, ToRubyResult};
use super::super::{inspect};
#[doc(hidden)]
macro_rules! impl_tuple_coercions {
($($name:ident),*) => {
impl_tuple_from_ruby!(count_items!($($name),*), $($name),*);
impl_tuple_to_ruby!(count_items!($($name),*), $($name),*);
};
($($any:tt)*) => {
compile_error!(stringify!("impl_tuple_coercions" $($any)*));
};
}
#[doc(hidden)]
macro_rules! impl_tuple_from_ruby {
($count:expr, $($name:ident),*) => {
impl<$($name: FromRuby,)*> FromRuby for ($($name,)*) {
type Checked = ($($name::Checked,)*);
fn from_ruby(value: VALUE) -> CheckResult<Self::Checked> {
if unsafe { sys::RB_TYPE_P(value, sys::T_ARRAY) } {
// Make sure we can actually do the conversions for the values.
let len = unsafe { sys::RARRAY_LEN(value) };
if len != $count {
type_error!(value, format!("an array with {} {}", $count, { if $count == 1 { "element" } else { "elements" } }))
}
extract_tuple_elements_from_ruby!(value, (0), $($name),*);
Ok(($($name,)*))
} else {
type_error!(value, "an array")
}
}
fn from_checked(checked: Self::Checked) -> Self {
#[allow(non_snake_case)]
let ($($name,)*) = checked;
($($name::from_checked($name),)*)
}
}
};
($($any:tt)*) => {
compile_error!(stringify!("impl_tuple_from_ruby" $($any)*));
};
}
#[doc(hidden)]
macro_rules! extract_tuple_elements_from_ruby {
($value:ident, $offset:tt) => {};
($value:ident, $offset:tt, $name:ident $($rest:tt)*) => {
#[allow(non_snake_case)]
let $name = {
let val = unsafe { sys::rb_ary_entry($value, $offset as isize) };
match $name::from_ruby(val) {
Ok(v) => v,
Err(e) => type_error!(format!("Failed to convert {}, element {} has the wrong type: {}", inspect($value), $offset, e)),
}
};
extract_tuple_elements_from_ruby!($value, ($offset + 1) $($rest)*);
};
($($any:tt)*) => {
compile_error!(stringify!("extract_tuple_elements_from_ruby" $($any)*));
};
}
#[doc(hidden)]
macro_rules! impl_tuple_to_ruby {
($count:expr, $($name:ident),*) => {
impl<$($name: ToRuby,)*> ToRuby for ($($name,)*) {
fn to_ruby(self) -> ToRubyResult {
let ary = unsafe { sys::rb_ary_new_capa($count as isize) };
#[allow(non_snake_case)]
let ($($name,)*) = self;
$(
unsafe { sys::rb_ary_push(ary, $name.to_ruby()?); }
)*;
Ok(ary)
}
}
};
($($any:tt)*) => {
compile_error!(stringify!("impl_tuple_to_ruby" $($any)*));
};
}
#[doc(hidden)]
macro_rules! count_items {
() => { 0 };
($item:tt $(, $rest:tt)*) => { 1 + count_items!($($rest),*) };
($($any:tt)*) => {
compile_error!(stringify!("count_items" $($any)*));
};
}
impl_tuple_coercions!(A);
impl_tuple_coercions!(A, B);
impl_tuple_coercions!(A, B, C);
impl_tuple_coercions!(A, B, C, D);
impl_tuple_coercions!(A, B, C, D, E);
impl_tuple_coercions!(A, B, C, D, E, F);
impl_tuple_coercions!(A, B, C, D, E, F, G);
impl_tuple_coercions!(A, B, C, D, E, F, G, H);
impl_tuple_coercions!(A, B, C, D, E, F, G, H, I);
impl_tuple_coercions!(A, B, C, D, E, F, G, H, I, J);
impl_tuple_coercions!(A, B, C, D, E, F, G, H, I, J, K);
impl_tuple_coercions!(A, B, C, D, E, F, G, H, I, J, K, L);

View File

@ -1,8 +1,24 @@
use sys::{self, VALUE};
use ToRuby;
use sys::{VALUE, Qnil};
use super::{FromRuby, CheckResult, ToRuby, ToRubyResult};
impl ToRuby for () {
fn to_ruby(self) -> VALUE {
unsafe { sys::Qnil }
impl FromRuby for () {
type Checked = ();
fn from_ruby(value: VALUE) -> CheckResult<()> {
if value == unsafe { Qnil } {
Ok(())
} else {
type_error!(value, "nil")
}
}
fn from_checked(checked: ()) -> () {
checked
}
}
impl ToRuby for () {
fn to_ruby(self) -> ToRubyResult {
Ok(unsafe { Qnil })
}
}

20
src/coercions/value.rs Normal file
View File

@ -0,0 +1,20 @@
use sys::{VALUE};
use super::{FromRuby, CheckResult, ToRuby, ToRubyResult};
impl FromRuby for VALUE {
type Checked = VALUE;
fn from_ruby(value: VALUE) -> CheckResult<VALUE> {
Ok(value)
}
fn from_checked(checked: VALUE) -> VALUE {
checked
}
}
impl ToRuby for VALUE {
fn to_ruby(self) -> ToRubyResult {
Ok(self)
}
}

42
src/coercions/vec.rs Normal file
View File

@ -0,0 +1,42 @@
use sys::{self, VALUE};
use super::{CheckResult, FromRuby, ToRuby, ToRubyResult};
use super::super::{inspect};
impl<T: FromRuby> FromRuby for Vec<T> {
type Checked = Vec<T::Checked>;
fn from_ruby(value: VALUE) -> CheckResult<Self::Checked> {
if unsafe { sys::RB_TYPE_P(value, sys::T_ARRAY) } {
// Make sure we can actually do the conversions for the values.
let len = unsafe { sys::RARRAY_LEN(value) };
let mut checked = Vec::with_capacity(len as usize);
for i in 0..len {
let val = unsafe { sys::rb_ary_entry(value, i) };
match T::from_ruby(val) {
Ok(v) => checked.push(v),
Err(e) => type_error!(format!("Failed to convert {}, element {} has the wrong type: {}", inspect(value), i, e)),
}
}
Ok(checked)
} else {
type_error!(value, "an array")
}
}
fn from_checked(checked: Self::Checked) -> Self {
checked.into_iter().map(T::from_checked).collect()
}
}
impl<T: ToRuby> ToRuby for Vec<T> {
fn to_ruby(self) -> ToRubyResult {
let ary = unsafe { sys::rb_ary_new_capa(self.len() as isize) };
for item in self {
unsafe { sys::rb_ary_push(ary, item.to_ruby()?); }
}
Ok(ary)
}
}

84
src/errors.rs Normal file
View File

@ -0,0 +1,84 @@
use super::{Class, ToRuby};
use std::{any, fmt};
use sys::{VALUE, SPRINTF_TO_S, c_string, rb_eRuntimeError, rb_raise};
#[derive(Copy, Clone, Debug)]
pub struct Error {
class: Class,
message: ErrorMessage
}
#[derive(Copy, Clone, Debug)]
enum ErrorMessage {
Static(c_string),
Dynamic(VALUE)
}
impl Error {
pub fn with_c_string(message: c_string) -> Error {
Error { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Static(message) }
}
pub fn with_value(message: VALUE) -> Error {
Error { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Dynamic(message) }
}
pub fn from_any(any: Box<any::Any>) -> Error {
any.downcast::<Error>()
.map(|e| *e)
.or_else(|any| any.downcast::<&str>().map(|e| e.to_error()))
.or_else(|any| any.downcast::<String>().map(|e| e.to_error()))
.unwrap_or_else(|any| format!("Unknown Error (caused by `{:?}`)", any).to_error())
}
pub fn with_class(self, class: Class) -> Error {
Error { class, message: self.message }
}
pub unsafe fn raise(self) -> ! {
match self.message {
ErrorMessage::Static(c_string) => rb_raise(self.class.to_value(), c_string),
ErrorMessage::Dynamic(value) => rb_raise(self.class.to_value(), SPRINTF_TO_S, value)
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.message {
ErrorMessage::Static(c_string) => {
use ::std::ffi::CStr;
write!(f, "{}", unsafe { CStr::from_ptr(c_string) }.to_str().unwrap())
},
ErrorMessage::Dynamic(value) => {
use super::FromRuby;
write!(f, "{}", String::from_ruby_unwrap(value))
}
}
}
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
pub trait ToError {
fn to_error(self) -> Error;
}
impl ToError for Error {
fn to_error(self) -> Error {
self
}
}
impl<'a> ToError for &'a str {
fn to_error(self) -> Error {
Error::with_value(self.to_ruby().unwrap())
}
}
impl ToError for String {
fn to_error(self) -> Error {
Error::with_value(self.to_ruby().unwrap())
}
}

View File

@ -1,4 +1,11 @@
extern crate cslice;
#![recursion_limit="1024"]
#[allow(unused_imports)]
#[macro_use]
extern crate cstr_macro;
#[doc(hidden)]
pub use cstr_macro::*;
#[doc(hidden)]
pub extern crate libc;
@ -8,26 +15,80 @@ pub extern crate libcruby_sys as sys;
// pub use rb;
use std::ffi::CStr;
use sys::VALUE;
use sys::{VALUE, ID};
#[macro_export]
macro_rules! raise {
($msg:expr) => { return Err($crate::ToError::to_error($msg)); };
($class:expr, $msg:expr) => {
return Err($crate::ToError::to_error($msg).with_class($class));
};
}
#[macro_export]
macro_rules! raise_panic {
($msg:expr) => { panic!($crate::ToError::to_error($msg)); };
($class:expr, $msg:expr) => {
panic!($crate::ToError::to_error($msg).with_class($class));
};
}
#[macro_export]
macro_rules! type_error {
($message:expr) => { raise!(unsafe { $crate::Class::from_value($crate::sys::rb_eTypeError) }, $message); };
($actual:expr, $expected:expr) => {
{
type_error!(format!("Expected {}, got {}", $expected, $crate::inspect($actual)));
}
};
}
mod macros;
mod class_definition;
mod coercions;
mod errors;
mod macros;
pub use coercions::*;
pub use errors::*;
#[repr(C)]
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
pub struct Symbol(ID);
// Since the main use case for `Symbol` at the moment is as the
// key for a `HashMap`, this tries to avoid GC issues by alaways
// pinning the symbol, essentially making it a "copy type". This
// is probably overly aggressive, we can reconsider this when we
// have a more general-purpose mechanism to encode pinning
// semantics in the type system.
impl Symbol {
pub fn from_id(id: ID) -> Symbol {
Symbol(id)
}
pub fn to_id(self) -> ID {
self.0
}
pub fn from_string(string: String) -> Symbol {
Symbol(unsafe { sys::rb_intern_str(string.to_ruby().unwrap()) })
}
pub fn to_string(self) -> String {
unsafe { String::from_ruby_unwrap(sys::rb_id2str(self.0)) }
}
}
pub use class_definition::{ClassDefinition, MethodDefinition};
#[repr(C)]
#[derive(Copy, Clone, Debug)]
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub struct Class(VALUE);
impl Class {
pub fn inner(&self) -> VALUE {
self.0
}
}
pub trait RubyMethod {
fn install(self, class: VALUE, name: &CStr);
}
@ -65,6 +126,14 @@ fn ObjectClass() -> Class {
}
impl Class {
pub unsafe fn from_value(value: VALUE) -> Class {
Class(value)
}
pub fn to_value(self) -> VALUE {
self.0
}
pub fn new(name: &CStr) -> Class {
ObjectClass().subclass(name)
}
@ -81,12 +150,7 @@ impl Class {
}
pub fn inspect(val: VALUE) -> String {
unsafe { CheckedValue::<String>::new(sys::rb_inspect(val)).to_rust() }
}
pub fn invalid(val: VALUE, expected: &str) -> String {
let val = unsafe { CheckedValue::<String>::new(sys::rb_inspect(val)) };
format!("Expected {}, got {}", expected, val.to_rust())
unsafe { String::from_ruby_unwrap(sys::rb_inspect(val)) }
}
pub unsafe fn as_usize(value: ::VALUE) -> usize {
@ -94,58 +158,3 @@ pub unsafe fn as_usize(value: ::VALUE) -> usize {
}
pub type Metadata = ::VALUE;
#[derive(Copy, Clone, Debug)]
pub struct ExceptionInfo {
pub exception: Class,
pub message: VALUE
}
impl ExceptionInfo {
pub fn with_message<T: ToRuby>(string: T) -> ExceptionInfo {
ExceptionInfo {
exception: Class(unsafe { sys::rb_eRuntimeError }),
message: string.to_ruby(),
}
}
pub fn type_error<T: ToRuby>(string: T) -> ExceptionInfo {
ExceptionInfo {
exception: Class(unsafe { sys::rb_eTypeError }),
message: string.to_ruby(),
}
}
pub fn from_any(any: Box<std::any::Any>) -> ExceptionInfo {
any.downcast_ref::<ExceptionInfo>()
.map(|e| *e)
.or_else(||
any.downcast_ref::<&'static str>()
.map(|e| e.to_string())
.map(ExceptionInfo::with_message)
)
.or_else(||
any.downcast_ref::<String>()
.map(|e| e.as_str())
.map(ExceptionInfo::with_message)
)
.unwrap_or_else(||
ExceptionInfo::with_message(format!("Unknown Error; err={:?}", any))
)
}
pub fn message(&self) -> VALUE {
self.message
}
pub fn raise(&self) -> ! {
unsafe {
sys::rb_raise(self.exception.0,
sys::SPRINTF_TO_S,
self.message);
}
}
}
unsafe impl Send for ExceptionInfo {}
unsafe impl Sync for ExceptionInfo {}

View File

@ -2,7 +2,9 @@
macro_rules! codegen_allocator {
({
type: class,
name: $name:tt,
rust_name: $rust_name:tt,
ruby_name: $ruby_name:tt,
attributes: $attributes:tt,
meta: $meta:tt,
struct: (),
methods: $methods:tt
@ -10,24 +12,26 @@ macro_rules! codegen_allocator {
({
type: class,
name: $cls:tt,
rust_name: $rust_name:tt,
ruby_name: $ruby_name:tt,
attributes: $attributes:tt,
meta: { pub: $pub:tt, reopen: false },
struct: $struct:tt,
methods: [ $($method:tt)* ]
}) => (
impl $cls {
extern "C" fn __mark__(_klass: &$cls) {}
extern "C" fn __free__(_klass: Option<Box<$cls>>) {}
impl $rust_name {
extern "C" fn __mark__(_klass: &$rust_name) {}
extern "C" fn __free__(_klass: Option<Box<$rust_name>>) {}
#[inline]
fn __alloc_with__(rust_self: Option<Box<$cls>>) -> $crate::sys::VALUE {
fn __alloc_with__(rust_self: Option<Box<$rust_name>>) -> $crate::sys::VALUE {
use ::std::mem::transmute;
unsafe {
let instance = $crate::sys::Data_Wrap_Struct(
transmute($cls),
transmute($cls::__mark__ as usize),
transmute($cls::__free__ as usize),
transmute($rust_name),
transmute($rust_name::__mark__ as usize),
transmute($rust_name::__free__ as usize),
transmute(rust_self)
);

Some files were not shown because too many files have changed in this diff Show More