/* ** hash.c - Hash class ** ** See Copyright Notice in mruby.h */ #include #include #include #include #include /* * 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) { }