use std::rc::Rc;
use crate::{yamrb::{helpers::{mrb_call_block, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}, Error};
pub(crate) fn initialize_hash(vm: &mut VM) {
let array_class = vm.define_standard_class("Hash");
mrb_define_cmethod(vm, array_class.clone(), "[]", Box::new(mrb_hash_get_index_self));
mrb_define_cmethod(vm, array_class.clone(), "[]=", Box::new(mrb_hash_set_index_self));
mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_hash_each));
}
fn mrb_hash_get_index_self(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
let this = vm.getself();
mrb_hash_get_index(this, args[0].clone())
}
pub fn mrb_hash_get_index(this: Rc<RObject>, key: Rc<RObject>) -> Result<Rc<RObject>, Error> {
let hash = match &this.value {
RValue::Hash(a) => a.clone(),
_ => {
return Err(Error::RuntimeError("Hash#[] must called on a hash".to_string()));
}
};
let hash = hash.borrow();
let key = key.as_ref().as_hash_key()?;
match hash.get(&key).clone() {
Some((_, value)) => Ok(value.clone()),
None => Ok(Rc::new(RObject::nil())),
}
}
fn mrb_hash_set_index_self(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
let this = vm.getself();
let key = args[0].clone();
let value = args[1].clone();
mrb_hash_set_index(this, key, value)
}
pub fn mrb_hash_set_index(this: Rc<RObject>, key: Rc<RObject>, value: Rc<RObject>) -> Result<Rc<RObject>, Error> {
let hash = match &this.value {
RValue::Hash(a) => a,
_ => {
return Err(Error::RuntimeError("Hash#[] must called on a hash".to_string()));
}
};
let mut hash = hash.borrow_mut();
let hashed = key.as_hash_key()?;
hash.insert(hashed, (key.clone(), value.clone()));
Ok(value.clone())
}
fn mrb_hash_each(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
let this = vm.getself();
let block = &args[0];
match &this.value {
RValue::Hash(hash) => {
let hash = hash.borrow();
for (_, (key, value)) in hash.iter() {
let args = vec![key.clone(), value.clone()];
mrb_call_block(vm, block.clone(), None, &args)?;
}
}
_ => {
return Err(Error::RuntimeError("Hash#each must be called on a hash".to_string()));
}
};
Ok(this.clone())
}
#[test]
fn test_hashing() {
let vec1 = RObject::string("key".to_string());
let vec2 = RObject::string("key".to_string()).clone();
assert_eq!(vec1.as_hash_key(), vec2.as_hash_key());
}
#[test]
fn test_mrb_hash_set_and_index() {
use std::collections::HashMap;
use crate::yamrb::*;
let mut vm = VM::empty();
prelude::prelude(&mut vm);
let hash = Rc::new(RObject::hash(HashMap::new()));
let keys = vec![
Rc::new(RObject::string("key".to_string())),
Rc::new(RObject::integer(1234)),
Rc::new(RObject::symbol("key2".into())),
];
let values = vec![
Rc::new(RObject::integer(1)),
Rc::new(RObject::integer(2)),
Rc::new(RObject::integer(42)),
];
for (i, key) in keys.iter().enumerate() {
let value = &values[i];
mrb_hash_set_index(hash.clone(), key.clone(), value.clone()).expect("set index failed");
}
for (i, key) in keys.iter().enumerate() {
let value = mrb_hash_get_index(hash.clone(), key.clone()).expect("getting index failed");
let value: i64 = value.as_ref().try_into().expect("value is not integer");
let expected: i64 = values[i].as_ref().try_into().expect("expected is not integer");
assert_eq!(value, expected);
}
}
#[test]
fn test_mrb_hash_set_and_index_not_found() {
use std::collections::HashMap;
use crate::yamrb::*;
let mut vm = VM::empty();
prelude::prelude(&mut vm);
let hash = Rc::new(RObject::hash(HashMap::new()));
let key = Rc::new(RObject::string("key".to_string()));
let value = Rc::new(RObject::integer(42));
mrb_hash_set_index(hash.clone(), key.clone(), value.clone()).expect("set index failed");
let key = Rc::new(RObject::string("key2".to_string()));
let value = mrb_hash_get_index(hash.clone(), key.clone()).expect("getting index failed");
let value = value.as_ref();
assert!(value.is_nil());
}