1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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
This commit is contained in:
ktsj 2019-04-17 06:48:03 +00:00
parent b077654a2c
commit 9738f96fcf
21 changed files with 2840 additions and 3 deletions

2
NEWS
View file

@ -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]

View file

@ -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");
}

20
ast.c
View file

@ -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;

590
compile.c
View file

@ -5236,6 +5236,593 @@ compile_case2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no
return COMPILE_OK;
}
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)
{
@ -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));

View file

@ -65,6 +65,7 @@ firstline, predefined = __LINE__+1, %[\
core#set_postexe
core#hash_merge_ptr
core#hash_merge_kwd
core#raise
- debug#created_info

View file

@ -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);

2
eval.c
View file

@ -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;

View file

@ -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;
}

View file

@ -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(", ") })"

8
hash.c
View file

@ -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.

View file

@ -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;

View file

@ -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);

View file

@ -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

37
node.c
View file

@ -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;

19
node.h
View file

@ -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);

714
parse.y
View file

@ -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
@ -10397,6 +11001,80 @@ rb_parser_numparam_id(struct parser_params *p, int idx)
return args->tbl[idx-1];
}
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)
{
@ -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 */

View file

@ -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=>{

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

1
vm.c
View file

@ -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);