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:
parent
b077654a2c
commit
9738f96fcf
21 changed files with 2840 additions and 3 deletions
2
NEWS
2
NEWS
|
@ -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]
|
||||
|
||||
|
|
8
array.c
8
array.c
|
@ -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
20
ast.c
|
@ -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
590
compile.c
|
@ -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));
|
||||
|
|
|
@ -65,6 +65,7 @@ firstline, predefined = __LINE__+1, %[\
|
|||
core#set_postexe
|
||||
core#hash_merge_ptr
|
||||
core#hash_merge_kwd
|
||||
core#raise
|
||||
|
||||
- debug#created_info
|
||||
|
||||
|
|
2
error.c
2
error.c
|
@ -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
2
eval.c
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
8
hash.c
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
37
node.c
37
node.c
|
@ -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
19
node.h
|
@ -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
714
parse.y
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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=>{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
1075
test/ruby/test_pattern_matching.rb
Normal file
1075
test/ruby/test_pattern_matching.rb
Normal file
File diff suppressed because it is too large
Load diff
1
vm.c
1
vm.c
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue