diff --git a/Cargo.toml b/Cargo.toml index 48298e21..4a924907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ 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/primitive", "examples/calculator", "examples/console", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank"] [dependencies] libc = "0.2.0" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c8bae72e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM bitnami/minideb:jessie + +RUN apt-get update +RUN apt-get install curl -y --no-install-recommends +RUN apt-get install ca-certificates -y --no-install-recommends +RUN apt-get install wget tar -y --no-install-recommends +RUN apt-get install build-essential make -y --no-install-recommends +RUN apt-get install build-essential git -y --no-install-recommends + +RUN adduser --disabled-password --gecos '' helix +USER helix + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y + +ENV PATH="/home/helix/.cargo/bin:${PATH}" + +RUN rustup target add x86_64-unknown-linux-musl + +RUN cd ~ && wget -O ruby-install-0.6.1.tar.gz https://github.com/postmodern/ruby-install/archive/v0.6.1.tar.gz +RUN cd ~ && tar -xzvf ruby-install-0.6.1.tar.gz + +USER root + +RUN cd /home/helix/ruby-install-0.6.1 && make install +RUN ruby-install ruby 2.4.1 --system + +USER helix +ENV GEM_HOME=/home/helix/.gem +ENV GEM_PATH=/home/helix/.gem +ENV PATH="/home/helix/.gem/bin:${PATH}" +RUN gem install bundler rake --no-ri --no-rdoc + +VOLUME "app" +WORKDIR "/app" diff --git a/crates/libcruby-sys/build.rs b/crates/libcruby-sys/build.rs index f4b58bad..f836bdd0 100644 --- a/crates/libcruby-sys/build.rs +++ b/crates/libcruby-sys/build.rs @@ -1,65 +1,67 @@ -use std::{env,fs}; -use std::path::Path; -use std::process::Command; +// use std::{env,fs}; +// use std::path::Path; +// use std::process::Command; -fn main() { - // TODO: Clean this all up. There has to be a prettier way. - let target = env::var("TARGET").expect("TARGET required"); - let manifest_dir_str = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR required"); - let version_str = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION required").replace(".", "-"); +fn main() {} - let root = Path::new(manifest_dir_str.as_str()); +// fn main() { +// // TODO: Clean this all up. There has to be a prettier way. +// let target = env::var("TARGET").expect("TARGET required"); +// let manifest_dir_str = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR required"); +// let version_str = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION required").replace(".", "-"); - let lib_root_str = env::var("HELIX_LIB_DIR").unwrap_or(manifest_dir_str.clone()); - let lib_root = Path::new(lib_root_str.as_str()); +// let root = Path::new(manifest_dir_str.as_str()); - // Best way I could find to tell if we're packaging vs just building - let is_packaging = root.parent().expect("root has no parent").ends_with("target/package"); - let libname32 = format!("helix-runtime-{}.i386", version_str); - let libname64 = format!("helix-runtime-{}.x86_64", version_str); - let libname = if target.starts_with("x86_64") { libname64.clone() } else { libname32.clone() }; +// let lib_root_str = env::var("HELIX_LIB_DIR").unwrap_or(manifest_dir_str.clone()); +// let lib_root = Path::new(lib_root_str.as_str()); - // Not required for non-Windows, but it needs to be part of the package - if is_packaging && (!lib_root.join(format!("{}.lib", libname32)).exists() || - !lib_root.join(format!("{}.lib", libname64)).exists()) { - panic!("{}.lib and {}.lib must exist when packaging. Please run ./prepackage.sh and set HELIX_LIB_DIR.", libname32, libname64); - } +// // Best way I could find to tell if we're packaging vs just building +// let is_packaging = root.parent().expect("root has no parent").ends_with("target/package"); +// let libname32 = format!("helix-runtime-{}.i386", version_str); +// let libname64 = format!("helix-runtime-{}.x86_64", version_str); +// let libname = if target.starts_with("x86_64") { libname64.clone() } else { libname32.clone() }; - if target.contains("windows") && !lib_root.join(format!("{}.lib", libname)).exists() { - panic!("{}.lib must exist when running. Set HELIX_LIB_DIR to ruby/windows_build for development.", libname); - } +// // Not required for non-Windows, but it needs to be part of the package +// if is_packaging && (!lib_root.join(format!("{}.lib", libname32)).exists() || +// !lib_root.join(format!("{}.lib", libname64)).exists()) { +// panic!("{}.lib and {}.lib must exist when packaging. Please run ./prepackage.sh and set HELIX_LIB_DIR.", libname32, libname64); +// } - if target.contains("windows") { - let out_dir_str = env::var("OUT_DIR").expect("OUT_DIR required"); +// if target.contains("windows") && !lib_root.join(format!("{}.lib", libname)).exists() { +// panic!("{}.lib must exist when running. Set HELIX_LIB_DIR to ruby/windows_build for development.", libname); +// } - let out_dir = Path::new(out_dir_str.as_str()); +// if target.contains("windows") { +// let out_dir_str = env::var("OUT_DIR").expect("OUT_DIR required"); - // Read info about current Ruby install - let raw_ruby_info = Command::new("ruby") - .arg(root.join("ruby_info.rb")) - .output() - .expect("failed to get Ruby info"); - let raw_ruby_output = String::from_utf8_lossy(&raw_ruby_info.stdout); - let mut raw_ruby_lines = raw_ruby_output.lines(); - let ruby_libdir = Path::new(raw_ruby_lines.next().expect("Ruby info has no libdir")); - let libruby = raw_ruby_lines.next().expect("Ruby info has no LIBRUBY"); - let libruby_so = raw_ruby_lines.next().expect("Ruby info has no LIBRUBY_SO"); - if raw_ruby_lines.next() != None { - panic!("Unexpected information returned in Ruby info"); - } +// let out_dir = Path::new(out_dir_str.as_str()); - let ruby_libname = libruby_so.split('.').next().expect("can't extract Ruby lib name"); +// // Read info about current Ruby install +// let raw_ruby_info = Command::new("ruby") +// .arg(root.join("ruby_info.rb")) +// .output() +// .expect("failed to get Ruby info"); +// let raw_ruby_output = String::from_utf8_lossy(&raw_ruby_info.stdout); +// let mut raw_ruby_lines = raw_ruby_output.lines(); +// let ruby_libdir = Path::new(raw_ruby_lines.next().expect("Ruby info has no libdir")); +// let libruby = raw_ruby_lines.next().expect("Ruby info has no LIBRUBY"); +// let libruby_so = raw_ruby_lines.next().expect("Ruby info has no LIBRUBY_SO"); +// if raw_ruby_lines.next() != None { +// panic!("Unexpected information returned in Ruby info"); +// } - // Copy .dll.a file to .lib since Rust msvc looks for .lib files only - fs::copy(ruby_libdir.join(libruby), out_dir.join(ruby_libname).with_extension("lib")) - .expect("unable to copy libruby"); +// let ruby_libname = libruby_so.split('.').next().expect("can't extract Ruby lib name"); - // Set up linker - println!("cargo:rustc-flags=-L {libpath} -l dylib={libruby} -L {root} -l helix-runtime:{libname}", - libpath=out_dir.to_str().expect("can't get str from out_dir"), - libruby=ruby_libname, - root=lib_root.to_str().expect("can't get str from root dir"), - libname=libname); - } -} +// // Copy .dll.a file to .lib since Rust msvc looks for .lib files only +// fs::copy(ruby_libdir.join(libruby), out_dir.join(ruby_libname).with_extension("lib")) +// .expect("unable to copy libruby"); + +// // Set up linker +// println!("cargo:rustc-flags=-L {libpath} -l dylib={libruby} -L {root} -l helix-runtime:{libname}", +// libpath=out_dir.to_str().expect("can't get str from out_dir"), +// libruby=ruby_libname, +// root=lib_root.to_str().expect("can't get str from root dir"), +// libname=libname); +// } +// } diff --git a/crates/libcruby-sys/src/lib.rs b/crates/libcruby-sys/src/lib.rs index f6f3b9c6..5e40a17b 100644 --- a/crates/libcruby-sys/src/lib.rs +++ b/crates/libcruby-sys/src/lib.rs @@ -158,6 +158,8 @@ extern "C" { #[link_name = "HELIX_T_BIGNUM"] pub static T_BIGNUM: isize; + pub fn rb_ary_aref(obj: VALUE, offset: libc::c_long) -> VALUE; + // unknown if working? // fn rb_define_variable(name: c_string, value: *const VALUE); pub fn rb_obj_class(obj: VALUE) -> VALUE; diff --git a/examples/membership/src/lib.rs b/examples/membership/src/lib.rs index 1db19e94..4f459f3d 100644 --- a/examples/membership/src/lib.rs +++ b/examples/membership/src/lib.rs @@ -27,13 +27,19 @@ ruby! { } } -// Delete me: +// This is incredibly terrible and illustrates an increasingly bad problem +// with the current factoring around reopen. TLDR: reopen really doesn't +// work at the moment and you shouldn't use it. -use helix::{UncheckedValue, ToRust}; +use helix::{UncheckedValue, ToRust, ruby}; +use helix::coercions::CallFrame; impl AsRef<[usize]> for Array { fn as_ref(&self) -> &[usize] { - let checked = self.helix.to_checked().unwrap(); - checked.to_rust() + let lt: &'static () = unsafe { std::mem::transmute(&()) }; + let frame = unsafe { CallFrame::new(lt) }; + let val = unsafe { ruby::Value::new(self.helix, frame) }; + let checked = UncheckedValue::<&[usize]>::to_checked(val).unwrap(); + ToRust::<&[usize]>::to_rust(checked) } } diff --git a/examples/primitive/.gitignore b/examples/primitive/.gitignore new file mode 100644 index 00000000..4268bc75 --- /dev/null +++ b/examples/primitive/.gitignore @@ -0,0 +1,3 @@ +target +*.bundle +*.gem diff --git a/examples/primitive/Cargo.toml b/examples/primitive/Cargo.toml new file mode 100644 index 00000000..fc57c1c3 --- /dev/null +++ b/examples/primitive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "primitive" +version = "0.1.0" +authors = ["Peter Wagenet "] + +[lib] + +crate-type = ["cdylib"] + +[dependencies.helix] +path = "../.." diff --git a/examples/primitive/Gemfile b/examples/primitive/Gemfile new file mode 100644 index 00000000..36c54510 --- /dev/null +++ b/examples/primitive/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gem 'helix_runtime', path: '../../ruby' +gem 'rake', '~> 10.0' +gem 'rspec', '~> 3.4' diff --git a/examples/primitive/Gemfile.lock b/examples/primitive/Gemfile.lock new file mode 100644 index 00000000..0cd1b4be --- /dev/null +++ b/examples/primitive/Gemfile.lock @@ -0,0 +1,39 @@ +PATH + remote: ../../ruby + specs: + helix_runtime (0.5.0) + rake (>= 10.0) + thor (~> 0.19.4) + +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.3) + rake (10.5.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + thor (0.19.4) + +PLATFORMS + ruby + x64-mingw32 + x86-mingw32 + +DEPENDENCIES + helix_runtime! + rake (~> 10.0) + rspec (~> 3.4) + +BUNDLED WITH + 1.14.6 diff --git a/examples/primitive/Rakefile b/examples/primitive/Rakefile new file mode 100755 index 00000000..354b075a --- /dev/null +++ b/examples/primitive/Rakefile @@ -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("primitive") 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 diff --git a/examples/primitive/lib/primitive.rb b/examples/primitive/lib/primitive.rb new file mode 100644 index 00000000..6350743d --- /dev/null +++ b/examples/primitive/lib/primitive.rb @@ -0,0 +1,2 @@ +require 'helix_runtime' +require 'primitive/native' diff --git a/examples/primitive/spec/primitive_spec.rb b/examples/primitive/spec/primitive_spec.rb new file mode 100644 index 00000000..2cfbf4f0 --- /dev/null +++ b/examples/primitive/spec/primitive_spec.rb @@ -0,0 +1,43 @@ +require "spec_helper" + +describe Primitive do + describe "#is_bool" do + cases = { + true => true, + false => true, + nil => false, + "true" => false, + "" => false, + 0 => false, + 0.5 => false, + {} => false, + Object.new => false + } + + cases.each do |test, expected| + it "#{test.inspect} => #{expected}" do + expect(Primitive.is_bool(test)).to eq(expected) + end + end + end + + describe "#as_bool" do + it "true" do + expect(Primitive.as_bool(true)).to eq(true) + end + + it "nil" do + expect(-> { Primitive.as_bool(nil) }).to raise_error(RuntimeError) + end + end + + describe "#first" do + it "[1, 2, 3]" do + expect(Primitive.first([1,2,3])).to eq(1) + end + + it "['a', 2, 'c']" do + expect(Primitive.first(['a', 2, 'c'])).to eq('a') + end + end +end diff --git a/examples/primitive/spec/spec_helper.rb b/examples/primitive/spec/spec_helper.rb new file mode 100644 index 00000000..93368651 --- /dev/null +++ b/examples/primitive/spec/spec_helper.rb @@ -0,0 +1,2 @@ +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +require 'primitive' diff --git a/examples/primitive/src/lib.rs b/examples/primitive/src/lib.rs new file mode 100644 index 00000000..68dc5554 --- /dev/null +++ b/examples/primitive/src/lib.rs @@ -0,0 +1,19 @@ +#[macro_use] +extern crate helix; +use helix::ruby; + +ruby! { + class Primitive { + def is_bool(value: ruby::Value) -> bool { + value.is_type(ruby::Type::True) || value.is_type(ruby::Type::False) + } + + def as_bool(value: ruby::Value) -> bool { + value.to_rust() + } + + def first(ary: ruby::Array) -> ruby::Value { + ary[0] + } + } +} diff --git a/examples/turbo_blank/src/lib.rs b/examples/turbo_blank/src/lib.rs index 7bb0fe5a..5298c660 100644 --- a/examples/turbo_blank/src/lib.rs +++ b/examples/turbo_blank/src/lib.rs @@ -13,11 +13,14 @@ ruby! { // Delete me: -use helix::{UncheckedValue, ToRust}; +use helix::{UncheckedValue, ToRust, ruby}; +use helix::coercions::CallFrame; impl ToString for RubyString { fn to_string(&self) -> String { - let checked = self.helix.to_checked().unwrap(); - checked.to_rust() + let lt = &(); + let val = unsafe { ruby::Value::new(self.helix, CallFrame::new(lt)) }; + let checked = UncheckedValue::::to_checked(val).unwrap(); + ToRust::::to_rust(checked) } } diff --git a/src/coercions/bool.rs b/src/coercions/bool.rs index e08cf3ec..a6af3017 100644 --- a/src/coercions/bool.rs +++ b/src/coercions/bool.rs @@ -1,20 +1,23 @@ use sys::{self, VALUE, Qtrue, Qfalse}; - +use coercions::*; +use ruby::Value; use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby}; -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, sys::T_TRUE) || sys::RB_TYPE_P(self, sys::T_FALSE) } { - Ok(unsafe { CheckedValue::new(self) }) +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, bool>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), sys::T_TRUE) || sys::RB_TYPE_P(self.inner(), sys::T_FALSE) } { + Ok(unsafe { CheckedValue::<'a, bool>::new(self) }) } else { Err(format!("No implicit conversion of {} into Rust bool", "?")) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, bool> { fn to_rust(self) -> bool { - self.inner == unsafe { Qtrue } + unsafe { self.inner.inner() == Qtrue } } } diff --git a/src/coercions/float.rs b/src/coercions/float.rs index 17e92cb3..279315d1 100644 --- a/src/coercions/float.rs +++ b/src/coercions/float.rs @@ -1,21 +1,24 @@ use sys::{self, VALUE, T_FLOAT, T_FIXNUM, T_BIGNUM}; +use ruby::Value; use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby}; -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, T_FLOAT) || sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, f64>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), T_FLOAT) || sys::RB_TYPE_P(self.inner(), T_FIXNUM) || sys::RB_TYPE_P(self.inner(), T_BIGNUM) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; + let val = unsafe { CheckedValue::::from_value(sys::rb_inspect(self.inner()), self.frame()) }; Err(format!("No implicit conversion of {} into Rust f64", val.to_rust())) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, f64> { fn to_rust(self) -> f64 { - unsafe { sys::NUM2F64(self.inner) } + unsafe { sys::NUM2F64(self.inner.inner()) } } } diff --git a/src/coercions/integers.rs b/src/coercions/integers.rs index 4a237883..2fba9183 100644 --- a/src/coercions/integers.rs +++ b/src/coercions/integers.rs @@ -1,21 +1,24 @@ use sys::{self, VALUE, T_FIXNUM, T_BIGNUM}; +use ruby::Value; use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby}; -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, u64>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), T_FIXNUM) || sys::RB_TYPE_P(self.inner(), T_BIGNUM) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; + let val = unsafe { CheckedValue::::from_value(sys::rb_inspect(self.inner()), self.frame()) }; Err(format!("No implicit conversion of {} into Rust u64", val.to_rust())) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, u64> { fn to_rust(self) -> u64 { - unsafe { sys::NUM2U64(self.inner) } + unsafe { sys::NUM2U64(self.inner.inner()) } } } @@ -25,20 +28,22 @@ impl ToRuby for u64 { } } -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, sys::T_FIXNUM) || sys::RB_TYPE_P(self, sys::T_BIGNUM) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, i64>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), sys::T_FIXNUM) || sys::RB_TYPE_P(self.inner(), sys::T_BIGNUM) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; + let val = unsafe { CheckedValue::::from_value(sys::rb_inspect(self.inner()), self.frame()) }; Err(format!("No implicit conversion of {} into Rust i64", val.to_rust())) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, i64> { fn to_rust(self) -> i64 { - unsafe { sys::NUM2I64(self.inner) } + unsafe { sys::NUM2I64(self.inner.inner()) } } } @@ -48,20 +53,22 @@ impl ToRuby for i64 { } } -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, T_FIXNUM) || sys::RB_TYPE_P(self, T_BIGNUM) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, u32>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), T_FIXNUM) || sys::RB_TYPE_P(self.inner(), T_BIGNUM) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; + let val = unsafe { CheckedValue::::from_value(sys::rb_inspect(self.inner()), self.frame()) }; Err(format!("No implicit conversion of {} into Rust u32", val.to_rust())) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, u32> { fn to_rust(self) -> u32 { - unsafe { sys::NUM2U32(self.inner) } + unsafe { sys::NUM2U32(self.inner.inner()) } } } @@ -71,20 +78,22 @@ impl ToRuby for u32 { } } -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, sys::T_FIXNUM) || sys::RB_TYPE_P(self, sys::T_BIGNUM) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, i32>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), sys::T_FIXNUM) || sys::RB_TYPE_P(self.inner(), sys::T_BIGNUM) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; + let val = unsafe { CheckedValue::::from_value(sys::rb_inspect(self.inner()), self.frame()) }; Err(format!("No implicit conversion of {} into Rust i32", val.to_rust())) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, i32> { fn to_rust(self) -> i32 { - unsafe { sys::NUM2I32(self.inner) } + unsafe { sys::NUM2I32(self.inner.inner()) } } } diff --git a/src/coercions/mod.rs b/src/coercions/mod.rs index b7aee43b..e52449a0 100644 --- a/src/coercions/mod.rs +++ b/src/coercions/mod.rs @@ -6,24 +6,44 @@ mod integers; mod float; mod option; +use ruby; use sys::{VALUE}; use std::marker::PhantomData; -pub struct CheckedValue { - pub inner: VALUE, - marker: PhantomData +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct CallFrame<'a> { + lifetime: &'a () } -impl CheckedValue { - pub unsafe fn new(inner: VALUE) -> CheckedValue { - CheckedValue { inner: inner, marker: PhantomData } +impl<'a> CallFrame<'a> { + pub unsafe fn new<'lt>(lifetime: &'lt ()) -> CallFrame<'lt> { + CallFrame { lifetime } } } -pub type CheckResult = Result, String>; +pub struct CheckedValue<'a, T> { + pub inner: ruby::Value<'a>, + target: PhantomData +} + +impl<'a, T> CheckedValue<'a, T> { + // This is unsafe because it's the primary way that the coercion + // protocol asserts that subsequent operations are safe + pub unsafe fn new<'lt>(inner: ruby::Value<'lt>) -> CheckedValue<'lt, T> { + CheckedValue { inner, target: PhantomData } + } + + pub unsafe fn from_value<'lt>(inner: VALUE, frame: CallFrame<'lt>) -> CheckedValue<'lt, T> { + CheckedValue { inner: ruby::Value::new(inner, frame), target: PhantomData } + } +} + +pub type CheckResult = Result; + +pub trait UncheckedValue { + type ToRust: ToRust; -pub trait UncheckedValue { - fn to_checked(self) -> CheckResult; + fn to_checked(self) -> CheckResult; } pub trait ToRust { diff --git a/src/coercions/option.rs b/src/coercions/option.rs index 4116857f..9790d166 100644 --- a/src/coercions/option.rs +++ b/src/coercions/option.rs @@ -1,33 +1,29 @@ -use sys::{VALUE, Qnil}; -use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby}; +use sys::{Qnil}; +use super::{UncheckedValue, CheckResult, CheckedValue, ToRust}; +use ruby::Value; -impl UncheckedValue> for VALUE where VALUE: UncheckedValue { - fn to_checked(self) -> CheckResult> { - if unsafe { self == Qnil } { - Ok(unsafe { CheckedValue::new(self) }) - } else { - UncheckedValue::::to_checked(self) - .map(|_| unsafe { CheckedValue::new(self) }) - } - } +pub struct CheckedOption<'a, T> { + checked: Option> } -impl ToRust> for CheckedValue> where CheckedValue: ToRust { - fn to_rust(self) -> Option { - if unsafe { self.inner == Qnil } { - None +impl<'a, T> UncheckedValue> for Value<'a> + where Value<'a>: UncheckedValue, + CheckedOption<'a, T>: ToRust> +{ + type ToRust = CheckedOption<'a, T>; + + fn to_checked(self) -> CheckResult { + if unsafe { self.inner() } == unsafe { Qnil } { + Ok(CheckedOption { checked: None }) } else { - let checked: CheckedValue = unsafe { CheckedValue::new(self.inner) }; - Some(checked.to_rust()) + UncheckedValue::::to_checked(self) + .map(|_| CheckedOption { checked: Some(unsafe { CheckedValue::::new(self) }) }) } } } -impl ToRuby for Option where T: ToRuby { - fn to_ruby(self) -> VALUE { - match self { - Some(value) => value.to_ruby(), - None => unsafe { Qnil } - } +impl<'a, T> ToRust> for CheckedOption<'a, T> where CheckedValue<'a, T>: ToRust { + fn to_rust(self) -> Option { + self.checked.map(|c| c.to_rust()) } } diff --git a/src/coercions/slice.rs b/src/coercions/slice.rs index f3331af3..6b45bf05 100644 --- a/src/coercions/slice.rs +++ b/src/coercions/slice.rs @@ -1,13 +1,15 @@ use std; use sys; -use sys::{VALUE}; use ::inspect; +use ruby::Value; 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) } { +impl<'a> UncheckedValue<&'a[usize]> for Value<'a> { + type ToRust = CheckedValue<'a, &'a[usize]>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), sys::T_ARRAY) } { Ok(unsafe { CheckedValue::new(self) }) } else { Err(format!("No implicit conversion of {} into Slice", inspect(self))) @@ -15,10 +17,10 @@ impl<'a> UncheckedValue<&'a[usize]> for VALUE { } } -impl<'a> ToRust<&'a[usize]> for CheckedValue<&'a[usize]> { +impl<'a> ToRust<&'a[usize]> for CheckedValue<'a, &'a[usize]> { fn to_rust(self) -> &'a[usize] { - let size = unsafe { sys::RARRAY_LEN(self.inner) }; - let ptr = unsafe { sys::RARRAY_PTR(self.inner) }; + let size = unsafe { sys::RARRAY_LEN(self.inner.inner()) }; + let ptr = unsafe { sys::RARRAY_PTR(self.inner.inner()) }; unsafe { std::slice::from_raw_parts(ptr as *const usize, size as usize) } } } diff --git a/src/coercions/string.rs b/src/coercions/string.rs index 87fad11a..97f02347 100644 --- a/src/coercions/string.rs +++ b/src/coercions/string.rs @@ -3,25 +3,27 @@ use std; use sys; use sys::{VALUE}; +use ruby::Value; use super::{UncheckedValue, CheckResult, CheckedValue, ToRust, ToRuby}; // VALUE -> to_coercible_rust -> CheckResult -> unwrap() -> Coercible -> to_rust() -> String -impl UncheckedValue for VALUE { - fn to_checked(self) -> CheckResult { - if unsafe { sys::RB_TYPE_P(self, sys::T_STRING) } { +impl<'a> UncheckedValue for Value<'a> { + type ToRust = CheckedValue<'a, String>; + + fn to_checked(self) -> CheckResult { + if unsafe { sys::RB_TYPE_P(self.inner(), sys::T_STRING) } { Ok(unsafe { CheckedValue::::new(self) }) } else { - let val = unsafe { CheckedValue::::new(sys::rb_inspect(self)) }; - Err(format!("No implicit conversion of {} into String", val.to_rust())) + Err(format!("No implicit conversion of {} into String", ::inspect(self))) } } } -impl ToRust for CheckedValue { +impl<'a> ToRust for CheckedValue<'a, String> { fn to_rust(self) -> String { - let size = unsafe { sys::RSTRING_LEN(self.inner) }; - let ptr = unsafe { sys::RSTRING_PTR(self.inner) }; + let size = unsafe { sys::RSTRING_LEN(self.inner.inner()) }; + let ptr = unsafe { sys::RSTRING_PTR(self.inner.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() } diff --git a/src/lib.rs b/src/lib.rs index 9c4543d7..9f212b10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,8 @@ use sys::VALUE; mod macros; mod class_definition; -mod coercions; +pub mod coercions; +pub mod ruby; pub use coercions::*; @@ -80,8 +81,8 @@ impl Class { } } -pub fn inspect(val: VALUE) -> String { - unsafe { CheckedValue::::new(sys::rb_inspect(val)).to_rust() } +pub fn inspect<'a>(val: ruby::Value<'a>) -> String { + unsafe { CheckedValue::::from_value(sys::rb_inspect(val.inner()), val.frame()).to_rust() } } pub type Metadata = ::VALUE; diff --git a/src/macros/coercions.rs b/src/macros/coercions.rs index 14398044..1758d374 100644 --- a/src/macros/coercions.rs +++ b/src/macros/coercions.rs @@ -7,23 +7,25 @@ macro_rules! codegen_coercions { struct: (), methods: $methods:tt }) => ( - impl $crate::UncheckedValue<$cls> for $crate::sys::VALUE { - fn to_checked(self) -> $crate::CheckResult<$cls> { + impl<'a> $crate::UncheckedValue<$cls> for $crate::ruby::Value<'a> { + type ToRust = $crate::CheckedValue<'a, $cls>; + + fn to_checked(self) -> $crate::CheckResult { use $crate::{CheckedValue, sys}; use ::std::ffi::{CStr}; - if unsafe { $cls == ::std::mem::transmute(sys::rb_obj_class(self)) } { + if unsafe { $cls == ::std::mem::transmute(sys::rb_obj_class(self.inner())) } { Ok(unsafe { CheckedValue::new(self) }) } else { - let val = unsafe { CStr::from_ptr(sys::rb_obj_classname(self)).to_string_lossy() }; + let val = unsafe { CStr::from_ptr(sys::rb_obj_classname(self.inner())).to_string_lossy() }; Err(format!("No implicit conversion of {} into {}", val, stringify!($cls))) } } } - impl $crate::ToRust<$cls> for $crate::CheckedValue<$cls> { + impl<'a> $crate::ToRust<$cls> for $crate::CheckedValue<'a, $cls> { fn to_rust(self) -> $cls { - $cls { helix: self.inner } + $cls { helix: unsafe { self.inner.inner() } } } } @@ -70,28 +72,33 @@ macro_rules! impl_to_ruby { #[macro_export] macro_rules! impl_struct_to_rust { ($cls:ty, $helix_id:tt) => { - impl<'a> $crate::ToRust<$cls> for $crate::CheckedValue<$cls> { + impl<'a> $crate::ToRust<$cls> for $crate::CheckedValue<'a, $cls> { fn to_rust(self) -> $cls { - unsafe { ::std::mem::transmute($crate::sys::Data_Get_Struct_Value(self.inner)) } + unsafe { ::std::mem::transmute($crate::sys::Data_Get_Struct_Value(self.inner.inner())) } } } - impl<'a> $crate::UncheckedValue<$cls> for $crate::sys::VALUE { - fn to_checked(self) -> $crate::CheckResult<$cls> { + impl<'a> $crate::UncheckedValue<$cls> for $crate::ruby::Value<'a> { + type ToRust = $crate::CheckedValue<'a, $cls>; + + fn to_checked<'lt>(self) -> $crate::CheckResult { use $crate::{CheckedValue, sys}; use ::std::ffi::{CStr}; - if unsafe { $helix_id == ::std::mem::transmute(sys::rb_obj_class(self)) } { - if unsafe { $crate::sys::Data_Get_Struct_Value(self) == ::std::ptr::null_mut() } { - Err(format!("Uninitialized {}", $crate::inspect(unsafe { sys::rb_obj_class(self) }))) + if unsafe { $helix_id == ::std::mem::transmute(sys::rb_obj_class(self.inner())) } { + if unsafe { $crate::sys::Data_Get_Struct_Value(self.inner()) == ::std::ptr::null_mut() } { + let val = unsafe { sys::rb_obj_class(self.inner()) }; + let inspect = $crate::inspect(unsafe { $crate::ruby::Value::new(val, self.frame()) }); + Err(format!("Uninitialized {}", inspect)) } else { Ok(unsafe { CheckedValue::new(self) }) } } else { - let val = unsafe { CStr::from_ptr(sys::rb_obj_classname(self)).to_string_lossy() }; - Err(format!("No implicit conversion of {} into {}", val, $crate::inspect(unsafe { sys::rb_obj_class(self) }))) + let val = unsafe { CStr::from_ptr(sys::rb_obj_classname(self.inner())).to_string_lossy() }; + let target = unsafe { $crate::ruby::Value::new(sys::rb_obj_class(self.inner()), self.frame()) }; + Err(format!("No implicit conversion of {} into {}", val, $crate::inspect(target))) } } } } -} \ No newline at end of file +} diff --git a/src/macros/init.rs b/src/macros/init.rs index e462ae85..f98859ed 100644 --- a/src/macros/init.rs +++ b/src/macros/init.rs @@ -123,7 +123,11 @@ macro_rules! codegen_define_method { #[allow(unused_imports)] use $crate::{ToRust}; + let lifetime = &(); + let frame = unsafe { $crate::coercions::CallFrame::new(lifetime) }; + $( + let $arg = unsafe { $crate::ruby::Value::new($arg, frame) }; let $arg = match $crate::UncheckedValue::<$argty>::to_checked($arg) { Ok(v) => v, Err(e) => return Err($crate::ExceptionInfo::type_error(e)) @@ -194,12 +198,17 @@ macro_rules! codegen_define_method { #[allow(unused_imports)] use $crate::{ToRust}; + let lifetime = &(); + let frame = unsafe { $crate::coercions::CallFrame::new(lifetime) }; + + let rb_self = unsafe { $crate::ruby::Value::new(rb_self, frame) }; let rust_self = match $crate::UncheckedValue::::to_checked(rb_self) { Ok(v) => v, Err(e) => return Err($crate::ExceptionInfo::with_message(e)) }; $( + let $arg = unsafe { $crate::ruby::Value::new($arg, frame) }; let $arg = match $crate::UncheckedValue::<$argty>::to_checked($arg) { Ok(v) => v, Err(e) => return Err($crate::ExceptionInfo::type_error(e)) @@ -263,7 +272,11 @@ macro_rules! codegen_define_method { #[allow(unused_imports)] use $crate::{ToRust}; + let lifetime = &(); + let frame = unsafe { $crate::coercions::CallFrame::new(lifetime) }; + $( + let $arg = unsafe { $crate::ruby::Value::new($arg, frame) }; let $arg = try!($crate::UncheckedValue::<$argty>::to_checked($arg)); )* diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 85d306f3..67d65bcc 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -79,4 +79,3 @@ macro_rules! throw { panic!($crate::ExceptionInfo::with_message(String::from($msg))) } } - diff --git a/src/ruby/array.rs b/src/ruby/array.rs new file mode 100644 index 00000000..9e883f26 --- /dev/null +++ b/src/ruby/array.rs @@ -0,0 +1,46 @@ +use libc; +use std; +use std::ops::{Deref, Index}; +use sys::{self}; +use super::{Value}; +use coercions::*; + +#[derive(Debug, Copy, Clone)] +pub struct Array<'a> { + inner: Value<'a> +} + +impl<'a> Deref for Array<'a> { + type Target = Value<'a>; + + fn deref(&self) -> &Value<'a> { + &self.inner + } +} + +impl<'a> Index for Array<'a> { + type Output = Value<'a>; + + fn index(&self, offset: usize) -> &Value<'a> { + let val = unsafe { sys::rb_ary_aref(self.to_ruby(), offset as libc::c_long) }; + unsafe { std::mem::transmute(val) } + } +} + +pub struct CheckedArray<'a> { + inner: Value<'a> +} + +impl<'a> UncheckedValue> for Value<'a> { + type ToRust = CheckedArray<'a>; + + fn to_checked(self) -> CheckResult { + Ok(CheckedArray { inner: self }) + } +} + +impl<'a> ToRust> for CheckedArray<'a> { + fn to_rust(self) -> Array<'a> { + Array { inner: self.inner } + } +} diff --git a/src/ruby/mod.rs b/src/ruby/mod.rs new file mode 100644 index 00000000..433b6920 --- /dev/null +++ b/src/ruby/mod.rs @@ -0,0 +1,7 @@ +mod value; +mod types; +mod array; + +pub use self::value::{Value}; +pub use self::types::{Type}; +pub use self::array::{Array}; diff --git a/src/ruby/types.rs b/src/ruby/types.rs new file mode 100644 index 00000000..9410cdea --- /dev/null +++ b/src/ruby/types.rs @@ -0,0 +1,57 @@ +use sys; +use ToRuby; +use super::Value; + +pub enum Type { + String, + Array, + True, + False, + Fixnum, + Bignum, + Float, + Unknown(isize) +} + +impl Type { + pub fn from(ty: isize) -> Type { + if ty == unsafe { sys::T_STRING } { + Type::String + } else if ty == unsafe { sys::T_ARRAY } { + Type::Array + } else if ty == unsafe { sys::T_TRUE } { + Type::True + } else if ty == unsafe { sys::T_FALSE } { + Type::False + } else if ty == unsafe { sys::T_FIXNUM } { + Type::Fixnum + } else if ty == unsafe { sys::T_BIGNUM } { + Type::Bignum + } else if ty == unsafe { sys::T_FLOAT } { + Type::Float + } else { + Type::Unknown(ty) + } + } + + pub fn of(val: &Value) -> Type { + Type::from(unsafe { sys::TYPE(val.to_ruby()) }) + } + + pub fn to_ruby(&self) -> isize { + match *self { + Type::String => unsafe { sys::T_STRING }, + Type::Array => unsafe { sys::T_ARRAY }, + Type::True => unsafe { sys::T_TRUE }, + Type::False => unsafe { sys::T_FALSE }, + Type::Fixnum => unsafe { sys::T_FIXNUM }, + Type::Bignum => unsafe { sys::T_BIGNUM }, + Type::Float => unsafe { sys::T_FLOAT }, + Type::Unknown(val) => val + } + } + + pub fn matches(&self, value: &Value) -> bool { + unsafe { sys::RB_TYPE_P(value.to_ruby(), self.to_ruby()) } + } +} diff --git a/src/ruby/value.rs b/src/ruby/value.rs new file mode 100644 index 00000000..86f56d38 --- /dev/null +++ b/src/ruby/value.rs @@ -0,0 +1,55 @@ +use sys::VALUE; +use super::Type; +use coercions::*; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct Value<'a> { + inner: VALUE, + frame: CallFrame<'a> +} + +impl<'a> Value<'a> { + pub unsafe fn new<'b>(value: VALUE, frame: CallFrame<'b>) -> Value<'b> { + Value { inner: value, frame } + } + + pub unsafe fn inner(&self) -> VALUE { + self.inner + } + + pub unsafe fn frame(&self) -> CallFrame<'a> { + self.frame + } + + pub fn is_type(&self, ty: Type) -> bool { + ty.matches(self) + } + + pub fn ruby_type(&self) -> Type { + Type::of(self) + } + + pub fn to_rust(&self) -> T where Value<'a>: UncheckedValue, CheckedValue<'a, T>: ToRust { + self.to_checked().unwrap().to_rust() + } +} + +impl<'a> ToRuby for Value<'a> { + fn to_ruby(self) -> VALUE { + self.inner + } +} + +impl<'a> UncheckedValue> for Value<'a> { + type ToRust = Value<'a>; + + fn to_checked(self) -> CheckResult> { + Ok(self) + } +} + +impl<'a> ToRust> for Value<'a> { + fn to_rust(self) -> Value<'a> { + self + } +}