diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index f9415cccb..578912636 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -5026,34 +5026,38 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, pop(); } else if (pat_hash->rest && pat_hash->rest != (node*)-2) { - /* **var: capture remaining keys (hash minus matched keys) */ + /* **var: capture remaining keys via hash.__except(key1, key2, ...) */ struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_hash->rest); if (rest_var->name) { int var_idx = lv_idx(s, rest_var->name); - int rest_reg = cursp(); - /* rest = hash.dup */ - gen_move(s, cursp(), hash_reg, 0); + int recv = cursp(); + gen_move(s, recv, hash_reg, 0); push(); - genop_3(s, OP_SEND, rest_reg, sym_idx(s, MRB_SYM_2(s->mrb, dup)), 0); - /* Delete each matched key from the copy */ - for (pair = pat_hash->pairs; pair; pair = pair->cdr) { - node *key = pair->car->car; - gen_move(s, cursp(), rest_reg, 0); - push(); - if (node_type(key) == NODE_SYM) { - genop_2(s, OP_LOADSYM, cursp(), sym_idx(s, sym_node(key)->symbol)); + if (num_keys > 0) { + /* Pass matched keys directly as arguments */ + int i = 0; + for (pair = pat_hash->pairs; pair; pair = pair->cdr, i++) { + node *key = pair->car->car; + if (node_type(key) == NODE_SYM) { + genop_2(s, OP_LOADSYM, cursp(), sym_idx(s, sym_node(key)->symbol)); + } + else { + codegen(s, key, VAL); + } + push(); } - else { - codegen(s, key, VAL); - } - push(); push(); pop(); pop(); pop(); - genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM_2(s->mrb, __delete)), 1); + genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM_2(s->mrb, __except)), num_keys); + for (i = 0; i < num_keys; i++) pop(); + } + else { + /* No keys to exclude: rest = hash.dup */ + genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM_2(s->mrb, dup)), 0); } /* Assign to variable */ if (var_idx > 0) { - gen_move(s, var_idx, rest_reg, 1); + gen_move(s, var_idx, recv, 1); } - pop(); /* rest_reg */ + pop(); /* recv */ } } /* ** (anonymous rest) or partial match: nothing extra */ diff --git a/src/hash.c b/src/hash.c index 8f0f40f0c..512ecdf6a 100644 --- a/src/hash.c +++ b/src/hash.c @@ -2042,6 +2042,39 @@ mrb_hash_compact(mrb_state *mrb, mrb_value hash) return hash; } +/* + * Internal method for pattern matching **rest. + * Returns a new hash excluding specified keys. + * + * {a: 1, b: 2, c: 3}.__except(:a, :c) #=> {b: 2} + */ +static mrb_value +mrb_hash_except_keys(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(mrb); + struct RHash *h = mrb_hash_ptr(hash); + int ai = mrb_gc_arena_save(mrb); + + H_EACH(h, entry) { + mrb_bool found = FALSE; + for (mrb_int i = 0; i < argc; i++) { + if (mrb_equal(mrb, entry->key, argv[i])) { + found = TRUE; + break; + } + } + if (!found) { + mrb_hash_set(mrb, result, entry->key, entry->val); + } + mrb_gc_arena_restore(mrb, ai); + } + return result; +} + /* * call-seq: * hash.to_s -> string @@ -2280,5 +2313,6 @@ mrb_init_hash(mrb_state *mrb) mrb_define_method_id(mrb, h, MRB_SYM(rassoc), mrb_hash_rassoc, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, h, MRB_SYM(__merge), mrb_hash_merge_m, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, h, MRB_SYM(__compact), mrb_hash_compact, MRB_ARGS_NONE()); /* implementation of Hash#compact! */ + mrb_define_method_id(mrb, h, MRB_SYM(__except), mrb_hash_except_keys, MRB_ARGS_ANY()); /* for pattern matching **rest */ } #undef lesser