diff --git i/hash.c w/hash.c index 873219a..8d0c72a 100644 --- i/hash.c +++ w/hash.c @@ -1748,6 +1748,45 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) return ST_CONTINUE; } +static VALUE +rb_enum_update_i(VALUE obj, VALUE hash, int argc, VALUE *argv) +{ + if (argc == 1 && RB_TYPE_P(obj, T_ARRAY)) { + argc = RARRAY_LEN(obj); + argv = RARRAY_PTR(obj); + } + if (argc == 2) { + st_insert(RHASH(hash)->ntbl, argv[0], argv[1]); + } + return Qnil; +} + +static VALUE +rb_enum_update_block_i(VALUE obj, VALUE hash, int argc, VALUE *argv) +{ + VALUE key, value = Qundef, oldval; + if (argc == 1 && RB_TYPE_P(obj, T_ARRAY)) { + argc = RARRAY_LEN(obj); + argv = RARRAY_PTR(obj); + } + switch (argc) { + case 2: + value = argv[1]; + case 1: + key = argv[0]; + oldval = rb_hash_lookup2(hash, key, Qundef); + if (oldval != Qundef) { + value = rb_yield_values(argc + 1, key, oldval, value); + } + else if (value == Qundef) { + value = rb_yield(key); + } + st_insert(RHASH(hash)->ntbl, key, value); + break; + } + return Qnil; +} + /* * call-seq: * hsh.merge!(other_hash) -> hsh @@ -1769,13 +1808,36 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) * h2 = { "b" => 254, "c" => 300 } * h1.merge!(h2) { |key, v1, v2| v1 } * #=> {"a"=>100, "b"=>200, "c"=>300} + * + * If other_hash is not a Hash but an Enumerable, its #each + * method should yield [key, newval] pairs or mere key. In the latter + * case, thegiven block is always called regardless oldval, but without + * newval. + * + * h1 = { "a" => 100, "b" => 200 } + * e1 = [["b", 254], ["c", 300]] + * h1.merge!(e1) { |key, *v| v } + * #=> {"a"=>100, "b"=>[200,254], "c"=>300} + * + * h1 = { "a" => 100, "b" => 200 } + * e1 = [["b"], ["c"]] + * h1.merge!(e1) { |key, v1, v2| v1 } + * #=> {"a"=>100, "b"=>[200], "c"=>[]} */ static VALUE rb_hash_update(VALUE hash1, VALUE hash2) { + VALUE tmp; rb_hash_modify(hash1); - hash2 = to_hash(hash2); + tmp = rb_check_hash_type(hash2); + if (NIL_P(tmp)) { + VALUE (*bfunc)(ANYARGS) = rb_block_given_p() ? + rb_enum_update_block_i : rb_enum_update_i; + rb_each_call(hash2, 0, 0, bfunc, hash1); + return hash1; + } + hash2 = tmp; if (rb_block_given_p()) { rb_hash_foreach(hash2, rb_hash_update_block_i, hash1); } diff --git i/include/ruby/ruby.h w/include/ruby/ruby.h index 4a3e0ff..aa7808b 100644 --- i/include/ruby/ruby.h +++ w/include/ruby/ruby.h @@ -1164,6 +1164,7 @@ PRINTF_ARGS(void rb_compile_warn(const char *, int, const char*, ...), 3, 4); typedef VALUE rb_block_call_func(VALUE, VALUE, int, VALUE*); VALUE rb_each(VALUE); +VALUE rb_each_call(VALUE,int,VALUE*,VALUE(*)(ANYARGS),VALUE); VALUE rb_yield(VALUE); VALUE rb_yield_values(int n, ...); VALUE rb_yield_values2(int n, const VALUE *argv); diff --git i/vm_eval.c w/vm_eval.c index 05fd3fa..6f93897 100644 --- i/vm_eval.c +++ w/vm_eval.c @@ -947,6 +947,12 @@ rb_each(VALUE obj) return rb_call(obj, idEach, 0, 0, CALL_FCALL); } +VALUE +rb_each_call(VALUE obj, int argc, VALUE *argv, VALUE (*bl_proc)(ANYARGS), VALUE arg) +{ + return rb_block_call(obj, idEach, argc, argv, bl_proc, arg); +} + static VALUE eval_string_with_cref(VALUE self, VALUE src, VALUE scope, NODE *cref, const char *volatile file, volatile int line) {