diff options
author | ktsj <ktsj@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-04-17 06:48:03 +0000 |
---|---|---|
committer | ktsj <ktsj@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-04-17 06:48:03 +0000 |
commit | 9738f96fcfe50b2a605e350bdd40bd7a85665f54 (patch) | |
tree | a8495fa0a315ef4015f01db4d158b74987d18277 | |
parent | b077654a2c89485c086e77c337d30a11ff3781c3 (diff) |
Introduce pattern matching [EXPERIMENTAL]
[ruby-core:87945] [Feature #14912]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@67586 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | array.c | 8 | ||||
-rw-r--r-- | ast.c | 20 | ||||
-rw-r--r-- | compile.c | 590 | ||||
-rw-r--r-- | defs/id.def | 1 | ||||
-rw-r--r-- | error.c | 2 | ||||
-rw-r--r-- | eval.c | 2 | ||||
-rw-r--r-- | ext/objspace/objspace.c | 4 | ||||
-rw-r--r-- | ext/ripper/tools/dsl.rb | 2 | ||||
-rw-r--r-- | hash.c | 8 | ||||
-rw-r--r-- | include/ruby/ruby.h | 1 | ||||
-rw-r--r-- | internal.h | 1 | ||||
-rw-r--r-- | lib/pp.rb | 4 | ||||
-rw-r--r-- | node.c | 37 | ||||
-rw-r--r-- | node.h | 19 | ||||
-rw-r--r-- | parse.y | 714 | ||||
-rw-r--r-- | test/coverage/test_coverage.rb | 32 | ||||
-rw-r--r-- | test/ripper/test_parser_events.rb | 18 | ||||
-rw-r--r-- | test/ripper/test_sexp.rb | 302 | ||||
-rw-r--r-- | test/ruby/test_pattern_matching.rb | 1075 | ||||
-rw-r--r-- | vm.c | 1 |
21 files changed, 2840 insertions, 3 deletions
@@ -14,6 +14,8 @@ sufficient information, see the ChangeLog file or Redmine === Language changes +* Introduce pattern matching [Feature #14912] + * Method reference operator, <code>.:</code> is introduced as an experimental feature. [Feature #12125] [Feature #13581] @@ -6544,6 +6544,12 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) return v; } +static VALUE +rb_ary_deconstruct(VALUE ary) +{ + return ary; +} + /* * Arrays are ordered, integer-indexed collections of any object. * @@ -6910,5 +6916,7 @@ Init_Array(void) rb_define_method(rb_cArray, "dig", rb_ary_dig, -1); rb_define_method(rb_cArray, "sum", rb_ary_sum, -1); + rb_define_method(rb_cArray, "deconstruct", rb_ary_deconstruct, 0); + id_random = rb_intern("random"); } @@ -364,8 +364,12 @@ node_children(rb_ast_t *ast, NODE *node) return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); case NODE_CASE2: return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); + case NODE_CASE3: + return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); case NODE_WHEN: return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next); + case NODE_IN: + return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next); case NODE_WHILE: goto loop; case NODE_UNTIL: @@ -622,6 +626,22 @@ node_children(rb_ast_t *ast, NODE *node) } return rb_ary_new_from_args(3, locals, NEW_CHILD(ast, node->nd_args), NEW_CHILD(ast, node->nd_body)); } + case NODE_ARYPTN: + { + struct rb_ary_pattern_info *apinfo = node->nd_apinfo; + return rb_ary_new_from_args(4, + NEW_CHILD(ast, node->nd_pconst), + NEW_CHILD(ast, apinfo->pre_args), + NEW_CHILD(ast, apinfo->rest_arg), + NEW_CHILD(ast, apinfo->post_args)); + } + case NODE_HSHPTN: + { + return rb_ary_new_from_args(3, + NEW_CHILD(ast, node->nd_pconst), + NEW_CHILD(ast, node->nd_pkwargs), + NEW_CHILD(ast, node->nd_pkwrestarg)); + } case NODE_ARGS_AUX: case NODE_LAST: break; @@ -5237,6 +5237,593 @@ compile_case2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no } static int +iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int in_alt_pattern) +{ + const int line = nd_line(node); + + switch (nd_type(node)) { + case NODE_ARYPTN: { + /* + * if pattern.use_rest_num? + * rest_num = 0 + * end + * if pattern.has_constant_node? + * unless pattern.constant === obj + * goto match_failed + * end + * end + * unless obj.respond_to?(:deconstruct) + * goto match_failed + * end + * d = obj.deconstruct + * unless Array === d + * goto type_error + * end + * min_argc = pattern.pre_args_num + pattern.post_args_num + * if pattern.has_rest_arg? + * unless d.length >= min_argc + * goto match_failed + * end + * else + * unless d.length == min_argc + * goto match_failed + * end + * end + * pattern.pre_args_num.each do |i| + * unless pattern.pre_args[i].match?(d[i]) + * goto match_failed + * end + * end + * if pattern.use_rest_num? + * rest_num = d.length - min_argc + * if pattern.has_rest_arg? && pattern.has_rest_arg_id # not `*`, but `*rest` + * unless pattern.rest_arg.match?(d[pattern.pre_args_num, rest_num]) + * goto match_failed + * end + * end + * end + * pattern.post_args_num.each do |i| + * j = pattern.pre_args_num + i + * if pattern.use_rest_num? + * j += rest_num + * end + * unless pattern.post_args[i].match?(d[j]) + * goto match_failed + * end + * end + * true + * goto fin + * type_error: + * FrozenCore.raise TypeError + * match_failed: + * false + * fin: + */ + struct rb_ary_pattern_info *apinfo = node->nd_apinfo; + const NODE *args = apinfo->pre_args; + const int pre_args_num = apinfo->pre_args ? rb_long2int(apinfo->pre_args->nd_alen) : 0; + const int post_args_num = apinfo->post_args ? rb_long2int(apinfo->post_args->nd_alen) : 0; + + const int min_argc = pre_args_num + post_args_num; + const int use_rest_num = apinfo->rest_arg && ((nd_type(apinfo->rest_arg) != NODE_BEGIN) || + (nd_type(apinfo->rest_arg) == NODE_BEGIN && post_args_num > 0)); + + LABEL *match_failed, *type_error, *fin; + int i; + match_failed = NEW_LABEL(line); + type_error = NEW_LABEL(line); + fin = NEW_LABEL(line); + + if (use_rest_num) { + ADD_INSN1(ret, line, putobject, INT2FIX(0)); /* allocate stack for rest_num */ + ADD_INSN(ret, line, swap); + } + + if (node->nd_pconst) { + ADD_INSN(ret, line, dup); + CHECK(COMPILE(ret, "constant", node->nd_pconst)); + ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + ADD_INSNL(ret, line, branchunless, match_failed); + } + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct"))); + ADD_SEND(ret, line, idRespond_to, INT2FIX(1)); + ADD_INSNL(ret, line, branchunless, match_failed); + + ADD_SEND(ret, line, rb_intern("deconstruct"), INT2FIX(0)); + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, rb_cArray); + ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + ADD_INSNL(ret, line, branchunless, type_error); + + ADD_INSN(ret, line, dup); + ADD_SEND(ret, line, idLength, INT2FIX(0)); + ADD_INSN1(ret, line, putobject, INT2FIX(min_argc)); + ADD_SEND(ret, line, apinfo->rest_arg ? idGE : idEq, INT2FIX(1)); + ADD_INSNL(ret, line, branchunless, match_failed); + + for (i = 0; i < pre_args_num; i++) { + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, INT2FIX(i)); + ADD_SEND(ret, line, idAREF, INT2FIX(1)); + iseq_compile_pattern_each(iseq, ret, args->nd_head, in_alt_pattern); + args = args->nd_next; + ADD_INSNL(ret, line, branchunless, match_failed); + } + + if (apinfo->rest_arg) { + if (nd_type(apinfo->rest_arg) == NODE_BEGIN) { + if (post_args_num > 0) { + ADD_INSN(ret, line, dup); + ADD_SEND(ret, line, idLength, INT2FIX(0)); + ADD_INSN1(ret, line, putobject, INT2FIX(min_argc)); + ADD_SEND(ret, line, idMINUS, INT2FIX(1)); + ADD_INSN1(ret, line, setn, INT2FIX(2)); + ADD_INSN(ret, line, pop); + } + } + else { + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, INT2FIX(pre_args_num)); + ADD_INSN1(ret, line, topn, INT2FIX(1)); + ADD_SEND(ret, line, idLength, INT2FIX(0)); + ADD_INSN1(ret, line, putobject, INT2FIX(min_argc)); + ADD_SEND(ret, line, idMINUS, INT2FIX(1)); + ADD_INSN1(ret, line, setn, INT2FIX(4)); + ADD_SEND(ret, line, idAREF, INT2FIX(2)); + + iseq_compile_pattern_each(iseq, ret, apinfo->rest_arg, in_alt_pattern); + ADD_INSNL(ret, line, branchunless, match_failed); + } + } + + args = apinfo->post_args; + for (i = 0; i < post_args_num; i++) { + ADD_INSN(ret, line, dup); + + ADD_INSN1(ret, line, putobject, INT2FIX(pre_args_num + i)); + if (use_rest_num) { + ADD_INSN1(ret, line, topn, INT2FIX(3)); + ADD_SEND(ret, line, idPLUS, INT2FIX(1)); + } + + ADD_SEND(ret, line, idAREF, INT2FIX(1)); + iseq_compile_pattern_each(iseq, ret, args->nd_head, in_alt_pattern); + args = args->nd_next; + ADD_INSNL(ret, line, branchunless, match_failed); + } + + ADD_INSN(ret, line, pop); + if (use_rest_num) { + ADD_INSN(ret, line, pop); + } + ADD_INSN1(ret, line, putobject, Qtrue); + ADD_INSNL(ret, line, jump, fin); + + ADD_LABEL(ret, type_error); + ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line, putobject, rb_eTypeError); + ADD_INSN1(ret, line, putobject, rb_fstring_lit("deconstruct must return Array")); + ADD_SEND(ret, line, id_core_raise, INT2FIX(2)); + + ADD_LABEL(ret, match_failed); + ADD_INSN(ret, line, pop); + if (use_rest_num) { + ADD_INSN(ret, line, pop); + } + ADD_INSN1(ret, line, putobject, Qfalse); + ADD_LABEL(ret, fin); + + break; + } + case NODE_HSHPTN: { + /* + * keys = nil + * if pattern.has_kw_args_node? && !pattern.has_kw_rest_arg_node? + * keys = pattern.kw_args_node.keys + * end + * if pattern.has_constant_node? + * unless pattern.constant === obj + * goto match_failed + * end + * end + * unless obj.respond_to?(:deconstruct_keys) + * goto match_failed + * end + * d = obj.deconstruct_keys(keys) + * unless Hash === d + * goto type_error + * end + * if pattern.has_kw_rest_arg_node? + * d = d.dup + * end + * if pattern.has_kw_args_node? + * pattern.kw_args_node.each |k,| + * unless d.key?(k) + * goto match_failed + * end + * end + * pattern.kw_args_node.each |k, pat| + * if pattern.has_kw_rest_arg_node? + * unless pat.match?(d.delete(k)) + * goto match_failed + * end + * else + * unless pat.match?(d[k]) + * goto match_failed + * end + * end + * end + * else + * unless d.empty? + * goto match_failed + * end + * end + * if pattern.has_kw_rest_arg_node? + * pattern.kw_rest_arg_node.match?(d) + * end + * true + * goto fin + * type_error: + * FrozenCore.raise TypeError + * match_failed: + * false + * fin: + */ + LABEL *match_failed, *type_error, *fin; + VALUE keys = Qnil; + + match_failed = NEW_LABEL(line); + type_error = NEW_LABEL(line); + fin = NEW_LABEL(line); + + if (node->nd_pkwargs && !node->nd_pkwrestarg) { + const NODE *kw_args = node->nd_pkwargs->nd_head; + keys = rb_ary_new_capa(kw_args ? kw_args->nd_alen/2 : 0); + iseq_add_mark_object_compile_time(iseq, rb_obj_hide(keys)); + while (kw_args) { + rb_ary_push(keys, kw_args->nd_head->nd_lit); + kw_args = kw_args->nd_next->nd_next; + } + } + + if (node->nd_pconst) { + ADD_INSN(ret, line, dup); + CHECK(COMPILE(ret, "constant", node->nd_pconst)); + ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + ADD_INSNL(ret, line, branchunless, match_failed); + } + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct_keys"))); + ADD_SEND(ret, line, idRespond_to, INT2FIX(1)); + ADD_INSNL(ret, line, branchunless, match_failed); + + if (NIL_P(keys)) { + ADD_INSN(ret, line, putnil); + } + else { + ADD_INSN1(ret, line, duparray, keys); + } + ADD_SEND(ret, line, rb_intern("deconstruct_keys"), INT2FIX(1)); + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, rb_cHash); + ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + ADD_INSNL(ret, line, branchunless, type_error); + + if (node->nd_pkwrestarg) { + ADD_SEND(ret, line, rb_intern("dup"), INT2FIX(0)); + } + + if (node->nd_pkwargs) { + int i; + int keys_num; + const NODE *args; + args = node->nd_pkwargs->nd_head; + if (args) { + DECL_ANCHOR(match_values); + INIT_ANCHOR(match_values); + keys_num = rb_long2int(args->nd_alen) / 2; + for (i = 0; i < keys_num; i++) { + NODE *key_node = args->nd_head; + NODE *value_node = args->nd_next->nd_head; + VALUE key; + + if (nd_type(key_node) != NODE_LIT) { + UNKNOWN_NODE("NODE_IN", key_node, COMPILE_NG); + } + key = key_node->nd_lit; + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, key); + ADD_SEND(ret, line, rb_intern("key?"), INT2FIX(1)); + ADD_INSNL(ret, line, branchunless, match_failed); + + ADD_INSN(match_values, line, dup); + ADD_INSN1(match_values, line, putobject, key); + ADD_SEND(match_values, line, node->nd_pkwrestarg ? rb_intern("delete") : idAREF, INT2FIX(1)); + iseq_compile_pattern_each(iseq, match_values, value_node, in_alt_pattern); + ADD_INSNL(match_values, line, branchunless, match_failed); + args = args->nd_next->nd_next; + } + ADD_SEQ(ret, match_values); + } + } + else { + ADD_INSN(ret, line, dup); + ADD_SEND(ret, line, idEmptyP, INT2FIX(0)); + ADD_INSNL(ret, line, branchunless, match_failed); + } + + if (node->nd_pkwrestarg) { + ADD_INSN(ret, line, dup); + iseq_compile_pattern_each(iseq, ret, node->nd_pkwrestarg, in_alt_pattern); + ADD_INSNL(ret, line, branchunless, match_failed); + } + + ADD_INSN(ret, line, pop); + ADD_INSN1(ret, line, putobject, Qtrue); + ADD_INSNL(ret, line, jump, fin); + + ADD_LABEL(ret, type_error); + ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line, putobject, rb_eTypeError); + ADD_INSN1(ret, line, putobject, rb_fstring_lit("deconstruct_keys must return Hash")); + ADD_SEND(ret, line, id_core_raise, INT2FIX(2)); + + ADD_LABEL(ret, match_failed); + ADD_INSN(ret, line, pop); + ADD_INSN1(ret, line, putobject, Qfalse); + + ADD_LABEL(ret, fin); + break; + } + case NODE_LIT: + case NODE_STR: + case NODE_XSTR: + case NODE_DSTR: + case NODE_DSYM: + case NODE_DREGX: + case NODE_ARRAY: + case NODE_ZARRAY: + case NODE_LAMBDA: + case NODE_DOT2: + case NODE_DOT3: + case NODE_CONST: + case NODE_LVAR: + case NODE_DVAR: + case NODE_TRUE: + case NODE_FALSE: + case NODE_SELF: + case NODE_NIL: + case NODE_COLON2: + case NODE_COLON3: + CHECK(COMPILE(ret, "case in literal", node)); + ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + break; + case NODE_LASGN: { + struct rb_iseq_constant_body *const body = iseq->body; + ID id = node->nd_vid; + int idx = body->local_iseq->body->local_table_size - get_local_var_idx(iseq, id); + + if (in_alt_pattern) { + const char *name = rb_id2name(id); + if (name && strlen(name) > 0 && name[0] != '_') { + COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")", + rb_id2str(id)); + return COMPILE_NG; + } + } + + ADD_SETLOCAL(ret, line, idx, get_lvar_level(iseq)); + ADD_INSN1(ret, line, putobject, Qtrue); + break; + } + case NODE_DASGN: + case NODE_DASGN_CURR: { + int idx, lv, ls; + ID id = node->nd_vid; + + idx = get_dyna_var_idx(iseq, id, &lv, &ls); + + if (in_alt_pattern) { + const char *name = rb_id2name(id); + if (name && strlen(name) > 0 && name[0] != '_') { + COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")", + rb_id2str(id)); + return COMPILE_NG; + } + } + + if (idx < 0) { + COMPILE_ERROR(ERROR_ARGS "NODE_DASGN(_CURR): unknown id (%"PRIsVALUE")", + rb_id2str(id)); + return COMPILE_NG; + } + ADD_SETLOCAL(ret, line, ls - idx, lv); + ADD_INSN1(ret, line, putobject, Qtrue); + break; + } + case NODE_IF: + case NODE_UNLESS: { + LABEL *match_failed, *fin; + match_failed = NEW_LABEL(line); + fin = NEW_LABEL(line); + iseq_compile_pattern_each(iseq, ret, node->nd_body, in_alt_pattern); + ADD_INSNL(ret, line, branchunless, match_failed); + CHECK(COMPILE(ret, "case in if", node->nd_cond)); + if (nd_type(node) == NODE_IF) { + ADD_INSNL(ret, line, branchunless, match_failed); + } + else { + ADD_INSNL(ret, line, branchif, match_failed); + } + ADD_INSN1(ret, line, putobject, Qtrue); + ADD_INSNL(ret, line, jump, fin); + + ADD_LABEL(ret, match_failed); + ADD_INSN1(ret, line, putobject, Qfalse); + + ADD_LABEL(ret, fin); + break; + } + case NODE_HASH: { + NODE *n; + LABEL *match_failed, *fin; + match_failed = NEW_LABEL(line); + fin = NEW_LABEL(line); + + n = node->nd_head; + if (! (nd_type(n) == NODE_ARRAY && n->nd_alen == 2)) { + COMPILE_ERROR(ERROR_ARGS "unexpected node"); + return COMPILE_NG; + } + + ADD_INSN(ret, line, dup); + iseq_compile_pattern_each(iseq, ret, n->nd_head, in_alt_pattern); + ADD_INSNL(ret, line, branchunless, match_failed); + iseq_compile_pattern_each(iseq, ret, n->nd_next->nd_head, in_alt_pattern); + ADD_INSNL(ret, line, jump, fin); + + ADD_LABEL(ret, match_failed); + ADD_INSN(ret, line, pop); + ADD_INSN1(ret, line, putobject, Qfalse); + + ADD_LABEL(ret, fin); + break; + } + case NODE_OR: { + LABEL *match_succeeded, *fin; + match_succeeded = NEW_LABEL(line); + fin = NEW_LABEL(line); + + ADD_INSN(ret, line, dup); + iseq_compile_pattern_each(iseq, ret, node->nd_1st, TRUE); + ADD_INSNL(ret, line, branchif, match_succeeded); + iseq_compile_pattern_each(iseq, ret, node->nd_2nd, TRUE); + ADD_INSNL(ret, line, jump, fin); + + ADD_LABEL(ret, match_succeeded); + ADD_INSN(ret, line, pop); + ADD_INSN1(ret, line, putobject, Qtrue); + + ADD_LABEL(ret, fin); + break; + } + default: + UNKNOWN_NODE("NODE_IN", node, COMPILE_NG); + } + return COMPILE_OK; +} + +static int +compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_node, int popped) +{ + const NODE *pattern; + const NODE *node = orig_node; + LABEL *endlabel, *elselabel; + DECL_ANCHOR(head); + DECL_ANCHOR(body_seq); + DECL_ANCHOR(cond_seq); + int line, lineno, column, last_lineno, last_column; + enum node_type type; + VALUE branches = 0; + + INIT_ANCHOR(head); + INIT_ANCHOR(body_seq); + INIT_ANCHOR(cond_seq); + + CHECK(COMPILE(head, "case base", node->nd_head)); + + DECL_BRANCH_BASE(branches, nd_first_lineno(node), nd_first_column(node), nd_last_lineno(node), nd_last_column(node), "case"); + + node = node->nd_body; + EXPECT_NODE("NODE_CASE3", node, NODE_IN, COMPILE_NG); + type = nd_type(node); + line = nd_line(node); + lineno = nd_first_lineno(node); + column = nd_first_column(node); + last_lineno = nd_last_lineno(node); + last_column = nd_last_column(node); + + endlabel = NEW_LABEL(line); + elselabel = NEW_LABEL(line); + + ADD_SEQ(ret, head); /* case VAL */ + + while (type == NODE_IN) { + LABEL *l1; + + l1 = NEW_LABEL(line); + ADD_LABEL(body_seq, l1); + ADD_INSN(body_seq, line, pop); + ADD_TRACE_BRANCH_COVERAGE( + body_seq, + node->nd_body ? nd_first_lineno(node->nd_body) : lineno, + node->nd_body ? nd_first_column(node->nd_body) : column, + node->nd_body ? nd_last_lineno(node->nd_body) : last_lineno, + node->nd_body ? nd_last_column(node->nd_body) : last_column, + "in", + branches); + CHECK(COMPILE_(body_seq, "in body", node->nd_body, popped)); + ADD_INSNL(body_seq, line, jump, endlabel); + + pattern = node->nd_head; + if (pattern) { + ADD_INSN (cond_seq, nd_line(pattern), dup); + iseq_compile_pattern_each(iseq, cond_seq, pattern, FALSE); + ADD_INSNL(cond_seq, nd_line(pattern), branchif, l1); + } + else { + COMPILE_ERROR(ERROR_ARGS "unexpected node"); + return COMPILE_NG; + } + + node = node->nd_next; + if (!node) { + break; + } + type = nd_type(node); + line = nd_line(node); + lineno = nd_first_lineno(node); + column = nd_first_column(node); + last_lineno = nd_last_lineno(node); + last_column = nd_last_column(node); + } + /* else */ + if (node) { + ADD_LABEL(cond_seq, elselabel); + ADD_INSN(cond_seq, line, pop); + ADD_TRACE_BRANCH_COVERAGE(cond_seq, nd_first_lineno(node), nd_first_column(node), nd_last_lineno(node), nd_last_column(node), "else", branches); + CHECK(COMPILE_(cond_seq, "else", node, popped)); + ADD_INSNL(cond_seq, line, jump, endlabel); + } + else { + debugs("== else (implicit)\n"); + ADD_LABEL(cond_seq, elselabel); + ADD_TRACE_BRANCH_COVERAGE(cond_seq, nd_first_lineno(orig_node), nd_first_column(orig_node), nd_last_lineno(orig_node), nd_last_column(orig_node), "else", branches); + ADD_INSN1(cond_seq, nd_line(orig_node), putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(cond_seq, nd_line(orig_node), putobject, rb_eNoMatchingPatternError); + ADD_INSN1(cond_seq, nd_line(orig_node), topn, INT2FIX(2)); + ADD_SEND(cond_seq, nd_line(orig_node), id_core_raise, INT2FIX(2)); + ADD_INSN(cond_seq, nd_line(orig_node), pop); + ADD_INSN(cond_seq, nd_line(orig_node), pop); + if (!popped) { + ADD_INSN(cond_seq, nd_line(orig_node), putnil); + } + ADD_INSNL(cond_seq, nd_line(orig_node), jump, endlabel); + } + + ADD_SEQ(ret, cond_seq); + ADD_SEQ(ret, body_seq); + ADD_LABEL(ret, endlabel); + return COMPILE_OK; +} + +static int compile_loop(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped, const enum node_type type) { const int line = (int)nd_line(node); @@ -6153,6 +6740,9 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, in case NODE_CASE2: CHECK(compile_case2(iseq, ret, node, popped)); break; + case NODE_CASE3: + CHECK(compile_case3(iseq, ret, node, popped)); + break; case NODE_WHILE: case NODE_UNTIL: CHECK(compile_loop(iseq, ret, node, popped, type)); diff --git a/defs/id.def b/defs/id.def index 24ca5d3f9f..44890cf352 100644 --- a/defs/id.def +++ b/defs/id.def @@ -65,6 +65,7 @@ firstline, predefined = __LINE__+1, %[\ core#set_postexe core#hash_merge_ptr core#hash_merge_kwd + core#raise - debug#created_info @@ -871,6 +871,7 @@ VALUE rb_eSecurityError; VALUE rb_eNotImpError; VALUE rb_eNoMemError; VALUE rb_cNameErrorMesg; +VALUE rb_eNoMatchingPatternError; VALUE rb_eScriptError; VALUE rb_eSyntaxError; @@ -2486,6 +2487,7 @@ Init_Exception(void) rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException); rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError); rb_eEncCompatError = rb_define_class_under(rb_cEncoding, "CompatibilityError", rb_eEncodingError); + rb_eNoMatchingPatternError = rb_define_class("NoMatchingPatternError", rb_eRuntimeError); syserr_tbl = st_init_numtable(); rb_eSystemCallError = rb_define_class("SystemCallError", rb_eStandardError); @@ -733,7 +733,7 @@ extract_raise_opts(int argc, const VALUE *argv, VALUE *opts) * the +:cause+ argument. */ -static VALUE +VALUE rb_f_raise(int argc, VALUE *argv) { VALUE err; diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 7cef5d42a3..950168868e 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -377,7 +377,9 @@ count_nodes(int argc, VALUE *argv, VALUE os) COUNT_NODE(NODE_UNLESS); COUNT_NODE(NODE_CASE); COUNT_NODE(NODE_CASE2); + COUNT_NODE(NODE_CASE3); COUNT_NODE(NODE_WHEN); + COUNT_NODE(NODE_IN); COUNT_NODE(NODE_WHILE); COUNT_NODE(NODE_UNTIL); COUNT_NODE(NODE_ITER); @@ -472,6 +474,8 @@ count_nodes(int argc, VALUE *argv, VALUE os) COUNT_NODE(NODE_ATTRASGN); COUNT_NODE(NODE_LAMBDA); COUNT_NODE(NODE_METHREF); + COUNT_NODE(NODE_ARYPTN); + COUNT_NODE(NODE_HSHPTN); #undef COUNT_NODE case NODE_LAST: break; } diff --git a/ext/ripper/tools/dsl.rb b/ext/ripper/tools/dsl.rb index aafaa6f407..34b435e7c3 100644 --- a/ext/ripper/tools/dsl.rb +++ b/ext/ripper/tools/dsl.rb @@ -72,7 +72,7 @@ class DSL def method_missing(event, *args) if event.to_s =~ /!\z/ add_event(event, args) - elsif args.empty? and /\Aid[A-Z]/ =~ event.to_s + elsif args.empty? and /\Aid[A-Z_]/ =~ event.to_s event else "#{ event }(#{ args.join(", ") })" @@ -4349,6 +4349,12 @@ rb_hash_to_proc(VALUE hash) return rb_func_proc_new(hash_proc_call, hash); } +static VALUE +rb_hash_deconstruct_keys(VALUE hash, VALUE keys) +{ + return hash; +} + static int add_new_i(st_data_t *key, st_data_t *val, st_data_t arg, int existing) { @@ -6020,6 +6026,8 @@ Init_Hash(void) rb_define_method(rb_cHash, ">=", rb_hash_ge, 1); rb_define_method(rb_cHash, ">", rb_hash_gt, 1); + rb_define_method(rb_cHash, "deconstruct_keys", rb_hash_deconstruct_keys, 1); + /* Document-class: ENV * * ENV is a hash-like accessor for environment variables. diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index c0be6c1a50..c73fd63941 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -2053,6 +2053,7 @@ RUBY_EXTERN VALUE rb_eSysStackError; RUBY_EXTERN VALUE rb_eRegexpError; RUBY_EXTERN VALUE rb_eEncodingError; RUBY_EXTERN VALUE rb_eEncCompatError; +RUBY_EXTERN VALUE rb_eNoMatchingPatternError; RUBY_EXTERN VALUE rb_eScriptError; RUBY_EXTERN VALUE rb_eNameError; diff --git a/internal.h b/internal.h index 46a25ff77f..17bd7f1fe9 100644 --- a/internal.h +++ b/internal.h @@ -1456,6 +1456,7 @@ extern ID ruby_static_id_signo, ruby_static_id_status; void rb_class_modify_check(VALUE); #define id_signo ruby_static_id_signo #define id_status ruby_static_id_status +NORETURN(VALUE rb_f_raise(int argc, VALUE *argv)); /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); @@ -537,6 +537,10 @@ class RubyVM::AbstractSyntaxTree::Node pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block]) when :DEFN pretty_print_children(q, %w[mid body]) + when :ARYPTN + pretty_print_children(q, %w[const pre rest post]) + when :HSHPTN + pretty_print_children(q, %w[const kw kwrest]) else pretty_print_children(q) end @@ -195,6 +195,14 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) LAST_NODE; F_NODE(nd_body, "when clauses"); return; + case NODE_CASE3: + ANN("case statement (pattern matching)"); + ANN("format: case [nd_head]; [nd_body]; end"); + ANN("example: case x; in 1; foo; in 2; bar; else baz; end"); + F_NODE(nd_head, "case expr"); + LAST_NODE; + F_NODE(nd_body, "in clauses"); + return; case NODE_WHEN: ANN("when clause"); @@ -206,6 +214,16 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) F_NODE(nd_next, "next when clause"); return; + case NODE_IN: + ANN("in clause"); + ANN("format: in [nd_head]; [nd_body]; (when or else) [nd_next]"); + ANN("example: case x; in 1; foo; in 2; bar; else baz; end"); + F_NODE(nd_head, "in value"); + F_NODE(nd_body, "in body"); + LAST_NODE; + F_NODE(nd_next, "next in clause"); + return; + case NODE_WHILE: ANN("while statement"); ANN("format: while [nd_cond]; [nd_body]; end"); @@ -1017,6 +1035,25 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) F_NODE(nd_body, "body"); return; + case NODE_ARYPTN: + ANN("array pattern"); + ANN("format: [nd_pconst]([pre_args], ..., *[rest_arg], [post_args], ...)"); + F_NODE(nd_pconst, "constant"); + F_NODE(nd_apinfo->pre_args, "pre arguments"); + F_NODE(nd_apinfo->rest_arg, "rest argument"); + LAST_NODE; + F_NODE(nd_apinfo->post_args, "post arguments"); + return; + + case NODE_HSHPTN: + ANN("hash pattern"); + ANN("format: [nd_pconst]([nd_pkwargs], ..., **[nd_pkwrestarg])"); + F_NODE(nd_pconst, "constant"); + F_NODE(nd_pkwargs, "keyword arguments"); + LAST_NODE; + F_NODE(nd_pkwrestarg, "keyword rest argument"); + return; + case NODE_ARGS_AUX: case NODE_LAST: break; @@ -26,7 +26,9 @@ enum node_type { NODE_UNLESS, NODE_CASE, NODE_CASE2, + NODE_CASE3, NODE_WHEN, + NODE_IN, NODE_WHILE, NODE_UNTIL, NODE_ITER, @@ -121,6 +123,8 @@ enum node_type { NODE_ATTRASGN, NODE_LAMBDA, NODE_METHREF, + NODE_ARYPTN, + NODE_HSHPTN, NODE_LAST }; @@ -162,6 +166,7 @@ typedef struct RNode { long state; struct rb_global_entry *entry; struct rb_args_info *args; + struct rb_ary_pattern_info *apinfo; VALUE value; } u3; rb_code_location_t nd_loc; @@ -268,6 +273,12 @@ typedef struct RNode { #define nd_brace u2.argc +#define nd_pconst u1.node +#define nd_pkwargs u2.node +#define nd_pkwrestarg u3.node + +#define nd_apinfo u3.apinfo + #define NEW_NODE(t,a0,a1,a2,loc) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2),loc) #define NEW_DEFN(i,a,d,loc) NEW_NODE(NODE_DEFN,0,i,NEW_SCOPE(a,d,loc),loc) @@ -278,7 +289,9 @@ typedef struct RNode { #define NEW_UNLESS(c,t,e,loc) NEW_NODE(NODE_UNLESS,c,t,e,loc) #define NEW_CASE(h,b,loc) NEW_NODE(NODE_CASE,h,b,0,loc) #define NEW_CASE2(b,loc) NEW_NODE(NODE_CASE2,0,b,0,loc) +#define NEW_CASE3(h,b,loc) NEW_NODE(NODE_CASE3,h,b,0,loc) #define NEW_WHEN(c,t,e,loc) NEW_NODE(NODE_WHEN,c,t,e,loc) +#define NEW_IN(c,t,e,loc) NEW_NODE(NODE_IN,c,t,e,loc) #define NEW_WHILE(c,b,n,loc) NEW_NODE(NODE_WHILE,c,b,n,loc) #define NEW_UNTIL(c,b,n,loc) NEW_NODE(NODE_UNTIL,c,b,n,loc) #define NEW_FOR(i,b,loc) NEW_NODE(NODE_FOR,0,b,i,loc) @@ -434,6 +447,12 @@ struct rb_args_info { NODE *opt_args; }; +struct rb_ary_pattern_info { + NODE *pre_args; + NODE *rest_arg; + NODE *post_args; +}; + struct parser_params; void *rb_parser_malloc(struct parser_params *, size_t); void *rb_parser_realloc(struct parser_params *, void *, size_t); @@ -417,6 +417,11 @@ static NODE *method_add_block(struct parser_params*p, NODE *m, NODE *b, const YY static bool args_info_empty_p(struct rb_args_info *args); static NODE *new_args(struct parser_params*,NODE*,NODE*,ID,NODE*,NODE*,const YYLTYPE*); static NODE *new_args_tail(struct parser_params*,NODE*,ID,ID,const YYLTYPE*); +static NODE *new_array_pattern(struct parser_params *p, NODE *constant, NODE *pre_arg, NODE *aryptn, const YYLTYPE *loc); +static NODE *new_array_pattern_tail(struct parser_params *p, NODE *pre_args, int has_rest, ID rest_arg, NODE *post_args, const YYLTYPE *loc); +static NODE *new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc); +static NODE *new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc); + static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc); static NODE *args_with_numbered(struct parser_params*,NODE*,int); @@ -447,6 +452,7 @@ static NODE *opt_arg_append(NODE*, NODE*); static NODE *kwd_append(NODE*, NODE*); static NODE *new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc); +static NODE *new_unique_key_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc); static NODE *new_defined(struct parser_params *p, NODE *expr, const YYLTYPE *loc); @@ -703,6 +709,105 @@ args_with_numbered(struct parser_params *p, VALUE args, int max_numparam) return args; } +static VALUE +new_array_pattern(struct parser_params *p, VALUE constant, VALUE pre_arg, VALUE aryptn, const YYLTYPE *loc) +{ + NODE *t = (NODE *)aryptn; + VALUE pre_args = t->u1.value, rest_arg = t->u2.value, post_args = t->u3.value; + if (!NIL_P(pre_arg)) { + if (!NIL_P(pre_args)) { + rb_ary_unshift(pre_args, pre_arg); + } + else { + pre_args = rb_ary_new_from_args(1, pre_arg); + } + } + return dispatch4(aryptn, constant, pre_args, rest_arg, post_args); +} + +static VALUE +new_array_pattern_tail(struct parser_params *p, VALUE pre_args, VALUE has_rest, VALUE rest_arg, VALUE post_args, const YYLTYPE *loc) +{ + NODE *t; + if (has_rest) { + rest_arg = dispatch1(var_field, rest_arg ? rest_arg : Qnil); + } else { + rest_arg = Qnil; + } + t = rb_node_newnode(NODE_ARYPTN, pre_args, rest_arg, post_args, &NULL_LOC); + + add_mark_object(p, pre_args); + add_mark_object(p, rest_arg); + add_mark_object(p, post_args); + return (VALUE)t; +} + +#define new_hash(p,h,l) rb_ary_new_from_args(0) + +static VALUE +new_unique_key_hash(struct parser_params *p, VALUE ary, const YYLTYPE *loc) +{ + const long len = RARRAY_LEN(ary); + st_table *tbl; + long i; + + tbl = st_init_strtable_with_size(len); + for (i = 0; i < len; i++) { + VALUE key, a1, a2, a3; + a1 = RARRAY_AREF(ary, i); + if (!(RB_TYPE_P(a1, T_ARRAY) && RARRAY_LEN(a1) == 2)) goto error; + a2 = RARRAY_AREF(a1, 0); + if (!RB_TYPE_P(a2, T_ARRAY)) goto error; + switch (RARRAY_LEN(a2)) { + case 2: /* "key": */ + a3 = RARRAY_AREF(a2, 1); + if (!(RB_TYPE_P(a3, T_ARRAY) && RARRAY_LEN(a3) == 3)) goto error; + key = RARRAY_AREF(a3, 1); + break; + case 3: /* key: */ + key = RARRAY_AREF(a2, 1); + break; + default: + goto error; + } + if (!RB_TYPE_P(key, T_STRING)) goto error; + if (st_lookup(tbl, (st_data_t)RSTRING_PTR(key), 0)) goto error; + st_insert(tbl, (st_data_t)RSTRING_PTR(key), (st_data_t)ary); + } + st_free_table(tbl); + return ary; + + error: + ripper_error(p); + st_free_table(tbl); + return Qnil; +} + +static VALUE +new_hash_pattern(struct parser_params *p, VALUE constant, VALUE hshptn, const YYLTYPE *loc) +{ + NODE *t = (NODE *)hshptn; + VALUE kw_args = t->u1.value, kw_rest_arg = t->u2.value; + return dispatch3(hshptn, constant, kw_args, kw_rest_arg); +} + +static VALUE +new_hash_pattern_tail(struct parser_params *p, VALUE kw_args, VALUE kw_rest_arg, const YYLTYPE *loc) +{ + NODE *t; + if (kw_rest_arg) { + kw_rest_arg = dispatch1(var_field, kw_rest_arg); + } + else { + kw_rest_arg = Qnil; + } + t = rb_node_newnode(NODE_HSHPTN, kw_args, kw_rest_arg, 0, &NULL_LOC); + + add_mark_object(p, kw_args); + add_mark_object(p, kw_rest_arg); + return (VALUE)t; +} + #define new_defined(p,expr,loc) dispatch1(defined, (expr)) static VALUE heredoc_dedent(struct parser_params*,VALUE); @@ -744,7 +849,7 @@ static VALUE heredoc_dedent(struct parser_params*,VALUE); # define rb_warning3L(l,fmt,a,b,c) WARNING_CALL(WARNING_ARGS_L(l, fmt, 4), (a), (b), (c)) # define rb_warning4L(l,fmt,a,b,c,d) WARNING_CALL(WARNING_ARGS_L(l, fmt, 5), (a), (b), (c), (d)) #ifdef RIPPER -static ID id_warn, id_warning, id_gets; +static ID id_warn, id_warning, id_gets, id_assoc, id_or; # define WARN_S_L(s,l) STR_NEW(s,l) # define WARN_S(s) STR_NEW2(s) # define WARN_I(i) INT2NUM(i) @@ -895,9 +1000,15 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in %type <node> lambda f_larglist lambda_body brace_body do_body %type <node> brace_block cmd_brace_block do_block lhs none fitem %type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner +%type <node> p_case_body p_cases p_top_expr p_top_expr_body +%type <node> p_expr p_as p_alt p_expr_basic +%type <node> p_args p_args_head p_args_tail p_args_post p_arg +%type <node> p_value p_primitive p_variable p_var_ref p_const +%type <node> p_kwargs p_kwarg p_kw %type <id> keyword_variable user_variable sym operation operation2 operation3 %type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg %type <id> f_kwrest f_label f_arg_asgn call_op call_op2 reswords relop dot_or_colon +%type <id> p_kwrest %token END_OF_INPUT 0 "end-of-input" %token <id> '.' /* escaped chars, should be ignored otherwise */ @@ -2592,6 +2703,16 @@ primary : literal /*% %*/ /*% ripper: case!(Qnil, $3) %*/ } + | k_case expr_value opt_terms + p_case_body + k_end + { + /*%%%*/ + $$ = NEW_CASE3($2, $4, &@$); + rb_warn0L(nd_line($$), "Pattern matching is experimental, and the behavior may change in future versions of Ruby!"); + /*% %*/ + /*% ripper: case!($2, $4) %*/ + } | k_for for_var keyword_in expr_value_do compstmt k_end @@ -3467,6 +3588,489 @@ cases : opt_else | case_body ; +p_case_body : keyword_in + { + SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); + p->command_start = FALSE; + $<num>$ = p->in_kwarg; + p->in_kwarg = 1; + } + p_top_expr then + { + p->in_kwarg = !!$<num>2; + } + compstmt + p_cases + { + /*%%%*/ + $$ = NEW_IN($3, $6, $7, &@$); + /*% %*/ + /*% ripper: in!($3, $6, escape_Qundef($7)) %*/ + } + ; + +p_cases : opt_else + | p_case_body + ; + +p_top_expr : p_top_expr_body + | p_top_expr_body modifier_if expr_value + { + /*%%%*/ + $$ = new_if(p, $3, remove_begin($1), 0, &@$); + fixpos($$, $3); + /*% %*/ + /*% ripper: if_mod!($3, $1) %*/ + } + | p_top_expr_body modifier_unless expr_value + { + /*%%%*/ + $$ = new_unless(p, $3, remove_begin($1), 0, &@$); + fixpos($$, $3); + /*% %*/ + /*% ripper: unless_mod!($3, $1) %*/ + } + ; + +p_top_expr_body : p_expr + | p_expr ',' + { + $$ = new_array_pattern_tail(p, Qnone, 1, 0, Qnone, &@$); + $$ = new_array_pattern(p, Qnone, get_value($1), $$, &@$); + } + | p_expr ',' p_args + { + $$ = new_array_pattern(p, Qnone, get_value($1), $3, &@$); + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% + %*/ + } + | p_args_tail + { + $$ = new_array_pattern(p, Qnone, Qnone, $1, &@$); + } + | p_kwargs + { + $$ = new_hash_pattern(p, Qnone, $1, &@$); + } + ; + +p_expr : p_as + ; + +p_as : p_expr tASSOC p_variable + { + /*%%%*/ + NODE *n = NEW_LIST($1, &@$); + n = list_append(p, n, $3); + $$ = new_hash(p, n, &@$); + /*% %*/ + /*% ripper: binary!($1, STATIC_ID2SYM((id_assoc)), $3) %*/ + } + | p_alt + ; + +p_alt : p_alt '|' p_expr_basic + { + /*%%%*/ + $$ = NEW_NODE(NODE_OR, $1, $3, 0, &@$); + /*% %*/ + /*% ripper: binary!($1, STATIC_ID2SYM((id_or)), $3) %*/ + } + | p_expr_basic + ; + +p_expr_basic : p_value + | p_const '(' p_args rparen + { + $$ = new_array_pattern(p, $1, Qnone, $3, &@$); + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% + %*/ + } + | p_const '(' p_kwargs rparen + { + $$ = new_hash_pattern(p, $1, $3, &@$); + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% + %*/ + } + | p_const '(' rparen + { + $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$); + $$ = new_array_pattern(p, $1, Qnone, $$, &@$); + } + | p_const '[' p_args rbracket + { + $$ = new_array_pattern(p, $1, Qnone, $3, &@$); + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% + %*/ + } + | p_const '[' p_kwargs rbracket + { + $$ = new_hash_pattern(p, $1, $3, &@$); + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% + %*/ + } + | p_const '[' rbracket + { + $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$); + $$ = new_array_pattern(p, $1, Qnone, $$, &@$); + } + | tLBRACK p_args rbracket + { + $$ = new_array_pattern(p, Qnone, Qnone, $2, &@$); + } + | tLBRACK rbracket + { + $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$); + $$ = new_array_pattern(p, Qnone, Qnone, $$, &@$); + } + | tLBRACE p_kwargs '}' + { + $$ = new_hash_pattern(p, Qnone, $2, &@$); + } + | tLBRACE '}' + { + $$ = new_hash_pattern_tail(p, Qnone, 0, &@$); + $$ = new_hash_pattern(p, Qnone, $$, &@$); + } + | tLPAREN p_expr rparen + { + $$ = $2; + } + ; + +p_args : p_expr + { + /*%%%*/ + NODE *pre_args = NEW_LIST($1, &@$); + $$ = new_array_pattern_tail(p, pre_args, 0, 0, Qnone, &@$); + /*% + $$ = new_array_pattern_tail(p, rb_ary_new_from_args(1, get_value($1)), 0, 0, Qnone, &@$); + %*/ + } + | p_args_head + { + $$ = new_array_pattern_tail(p, $1, 1, 0, Qnone, &@$); + } + | p_args_head p_arg + { + /*%%%*/ + $$ = new_array_pattern_tail(p, list_concat($1, $2), 0, 0, Qnone, &@$); + /*% + VALUE pre_args = rb_ary_concat($1, get_value($2)); + $$ = new_array_pattern_tail(p, pre_args, 0, 0, Qnone, &@$); + %*/ + } + | p_args_head tSTAR tIDENTIFIER + { + $$ = new_array_pattern_tail(p, $1, 1, $3, Qnone, &@$); + } + | p_args_head tSTAR tIDENTIFIER ',' p_args_post + { + $$ = new_array_pattern_tail(p, $1, 1, $3, $5, &@$); + } + | p_args_head tSTAR + { + $$ = new_array_pattern_tail(p, $1, 1, 0, Qnone, &@$); + } + | p_args_head tSTAR ',' p_args_post + { + $$ = new_array_pattern_tail(p, $1, 1, 0, $4, &@$); + } + | p_args_tail + ; + +p_args_head : p_arg ',' + { + $$ = $1; + } + | p_args_head p_arg ',' + { + /*%%%*/ + $$ = list_concat($1, $2); + /*% %*/ + /*% ripper: rb_ary_concat($1, get_value($2)) %*/ + } + ; + +p_args_tail : tSTAR tIDENTIFIER + { + $$ = new_array_pattern_tail(p, Qnone, 1, $2, Qnone, &@$); + } + | tSTAR tIDENTIFIER ',' p_args_post + { + $$ = new_array_pattern_tail(p, Qnone, 1, $2, $4, &@$); + } + | tSTAR + { + $$ = new_array_pattern_tail(p, Qnone, 1, 0, Qnone, &@$); + } + | tSTAR ',' p_args_post + { + $$ = new_array_pattern_tail(p, Qnone, 1, 0, $3, &@$); + } + +p_args_post : p_arg + | p_args_post ',' p_arg + { + /*%%%*/ + $$ = list_concat($1, $3); + /*% %*/ + /*% ripper: rb_ary_concat($1, get_value($3)) %*/ + } + ; + +p_arg : p_expr + { + /*%%%*/ + $$ = NEW_LIST($1, &@$); + /*% %*/ + /*% ripper: rb_ary_new_from_args(1, get_value($1)) %*/ + } + ; + +p_kwargs : p_kwarg ',' p_kwrest + { + $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), $3, &@$); + } + | p_kwarg + { + $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), 0, &@$); + } + | p_kwrest + { + $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), $1, &@$); + } + ; + +p_kwarg : p_kw + | p_kwarg ',' p_kw + { + /*%%%*/ + $$ = list_concat($1, $3); + /*% %*/ + /*% ripper: rb_ary_concat($1, $3) %*/ + } + ; + +p_kw : tLABEL p_expr + { + /*%%%*/ + $$ = list_append(p, NEW_LIST(NEW_LIT(ID2SYM($1), &@$), &@$), $2); + /*% %*/ + /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, get_value($1), get_value($2))) %*/ + } + | tLABEL + { + if (!is_local_id(get_id($1))) { + yyerror1(&@1, "key must be valid as local variables"); + } + /*%%%*/ + $$ = list_append(p, NEW_LIST(NEW_LIT(ID2SYM($1), &@$), &@$), assignable(p, $1, 0, &@$)); + /*% %*/ + /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, get_value($1), Qnil)) %*/ + } + | tSTRING_BEG string_contents tLABEL_END p_expr + { + /*%%%*/ + YYLTYPE loc = code_loc_gen(&@1, &@3); + NODE *node = dsym_node(p, $2, &loc); + if (nd_type(node) == NODE_LIT) { + $$ = list_append(p, NEW_LIST(node, &loc), $4); + } + else { + yyerror1(&loc, "symbol literal with interpolation is not allowed"); + $$ = 0; + } + /*% %*/ + /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, $2, get_value($4))) %*/ + } + | tSTRING_BEG string_contents tLABEL_END + { + /*%%%*/ + YYLTYPE loc = code_loc_gen(&@1, &@3); + NODE *node = dsym_node(p, $2, &loc); + ID id; + if (nd_type(node) == NODE_LIT) { + id = SYM2ID(node->nd_lit); + if (!is_local_id(id)) { + yyerror1(&loc, "key must be valid as local variables"); + } + $$ = list_append(p, NEW_LIST(node, &loc), assignable(p, id, 0, &@$)); + } + else { + yyerror1(&loc, "symbol literal with interpolation is not allowed"); + $$ = 0; + } + /*% %*/ + /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, $2, Qnil)) %*/ + } + ; + +p_kwrest : kwrest_mark tIDENTIFIER + { + $$ = $2; + } + | kwrest_mark + { + $$ = 0; + } + ; + +p_value : p_primitive + | p_primitive tDOT2 p_primitive + { + /*%%%*/ + value_expr($1); + value_expr($3); + $$ = NEW_DOT2($1, $3, &@$); + /*% %*/ + /*% ripper: dot2!($1, $3) %*/ + } + | p_primitive tDOT3 p_primitive + { + /*%%%*/ + value_expr($1); + value_expr($3); + $$ = NEW_DOT3($1, $3, &@$); + /*% %*/ + /*% ripper: dot3!($1, $3) %*/ + } + | p_primitive tDOT2 + { + /*%%%*/ + YYLTYPE loc; + loc.beg_pos = @2.end_pos; + loc.end_pos = @2.end_pos; + + value_expr($1); + $$ = NEW_DOT2($1, new_nil(&loc), &@$); + /*% %*/ + /*% ripper: dot2!($1, Qnil) %*/ + } + | p_primitive tDOT3 + { + /*%%%*/ + YYLTYPE loc; + loc.beg_pos = @2.end_pos; + loc.end_pos = @2.end_pos; + + value_expr($1); + $$ = NEW_DOT3($1, new_nil(&loc), &@$); + /*% %*/ + /*% ripper: dot3!($1, Qnil) %*/ + } + | p_variable + | p_var_ref + | p_const + | tBDOT2 p_primitive + { + /*%%%*/ + YYLTYPE loc; + loc.beg_pos = @1.beg_pos; + loc.end_pos = @1.beg_pos; + + value_expr($2); + $$ = NEW_DOT2(new_nil(&loc), $2, &@$); + /*% %*/ + /*% ripper: dot2!(Qnil, $2) %*/ + } + | tBDOT3 p_primitive + { + /*%%%*/ + YYLTYPE loc; + loc.beg_pos = @1.beg_pos; + loc.end_pos = @1.beg_pos; + + value_expr($2); + $$ = NEW_DOT3(new_nil(&loc), $2, &@$); + /*% %*/ + /*% ripper: dot3!(Qnil, $2) %*/ + } + ; + +p_primitive : literal + | strings + | xstring + | regexp + | words + | qwords + | symbols + | qsymbols + | keyword_variable + { + /*%%%*/ + if (!($$ = gettable(p, $1, &@$))) $$ = NEW_BEGIN(0, &@$); + /*% %*/ + /*% ripper: var_ref!($1) %*/ + } + | tLAMBDA + { + token_info_push(p, "->", &@1); + } + lambda + { + $$ = $3; + /*%%%*/ + nd_set_first_loc($$, @1.beg_pos); + /*% %*/ + } + ; + +p_variable : tIDENTIFIER + { + /*%%%*/ + $$ = assignable(p, $1, 0, &@$); + /*% %*/ + /*% ripper: assignable(p, var_field(p, $1)) %*/ + } + ; + +p_var_ref : '^' tIDENTIFIER + { + /*%%%*/ + NODE *n = gettable(p, $2, &@$); + if (!(nd_type(n) == NODE_LVAR || nd_type(n) == NODE_DVAR)) { + compile_error(p, "%"PRIsVALUE": no such local variable", rb_id2str($2)); + } + $$ = n; + /*% %*/ + /*% ripper: var_ref!($2) %*/ + } + ; + +p_const : tCOLON3 cname + { + /*%%%*/ + $$ = NEW_COLON3($2, &@$); + /*% %*/ + /*% ripper: top_const_ref!($2) %*/ + } + | p_const tCOLON2 cname + { + /*%%%*/ + $$ = NEW_COLON2($1, $3, &@$); + /*% %*/ + /*% ripper: const_path_ref!($1, $3) %*/ + } + | tCONSTANT + { + /*%%%*/ + $$ = gettable(p, $1, &@$); + /*% %*/ + /*% ripper: var_ref!($1) %*/ + } + ; + opt_rescue : k_rescue exc_list exc_var then compstmt opt_rescue @@ -10398,6 +11002,80 @@ rb_parser_numparam_id(struct parser_params *p, int idx) } static NODE* +new_array_pattern(struct parser_params *p, NODE *constant, NODE *pre_arg, NODE *aryptn, const YYLTYPE *loc) +{ + struct rb_ary_pattern_info *apinfo = aryptn->nd_apinfo; + + aryptn->nd_pconst = constant; + + if (pre_arg) { + NODE *pre_args = NEW_LIST(pre_arg, loc); + if (apinfo->pre_args) { + apinfo->pre_args = list_concat(pre_args, apinfo->pre_args); + } else { + apinfo->pre_args = pre_args; + } + } + return aryptn; +} + +static NODE* +new_array_pattern_tail(struct parser_params *p, NODE *pre_args, int has_rest, ID rest_arg, NODE *post_args, const YYLTYPE *loc) +{ + int saved_line = p->ruby_sourceline; + struct rb_ary_pattern_info *apinfo; + NODE *node; + rb_imemo_tmpbuf_t *tmpbuf = new_tmpbuf(); + + apinfo = ZALLOC(struct rb_ary_pattern_info); + tmpbuf->ptr = (VALUE *)apinfo; + node = NEW_NODE(NODE_ARYPTN, 0, 0, apinfo, loc); + + apinfo->pre_args = pre_args; + + if (has_rest) { + if (rest_arg) { + apinfo->rest_arg = assignable(p, rest_arg, 0, loc); + } else { + apinfo->rest_arg = NEW_BEGIN(0, loc); + } + } else { + apinfo->rest_arg = NULL; + } + + apinfo->post_args = post_args; + + p->ruby_sourceline = saved_line; + return node; +} + +static NODE* +new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc) +{ + hshptn->nd_pconst = constant; + return hshptn; +} + +static NODE* +new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc) +{ + int saved_line = p->ruby_sourceline; + NODE *node, *kw_rest_arg_node; + + if (kw_rest_arg) { + kw_rest_arg_node = assignable(p, kw_rest_arg, 0, loc); + } + else { + kw_rest_arg_node = NULL; + } + + node = NEW_NODE(NODE_HSHPTN, 0, kw_args, kw_rest_arg_node, loc); + + p->ruby_sourceline = saved_line; + return node; +} + +static NODE* dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) { VALUE lit; @@ -10478,6 +11156,38 @@ new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc) if (hash) hash = remove_duplicate_keys(p, hash); return NEW_HASH(hash, loc); } + +static void +error_duplicate_keys(struct parser_params *p, NODE *hash) +{ + st_table *literal_keys = st_init_numtable_with_size(hash->nd_alen / 2); + while (hash && hash->nd_head && hash->nd_next) { + NODE *head = hash->nd_head; + NODE *next = hash->nd_next->nd_next; + VALUE key = (VALUE)head; + if (nd_type(head) != NODE_LIT) { + yyerror1(&head->nd_loc, "key must be symbol literal"); + } + if (st_lookup(literal_keys, (key = head->nd_lit), 0)) { + yyerror1(&head->nd_loc, "duplicated key name"); + } + else { + st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); + } + hash = next; + } + st_free_table(literal_keys); + return; +} + +static NODE * +new_unique_key_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc) +{ + if (hash) { + error_duplicate_keys(p, hash); + } + return NEW_HASH(hash, loc); +} #endif /* !RIPPER */ #ifndef RIPPER @@ -11933,6 +12643,8 @@ Init_ripper(void) id_warn = rb_intern_const("warn"); id_warning = rb_intern_const("warning"); id_gets = rb_intern_const("gets"); + id_assoc = rb_intern_const("=>"); + id_or = rb_intern_const("|"); (void)yystpcpy; /* may not used in newer bison */ diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb index 30523c341a..f88e97e9d6 100644 --- a/test/coverage/test_coverage.rb +++ b/test/coverage/test_coverage.rb @@ -359,6 +359,38 @@ class TestCoverage < Test::Unit::TestCase end; end + def test_branch_coverage_for_pattern_matching + result = { + :branches=> { + [:case, 0, 3, 4, 8, 7] => {[:in, 1, 5, 6, 5, 7]=>2, [:in, 2, 7, 6, 7, 7]=>0, [:else, 3, 3, 4, 8, 7]=>1}, + [:case, 4, 12, 2, 17, 5] => {[:in, 5, 14, 4, 14, 5]=>2, [:else, 6, 16, 4, 16, 5]=>1}}, + } + assert_coverage(<<~"end;", { branches: true }, result) + def foo(x) + begin + case x + in 0 + 0 + in 1 + 1 + end + rescue NoMatchingPatternError + end + + case x + in 0 + 0 + else + 1 + end + end + + foo(0) + foo(0) + foo(2) + end; + end + def test_branch_coverage_for_safe_method_invocation result = { :branches=>{ diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index be46ad917d..1d2c501c3b 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -1509,4 +1509,22 @@ class TestRipper::ParserEvents < Test::Unit::TestCase assert_warn("") {fmt, = warn("\r;")} assert_match(/encountered/, fmt) end + + def test_in + thru_in = false + parse('case 0; in 0; end', :on_in) {thru_in = true} + assert_equal true, thru_in + end + + def test_aryptn + thru_aryptn = false + parse('case 0; in [0]; end', :on_aryptn) {thru_aryptn = true} + assert_equal true, thru_aryptn + end + + def test_hshptn + thru_hshptn = false + parse('case 0; in {a:}; end', :on_hshptn) {thru_hshptn = true} + assert_equal true, thru_hshptn + end end if ripper_test diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index d63464d5a7..93e40f3bd7 100644 --- a/test/ripper/test_sexp.rb +++ b/test/ripper/test_sexp.rb @@ -140,4 +140,306 @@ eot s, bug15670) end + + pattern_matching_data = { + %q{ case 0; in 0; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:@int, "0", [1, 11]], [[:void_stmt]], nil]], + + %q{ case 0; in 0 if a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:if_mod, [:vcall, [:@ident, "a", [1, 16]]], [:@int, "0", [1, 11]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in 0 unless a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:unless_mod, [:vcall, [:@ident, "a", [1, 20]]], [:@int, "0", [1, 11]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:var_field, [:@ident, "a", [1, 11]]], [[:void_stmt]], nil]], + + %q{ case 0; in a,; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + nil, + [[:var_field, [:@ident, "a", [1, 11]]]], + [:var_field, nil], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in a,b; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + nil, + [[:var_field, [:@ident, "a", [1, 11]]], + [:var_field, [:@ident, "b", [1, 13]]]], + nil, + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in *a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, nil, nil, [:var_field, [:@ident, "a", [1, 12]]], nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in *a,b; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + nil, + nil, + [:var_field, [:@ident, "a", [1, 12]]], + [[:var_field, [:@ident, "b", [1, 14]]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in *a,b,c; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + nil, + nil, + [:var_field, [:@ident, "a", [1, 12]]], + [[:var_field, [:@ident, "b", [1, 14]]], + [:var_field, [:@ident, "c", [1, 16]]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in *; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:aryptn, nil, nil, [:var_field, nil], nil], [[:void_stmt]], nil]], + + %q{ case 0; in *,a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + nil, + nil, + [:var_field, nil], + [[:var_field, [:@ident, "a", [1, 13]]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in a:,**b; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + nil, + [[[:@label, "a:", [1, 11]], nil]], + [:var_field, [:@ident, "b", [1, 16]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in **a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, nil, [], [:var_field, [:@ident, "a", [1, 13]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in **; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:hshptn, nil, [], nil], [[:void_stmt]], nil]], + + %q{ case 0; in a: 0; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, nil, [[[:@label, "a:", [1, 11]], [:@int, "0", [1, 14]]]], nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in a:; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, nil, [[[:@label, "a:", [1, 11]], nil]], nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in "a": 0; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + nil, + [[[:string_content, [:@tstring_content, "a", [1, 12]]], + [:@int, "0", [1, 16]]]], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in "a":; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + nil, + [[[:string_content, [:@tstring_content, "a", [1, 12]]], nil]], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in a: 0, b: 0; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + nil, + [[[:@label, "a:", [1, 11]], [:@int, "0", [1, 14]]], + [[:@label, "b:", [1, 17]], [:@int, "0", [1, 20]]]], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in 0 => a; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:binary, + [:@int, "0", [1, 11]], + :"=>", + [:var_field, [:@ident, "a", [1, 16]]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in 0 | 1; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:binary, [:@int, "0", [1, 11]], :|, [:@int, "1", [1, 15]]], + [[:void_stmt]], + nil]], + + %q{ case 0; in A(0); end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + [:var_ref, [:@const, "A", [1, 11]]], + [[:@int, "0", [1, 13]]], + nil, + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in A(a:); end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + [:var_ref, [:@const, "A", [1, 11]]], + [[[:@label, "a:", [1, 13]], nil]], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in A(); end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, [:var_ref, [:@const, "A", [1, 11]]], nil, nil, nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in A[a]; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, + [:var_ref, [:@const, "A", [1, 11]]], + [[:var_field, [:@ident, "a", [1, 13]]]], + nil, + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in A[a:]; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, + [:var_ref, [:@const, "A", [1, 11]]], + [[[:@label, "a:", [1, 13]], nil]], + nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in A[]; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, [:var_ref, [:@const, "A", [1, 11]]], nil, nil, nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in [a]; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, nil, [[:var_field, [:@ident, "a", [1, 12]]]], nil, nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in []; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:aryptn, nil, nil, nil, nil], [[:void_stmt]], nil]], + + %q{ case 0; in {a: 0}; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, nil, [[[:@label, "a:", [1, 12]], [:@int, "0", [1, 15]]]], nil], + [[:void_stmt]], + nil]], + + %q{ case 0; in {}; end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:hshptn, nil, nil, nil], [[:void_stmt]], nil]], + + %q{ case 0; in (0); end } => + [:case, + [:@int, "0", [1, 5]], + [:in, [:@int, "0", [1, 12]], [[:void_stmt]], nil]], + + %q{ case 0; in a:, a:; end } => + nil, + + %q{ case 0; in a?:; end } => + nil, + } + pattern_matching_data.each_with_index do |(src, expected), i| + define_method(:"test_pattern_matching_#{i}") do + sexp = Ripper.sexp(src.strip) + assert_equal expected, sexp && sexp[1][0], src + end + end end if ripper_test diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb new file mode 100644 index 0000000000..b0d8e4778b --- /dev/null +++ b/test/ruby/test_pattern_matching.rb @@ -0,0 +1,1075 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestPatternMatching < Test::Unit::TestCase + class C + class << self + attr_accessor :keys + end + + def initialize(obj) + @obj = obj + end + + def deconstruct + @obj + end + + def deconstruct_keys(keys) + C.keys = keys + @obj + end + end + + def test_basic + assert_block do + case 0 + in 0 + true + else + false + end + end + + assert_block do + case 0 + in 1 + false + else + true + end + end + + assert_raise(NoMatchingPatternError) do + case 0 + in 1 + false + end + end + + begin + o = [0] + case o + in 1 + false + end + rescue => e + assert_match o.inspect, e.message + end + + assert_block do + begin + true + ensure + case 0 + in 0 + false + end + end + end + + assert_block do + begin + true + ensure + case 0 + in 1 + else + false + end + end + end + + assert_raise(NoMatchingPatternError) do + begin + ensure + case 0 + in 1 + end + end + end + + assert_block do + eval(%q{ + case true + in a + a + end + }) + end + + assert_block do + tap do |a| + tap do + case true + in a + a + end + end + end + end + + assert_raise(NoMatchingPatternError) do + o = BasicObject.new + def o.match + case 0 + in 1 + end + end + o.match + end + end + + def test_modifier + assert_block do + case 0 + in a if a == 0 + true + end + end + + assert_block do + case 0 + in a if a != 0 + else + true + end + end + + assert_block do + case 0 + in a unless a != 0 + true + end + end + + assert_block do + case 0 + in a unless a == 0 + else + true + end + end + end + + def test_as_pattern + assert_block do + case 0 + in 0 => a + a == 0 + end + end + end + + def test_alternative_pattern + assert_block do + [0, 1].all? do |i| + case i + in 0 | 1 + true + end + end + end + + assert_block do + case 0 + in _ | _a + true + end + end + + assert_syntax_error(%q{ + case 0 + in a | 0 + end + }, /illegal variable in alternative pattern/) + end + + def test_var_pattern + assert_block do + case [0, 1] + in a, a + a == 1 + end + end + end + + def test_literal_value_pattern + assert_block do + case [nil, self, true, false] + in [nil, self, true, false] + true + end + end + + assert_block do + case [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + in [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + true + end + + case [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + in [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + true + end + end + + assert_block do + x = 'x' + case ['a', 'a', x] + in ['a', "a", "#{x}"] + true + end + end + + assert_block do + case ["a\n"] + in [<<END] +a +END + true + end + end + + assert_block do + case [:a, :"a"] + in [:a, :"a"] + true + end + end + + assert_block do + case [0, 1, 2, 3, 4, 5] + in [0..1, 0...2, 0.., 0..., (...5), (..5)] + true + end + end + + assert_syntax_error(%q{ + case 0 + in a..b + end + }, /unexpected/) + + assert_block do + case 'abc' + in /a/ + true + end + end + + assert_block do + case 0 + in ->(i) { i == 0 } + true + end + end + + assert_block do + case [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), 'a'] + in [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), %r(a)] + true + end + end + + assert_block do + case [__FILE__, __LINE__ + 1, __ENCODING__] + in [__FILE__, __LINE__, __ENCODING__] + true + end + end + end + + def test_constant_value_pattern + assert_block do + case 0 + in Integer + true + end + end + + assert_block do + case 0 + in Object::Integer + true + end + end + + assert_block do + case 0 + in ::Object::Integer + true + end + end + end + + def test_pin_operator_value_pattern + assert_block do + a = /a/ + case 'abc' + in ^a + true + end + end + + assert_block do + case [0, 0] + in a, ^a + a == 0 + end + end + end + + def test_array_pattern + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in 0,; + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0, 1 + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0, 1 + else + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *a + a == [] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a + a == [0] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a, 0, 1 + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *a, 0, 1 + a == [] + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *a, 1, 2 + a == [0] + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *; + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *, 0, 1 + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *, 0, 1 + true + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *, 1, 2 + true + end + end + end + + assert_block do + case C.new([0]) + in C(0) + true + end + end + + assert_block do + case C.new([0]) + in Array(0) + else + true + end + end + + assert_block do + case C.new([]) + in C() + true + end + end + + assert_block do + case C.new([]) + in Array() + else + true + end + end + + assert_block do + case C.new([0]) + in C[0] + true + end + end + + assert_block do + case C.new([0]) + in Array[0] + else + true + end + end + + assert_block do + case C.new([]) + in C[] + true + end + end + + assert_block do + case C.new([]) + in Array[] + else + true + end + end + + assert_block do + case [] + in [] + true + end + end + + assert_block do + case C.new([]) + in [] + true + end + end + + assert_block do + case [0] + in [0] + true + end + end + + assert_block do + case C.new([0]) + in [0] + true + end + end + + assert_block do + case [] + in [0, *a] + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0] + in [0, *a, 1] + else + true + end + end + + assert_block do + case [0, 1] + in [0, *a, 1] + a == [] + end + end + + assert_block do + case [0, 1, 2] + in [0, *a, 2] + a == [1] + end + end + + assert_block do + case [] + in [0, *] + else + true + end + end + + assert_block do + case [0] + in [0, *] + true + end + end + + assert_block do + case [0, 1] + in [0, *] + true + end + end + + assert_block do + case [] + in [0, *a] + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0, 1] + in [0, *a] + a == [1] + end + end + end + + def test_hash_pattern + assert_block do + [{}, C.new({})].all? do |i| + case i + in a: 0 + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0, b: 1 + else + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{a: 0, b: 1, c: 2}, C.new({a: 0, b: 1, c: 2})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in a: + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a": 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a":; + a == 0 + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **a + a == {} + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **a + a == {a: 0} + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in a:, **b + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **b + a == 0 && b == {} + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a:, **b + a == 0 && b == {b: 1} + end + end + end + + assert_block do + case C.new({a: 0}) + in C(a: 0) + true + end + end + + assert_block do + case {a: 0} + in C(a: 0) + else + true + end + end + + assert_block do + case C.new({a: 0}) + in C[a: 0] + true + end + end + + assert_block do + case {a: 0} + in C[a: 0] + else + true + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {a: 0} + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {} + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {} + else + true + end + end + end + + assert_syntax_error(%q{ + case _ + in a:, a: + end + }, /duplicated key name/) + + assert_syntax_error(%q{ + case _ + in a?: + end + }, /key must be valid as local variables/) + + assert_block do + case {a?: true} + in a?: true + true + end + end + + assert_syntax_error(%q{ + case _ + in "a-b": + end + }, /key must be valid as local variables/) + + assert_block do + case {"a-b": true} + in "a-b": true + true + end + end + + assert_syntax_error(%q{ + case _ + in "#{a}": a + end + }, /symbol literal with interpolation is not allowed/) + + assert_syntax_error(%q{ + case _ + in "#{a}": + end + }, /symbol literal with interpolation is not allowed/) + end + + def test_paren + assert_block do + case 0 + in (0) + true + end + end + end + + def test_invalid_syntax + assert_syntax_error(%q{ + case 0 + in a, b: + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in [a:] + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {a} + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {0 => a} + end + }, /unexpected/) + end + + ################################################################ + + class CTypeError + def deconstruct + nil + end + + def deconstruct_keys(keys) + nil + end + end + + def test_deconstruct + assert_raise(TypeError) do + case CTypeError.new + in [] + end + end + end + + def test_deconstruct_keys + assert_raise(TypeError) do + case CTypeError.new + in {} + end + end + + assert_block do + case {} + in {} + $keys == nil + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:} + C.keys == [:a, :b] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:, **} + C.keys == [:a, :b] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:, **r} + C.keys == nil + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {**} + C.keys == [] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {**r} + C.keys == nil + end + end + end + + ################################################################ + + class TestPatternMatchingRefinements < Test::Unit::TestCase + class C1 + def deconstruct + [:C1] + end + end + + class C2 + end + + module M + refine Array do + def deconstruct + [0] + end + end + + refine Hash do + def deconstruct_keys(_) + {a: 0} + end + end + + refine C2.singleton_class do + def ===(obj) + obj.kind_of?(C1) + end + end + end + + using M + + def test_refinements + assert_block do + case [] + in [0] + true + end + end + + assert_block do + case {} + in {a: 0} + true + end + end + + assert_block do + case C1.new + in C2(:C1) + true + end + end + end + end +end @@ -2915,6 +2915,7 @@ Init_VM(void) rb_define_method_id(klass, id_core_set_postexe, m_core_set_postexe, 0); rb_define_method_id(klass, id_core_hash_merge_ptr, m_core_hash_merge_ptr, -1); rb_define_method_id(klass, id_core_hash_merge_kwd, m_core_hash_merge_kwd, 2); + rb_define_method_id(klass, id_core_raise, rb_f_raise, -1); rb_define_method_id(klass, idProc, rb_block_proc, 0); rb_define_method_id(klass, idLambda, rb_block_lambda, 0); rb_obj_freeze(fcore); |