calog/vendor/mruby/mrbgems/mruby-hash-ext/src/hash_ext.c
2026-07-04 20:25:00 -05:00

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