Add hash <-> HashMap coercion

This commit is contained in:
Godfrey Chan 2017-10-06 14:46:14 -07:00
parent ff2a9d2a59
commit 5ce16e9550
9 changed files with 162 additions and 0 deletions

View File

@ -49,6 +49,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"]
@ -99,6 +107,9 @@ 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;
@ -143,6 +154,9 @@ extern "C" {
#[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;
@ -177,6 +191,9 @@ extern "C" {
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_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_raise(exc: VALUE, string: c_string, ...) -> !;
pub fn rb_jump_tag(state: RubyException) -> !;

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,10 @@
#![recursion_limit="1024"]
#[macro_use]
extern crate helix;
use std::collections::HashMap;
ruby! {
class TextTransform {
def widen(text: String) -> String {
@ -22,6 +26,14 @@ ruby! {
}).collect()
}
def widen_array(text: Vec<String>) -> Vec<String> {
text.into_iter().map(TextTransform::widen).collect()
}
def widen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
text.into_iter().map(|(k,v)| (TextTransform::widen(k), TextTransform::widen(v))).collect()
}
def narrowen(text: String) -> String {
text.chars().map(|char| {
match char {
@ -41,6 +53,14 @@ ruby! {
}).collect()
}
def narrowen_array(text: Vec<String>) -> Vec<String> {
text.into_iter().map(TextTransform::narrowen).collect()
}
def narrowen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
text.into_iter().map(|(k,v)| (TextTransform::narrowen(k), TextTransform::narrowen(v))).collect()
}
def flip(text: String) -> String {
text.chars().rev().map(|char| {
match char {
@ -58,5 +78,13 @@ ruby! {
}
}).collect()
}
def flip_array(text: Vec<String>) -> Vec<String> {
text.into_iter().rev().map(TextTransform::flip).collect()
}
def flip_hash(text: HashMap<String, String>) -> HashMap<String, String> {
text.into_iter().map(|(k,v)| (TextTransform::flip(v), TextTransform::flip(k))).collect()
}
}
}

View File

@ -36,6 +36,10 @@ 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);
}

View File

@ -55,6 +55,8 @@ 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 int HELIX_TYPE(VALUE v);

View File

@ -76,6 +76,10 @@ 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
describe 'coercions' do
it "(INT2FIX)" do
expect(Dummy.INT2FIX(10)).to eq(10)

View File

@ -39,6 +39,10 @@ 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;
@ -186,6 +190,7 @@ 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(TYPE, 1);
EXPORT_FUNC(INT2FIX, 1);

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

@ -8,6 +8,7 @@ mod option;
mod result;
mod slice;
mod vec;
mod hash;
use sys::{VALUE};
use super::{Error, ToError};