From 19d07db1411832d74b09e2da25629d50e356248d Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Tue, 1 Jul 2025 14:34:38 +0900 Subject: [PATCH] mruby-hash-ext: implement Hash.[] constructor in C Replace Ruby implementation with C version for better performance. Handles all argument forms: multiple args, hash copy, array of arrays. Supports subclasses and maintains full compatibility with existing tests. Co-authored-by: Atlassian Rovo Dev --- mrbgems/mruby-hash-ext/mrblib/hash.rb | 56 -------------- mrbgems/mruby-hash-ext/src/hash_ext.c | 103 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 56 deletions(-) diff --git a/mrbgems/mruby-hash-ext/mrblib/hash.rb b/mrbgems/mruby-hash-ext/mrblib/hash.rb index 1b7311930..bc5b36746 100644 --- a/mrbgems/mruby-hash-ext/mrblib/hash.rb +++ b/mrbgems/mruby-hash-ext/mrblib/hash.rb @@ -3,62 +3,6 @@ class Hash # ISO does not define Hash#each_pair, so each_pair is defined in gem. alias each_pair each - ## - # 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} - # - - def self.[](*object) - length = object.length - if length == 1 - o = object[0] - if Hash === o - h = self.new - o.each { |k, v| h[k] = v } - return h - elsif o.respond_to?(:to_a) - h = self.new - o.to_a.each do |i| - raise ArgumentError, "wrong element type #{i.class} (expected array)" unless i.respond_to?(:to_a) - k, v = nil - case i.size - when 2 - k = i[0] - v = i[1] - when 1 - k = i[0] - else - raise ArgumentError, "invalid number of elements (#{i.size} for 1..2)" - end - h[k] = v - end - return h - end - end - unless length % 2 == 0 - raise ArgumentError, 'odd number of arguments for Hash' - end - h = self.new - 0.step(length - 2, 2) do |i| - h[object[i]] = object[i + 1] - end - h - end ## # call-seq: diff --git a/mrbgems/mruby-hash-ext/src/hash_ext.c b/mrbgems/mruby-hash-ext/src/hash_ext.c index 45fe1f22a..9412ea943 100644 --- a/mrbgems/mruby-hash-ext/src/hash_ext.c +++ b/mrbgems/mruby-hash-ext/src/hash_ext.c @@ -7,6 +7,7 @@ #include #include #include +#include #include /* @@ -96,6 +97,107 @@ hash_except(mrb_state *mrb, mrb_value hash) 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; +} + void mrb_mruby_hash_ext_gem_init(mrb_state *mrb) { @@ -105,6 +207,7 @@ mrb_mruby_hash_ext_gem_init(mrb_state *mrb) mrb_define_method_id(mrb, h, MRB_SYM(values_at), hash_values_at, MRB_ARGS_ANY()); mrb_define_method_id(mrb, h, MRB_SYM(slice), hash_slice, MRB_ARGS_ANY()); mrb_define_method_id(mrb, h, MRB_SYM(except), hash_except, MRB_ARGS_ANY()); + mrb_define_class_method_id(mrb, h, MRB_OPSYM(aref), hash_s_create, MRB_ARGS_ANY()); } void