391 lines
10 KiB
C
Vendored
391 lines
10 KiB
C
Vendored
/*
|
|
** hash.c - Hash class
|
|
**
|
|
** See Copyright Notice in mruby.h
|
|
*/
|
|
|
|
#include <mruby.h>
|
|
#include <mruby/array.h>
|
|
#include <mruby/hash.h>
|
|
#include <mruby/class.h>
|
|
#include <mruby/internal.h>
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.values_at(key, ...) -> array
|
|
*
|
|
* Return an array containing the values associated with the given keys.
|
|
* Also see `Hash.select`.
|
|
*
|
|
* h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
|
|
* h.values_at("cow", "cat") #=> ["bovine", "feline"]
|
|
*/
|
|
|
|
static mrb_value
|
|
hash_values_at(mrb_state *mrb, mrb_value hash)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
mrb_value result = mrb_ary_new_capa(mrb, argc);
|
|
if (argc == 0) return result;
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
mrb_ary_push(mrb, result, mrb_hash_get(mrb, hash, argv[i]));
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.slice(*keys) -> a_hash
|
|
*
|
|
* Returns a hash containing only the given keys and their values.
|
|
*
|
|
* h = { a: 100, b: 200, c: 300 }
|
|
* h.slice(:a) #=> {:a=>100}
|
|
* h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
|
|
*/
|
|
static mrb_value
|
|
hash_slice(mrb_state *mrb, mrb_value hash)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
mrb_value result = mrb_hash_new_capa(mrb, argc);
|
|
if (argc == 0) return result; /* empty hash */
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
mrb_value key = argv[i];
|
|
mrb_value val;
|
|
|
|
val = mrb_hash_fetch(mrb, hash, key, mrb_undef_value());
|
|
if (!mrb_undef_p(val)) {
|
|
mrb_hash_set(mrb, result, key, val);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct slice_bang_i_arg {
|
|
mrb_value keep_keys;
|
|
mrb_value keys_to_remove;
|
|
};
|
|
|
|
/*
|
|
* Iterator for `slice!`.
|
|
*
|
|
* Iterates over a hash, identifying keys that are not present in the `keep_keys`
|
|
* hash. Keys identified for removal are appended to the `keys_to_remove` array.
|
|
*
|
|
* @param mrb The mruby state.
|
|
* @param key The key of the current hash entry.
|
|
* @param val The value of the current hash entry (unused).
|
|
* @param data A pointer to a `slice_bang_i_arg` struct, which contains
|
|
* `keep_keys` and `keys_to_remove`.
|
|
* @return Always returns `0` to ensure the iteration continues over all
|
|
* hash entries.
|
|
*/
|
|
static int
|
|
slice_bang_i(mrb_state *mrb, mrb_value key, mrb_value val, void *data)
|
|
{
|
|
struct slice_bang_i_arg *args = (struct slice_bang_i_arg *)data;
|
|
if (!mrb_hash_key_p(mrb, args->keep_keys, key)) {
|
|
mrb_ary_push(mrb, args->keys_to_remove, key);
|
|
}
|
|
return 0; /* Continue iteration */
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.slice!(*keys) -> a_hash
|
|
*
|
|
* Deletes keys from hsh that are not in `keys`.
|
|
* Returns a new hash containing the deleted key-value pairs.
|
|
*
|
|
* h = { a: 1, b: 2, c: 3, d: 4 }
|
|
* h.slice!(:a, :c) #=> { b: 2, d: 4 }
|
|
* h #=> { a: 1, c: 3 }
|
|
*/
|
|
static mrb_value
|
|
hash_slice_bang(mrb_state *mrb, mrb_value self)
|
|
{
|
|
struct slice_bang_i_arg args;
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
|
|
args.keep_keys = mrb_hash_new_capa(mrb, argc);
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
mrb_hash_set(mrb, args.keep_keys, argv[i], mrb_true_value());
|
|
}
|
|
|
|
args.keys_to_remove = mrb_ary_new(mrb);
|
|
mrb_hash_foreach(mrb, mrb_hash_ptr(self), slice_bang_i, &args);
|
|
|
|
mrb_int len = RARRAY_LEN(args.keys_to_remove);
|
|
mrb_value removed_hash = mrb_hash_new_capa(mrb, len);
|
|
for (mrb_int i = 0; i < len; i++) {
|
|
mrb_value key = mrb_ary_ref(mrb, args.keys_to_remove, i);
|
|
mrb_value val = mrb_hash_delete_key(mrb, self, key);
|
|
mrb_hash_set(mrb, removed_hash, key, val);
|
|
}
|
|
|
|
return removed_hash;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.except(*keys) -> a_hash
|
|
*
|
|
* Returns a hash excluding the given keys and their values.
|
|
*
|
|
* h = { a: 100, b: 200, c: 300 }
|
|
* h.except(:a) #=> {:b=>200, :c=>300}
|
|
* h.except(:b, :c, :d) #=> {:a=>100}
|
|
*/
|
|
static mrb_value
|
|
hash_except(mrb_state *mrb, mrb_value hash)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
mrb_value result = mrb_hash_dup(mrb, hash);
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
mrb_hash_delete_key(mrb, result, argv[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Hash[ key, value, ... ] -> new_hash
|
|
* Hash[ [ [key, value], ... ] ] -> new_hash
|
|
* Hash[ object ] -> new_hash
|
|
*
|
|
* Creates a new hash populated with the given objects.
|
|
*
|
|
* Similar to the literal `{ _key_ => _value_, ... }`. In the first
|
|
* form, keys and values occur in pairs, so there must be an even number of
|
|
* arguments.
|
|
*
|
|
* The second and third form take a single argument which is either an array
|
|
* of key-value pairs or an object convertible to a hash.
|
|
*
|
|
* Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
|
|
* Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200}
|
|
* Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200}
|
|
*/
|
|
static mrb_value
|
|
hash_s_create(mrb_state *mrb, mrb_value klass)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
mrb_value hash;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
|
|
if (argc == 1) {
|
|
mrb_value obj = argv[0];
|
|
|
|
/* Case 1: Hash argument - copy constructor */
|
|
if (mrb_hash_p(obj)) {
|
|
hash = mrb_hash_new(mrb);
|
|
mrb_hash_merge(mrb, hash, obj);
|
|
/* Set the correct class if it's a subclass */
|
|
if (mrb_class_ptr(klass) != mrb->hash_class) {
|
|
mrb_obj_ptr(hash)->c = mrb_class_ptr(klass);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/* Case 2: Array argument with nested arrays */
|
|
if (mrb_array_p(obj)) {
|
|
mrb_int ary_len = RARRAY_LEN(obj);
|
|
hash = mrb_hash_new_capa(mrb, ary_len);
|
|
|
|
for (mrb_int i = 0; i < ary_len; i++) {
|
|
mrb_value elem = mrb_ary_ref(mrb, obj, i);
|
|
mrb_value key = mrb_nil_value(), val = mrb_nil_value();
|
|
|
|
if (!mrb_array_p(elem)) {
|
|
mrb_raisef(mrb, E_ARGUMENT_ERROR,
|
|
"wrong element type %C (expected array)", mrb_obj_class(mrb, elem));
|
|
}
|
|
|
|
mrb_int elem_len = RARRAY_LEN(elem);
|
|
|
|
switch (elem_len) {
|
|
case 2:
|
|
key = mrb_ary_ref(mrb, elem, 0);
|
|
val = mrb_ary_ref(mrb, elem, 1);
|
|
break;
|
|
case 1:
|
|
key = mrb_ary_ref(mrb, elem, 0);
|
|
val = mrb_nil_value();
|
|
break;
|
|
case 0:
|
|
default:
|
|
mrb_raisef(mrb, E_ARGUMENT_ERROR,
|
|
"invalid number of elements (%i for 1..2)", elem_len);
|
|
}
|
|
|
|
mrb_hash_set(mrb, hash, key, val);
|
|
}
|
|
/* Set the correct class if it's a subclass */
|
|
if (mrb_class_ptr(klass) != mrb->hash_class) {
|
|
mrb_obj_ptr(hash)->c = mrb_class_ptr(klass);
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
/* Case 3: Multiple arguments as key-value pairs */
|
|
if (argc % 2 != 0) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "odd number of arguments for Hash");
|
|
}
|
|
|
|
hash = mrb_hash_new_capa(mrb, argc / 2);
|
|
for (mrb_int i = 0; i < argc; i += 2) {
|
|
mrb_hash_set(mrb, hash, argv[i], argv[i + 1]);
|
|
}
|
|
|
|
/* Set the correct class if it's a subclass */
|
|
if (mrb_class_ptr(klass) != mrb->hash_class) {
|
|
mrb_obj_ptr(hash)->c = mrb_class_ptr(klass);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
/* Data structure for hash_key search */
|
|
struct key_search {
|
|
mrb_value target;
|
|
mrb_value result;
|
|
mrb_bool found;
|
|
};
|
|
|
|
/*
|
|
* Iterator for `key`.
|
|
*
|
|
* This function is designed to be used with `mrb_hash_foreach`. It iterates
|
|
* over hash entries to find a key that corresponds to a specific target value.
|
|
*
|
|
* When a match is found, it stores the key, sets a flag, and stops the
|
|
* iteration.
|
|
*
|
|
* @param mrb The mruby state.
|
|
* @param key The key of the current hash entry.
|
|
* @param val The value of the current hash entry.
|
|
* @param data A pointer to a `key_search` struct, which contains the
|
|
* target value and holds the result.
|
|
* @return Returns `1` to stop the iteration once a match is found,
|
|
* otherwise returns `0` to continue.
|
|
*/
|
|
static int
|
|
hash_key_i(mrb_state *mrb, mrb_value key, mrb_value val, void *data)
|
|
{
|
|
struct key_search *search = (struct key_search*)data;
|
|
if (mrb_equal(mrb, val, search->target)) {
|
|
search->result = key;
|
|
search->found = TRUE;
|
|
return 1; /* Stop iteration */
|
|
}
|
|
return 0; /* Continue iteration */
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.key(value) -> key
|
|
*
|
|
* Returns the key of an occurrence of a given value. If the value is
|
|
* not found, returns `nil`.
|
|
*
|
|
* h = { "a" => 100, "b" => 200, "c" => 300, "d" => 300 }
|
|
* h.key(200) #=> "b"
|
|
* h.key(300) #=> "c" or "d"
|
|
* h.key(999) #=> nil
|
|
*/
|
|
static mrb_value
|
|
hash_key(mrb_state *mrb, mrb_value hash)
|
|
{
|
|
mrb_value val;
|
|
struct key_search search;
|
|
|
|
mrb_get_args(mrb, "o", &val);
|
|
|
|
search.target = val;
|
|
search.result = mrb_nil_value();
|
|
search.found = FALSE;
|
|
|
|
mrb_hash_foreach(mrb, mrb_hash_ptr(hash), hash_key_i, &search);
|
|
|
|
return search.found ? search.result : mrb_nil_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hsh.__merge(*others) -> hsh
|
|
*
|
|
* Merges multiple hashes into hsh. This is an internal method
|
|
* used by merge! for non-block cases.
|
|
*
|
|
* Raises ArgumentError if no arguments given.
|
|
* Raises TypeError if any argument is not a Hash.
|
|
*
|
|
* h = { a: 1, b: 2 }
|
|
* h.__merge({ c: 3 }, { d: 4 }) #=> { a: 1, b: 2, c: 3, d: 4 }
|
|
*/
|
|
static mrb_value
|
|
hash_merge(mrb_state *mrb, mrb_value hash)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
|
|
/* Validate arguments */
|
|
if (argc == 0) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given 0, expected 1+)");
|
|
}
|
|
|
|
/* Merge multiple hashes in C */
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
if (!mrb_hash_p(argv[i])) {
|
|
mrb_raisef(mrb, E_TYPE_ERROR, "no implicit conversion of %C into Hash",
|
|
mrb_obj_class(mrb, argv[i]));
|
|
}
|
|
mrb_hash_merge(mrb, hash, argv[i]);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static const mrb_mt_entry hash_ext_rom_entries[] = {
|
|
MRB_MT_ENTRY(hash_values_at, MRB_SYM(values_at), MRB_ARGS_ANY()),
|
|
MRB_MT_ENTRY(hash_slice, MRB_SYM(slice), MRB_ARGS_ANY()),
|
|
MRB_MT_ENTRY(hash_slice_bang, MRB_SYM_B(slice), MRB_ARGS_ANY()),
|
|
MRB_MT_ENTRY(hash_except, MRB_SYM(except), MRB_ARGS_ANY()),
|
|
MRB_MT_ENTRY(hash_key, MRB_SYM(key), MRB_ARGS_REQ(1)),
|
|
MRB_MT_ENTRY(hash_merge, MRB_SYM(__merge), MRB_ARGS_ANY()),
|
|
};
|
|
|
|
void
|
|
mrb_mruby_hash_ext_gem_init(mrb_state *mrb)
|
|
{
|
|
struct RClass *h;
|
|
|
|
h = mrb->hash_class;
|
|
MRB_MT_INIT_ROM(mrb, h, hash_ext_rom_entries);
|
|
mrb_define_class_method_id(mrb, h, MRB_OPSYM(aref), hash_s_create, MRB_ARGS_ANY());
|
|
}
|
|
|
|
void
|
|
mrb_mruby_hash_ext_gem_final(mrb_state *mrb)
|
|
{
|
|
}
|