summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKazuki Tsujimoto <kazuki@callcc.net>2019-09-01 16:39:34 +0900
committerKazuki Tsujimoto <kazuki@callcc.net>2019-09-01 16:39:34 +0900
commit94d6ec1d90bb28e5f303867b048e6322d8781cb1 (patch)
tree55a794614931f6dc7778f6b1b672ca3a01f0b34f
parentcda5745c1bacdb3be8384d21ee0dd70a9d95af5b (diff)
Make pattern matching support **nil syntax
-rw-r--r--ast.c5
-rw-r--r--compile.c23
-rw-r--r--node.c7
-rw-r--r--node.h1
-rw-r--r--parse.y21
-rw-r--r--test/ruby/test_pattern_matching.rb38
6 files changed, 87 insertions, 8 deletions
diff --git a/ast.c b/ast.c
index 601a819c1b..fdb4b7a621 100644
--- a/ast.c
+++ b/ast.c
@@ -652,10 +652,13 @@ node_children(rb_ast_t *ast, NODE *node)
}
case NODE_HSHPTN:
{
+ VALUE kwrest = node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD ? ID2SYM(rb_intern("NODE_SPECIAL_NO_REST_KEYWORD")) :
+ NEW_CHILD(ast, node->nd_pkwrestarg);
+
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));
+ kwrest);
}
case NODE_ARGS_AUX:
case NODE_LAST:
diff --git a/compile.c b/compile.c
index d11e2f9f32..02171c6349 100644
--- a/compile.c
+++ b/compile.c
@@ -5467,7 +5467,15 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c
* end
* end
* if pattern.has_kw_rest_arg_node?
- * pattern.kw_rest_arg_node.match?(d)
+ * if pattern.no_rest_keyword?
+ * unless d.empty?
+ * goto match_failed
+ * end
+ * else
+ * unless pattern.kw_rest_arg_node.match?(d)
+ * goto match_failed
+ * end
+ * end
* end
* true
* goto fin
@@ -5563,9 +5571,16 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c
}
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);
+ if (node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD) {
+ ADD_INSN(ret, line, dup);
+ ADD_SEND(ret, line, idEmptyP, INT2FIX(0));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+ else {
+ 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);
diff --git a/node.c b/node.c
index 4c2810b0e2..f4df845d2c 100644
--- a/node.c
+++ b/node.c
@@ -1065,7 +1065,12 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
F_NODE(nd_pconst, "constant");
F_NODE(nd_pkwargs, "keyword arguments");
LAST_NODE;
- F_NODE(nd_pkwrestarg, "keyword rest argument");
+ if (node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD) {
+ F_MSG(nd_pkwrestarg, "keyword rest argument", "NODE_SPECIAL_NO_REST_KEYWORD (**nil)");
+ }
+ else {
+ F_NODE(nd_pkwrestarg, "keyword rest argument");
+ }
return;
case NODE_ARGS_AUX:
diff --git a/node.h b/node.h
index 3b565fc083..dbc3162512 100644
--- a/node.h
+++ b/node.h
@@ -385,6 +385,7 @@ typedef struct RNode {
#define NODE_SPECIAL_NO_NAME_REST ((NODE *)-1)
#define NODE_NAMED_REST_P(node) ((node) != NODE_SPECIAL_NO_NAME_REST)
#define NODE_SPECIAL_EXCESSIVE_COMMA ((ID)1)
+#define NODE_SPECIAL_NO_REST_KEYWORD ((NODE *)-1)
VALUE rb_node_case_when_optimizable_literal(const NODE *const node);
diff --git a/parse.y b/parse.y
index f6d6e27eb7..6729e104be 100644
--- a/parse.y
+++ b/parse.y
@@ -1033,7 +1033,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%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
+%type <id> p_kwrest p_kwnorest
%type <id> f_no_kwarg
%token END_OF_INPUT 0 "end-of-input"
%token <id> '.'
@@ -3950,6 +3950,14 @@ p_kwargs : p_kwarg ',' p_kwrest
{
$$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), $1, &@$);
}
+ | p_kwarg ',' p_kwnorest
+ {
+ $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), ID2SYM(idNil), &@$);
+ }
+ | p_kwnorest
+ {
+ $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), ID2SYM(idNil), &@$);
+ }
;
p_kwarg : p_kw
@@ -4026,6 +4034,12 @@ p_kwrest : kwrest_mark tIDENTIFIER
}
;
+p_kwnorest : kwrest_mark keyword_nil
+ {
+ $$ = 0;
+ }
+ ;
+
p_value : p_primitive
| p_primitive tDOT2 p_primitive
{
@@ -11253,7 +11267,10 @@ new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, co
int saved_line = p->ruby_sourceline;
NODE *node, *kw_rest_arg_node;
- if (kw_rest_arg) {
+ if (kw_rest_arg == ID2SYM(idNil)) {
+ kw_rest_arg_node = NODE_SPECIAL_NO_REST_KEYWORD;
+ }
+ else if (kw_rest_arg) {
kw_rest_arg_node = assignable(p, kw_rest_arg, 0, loc);
}
else {
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index 988a9dfe39..1f209b0081 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -847,6 +847,44 @@ END
end
assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **nil
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **nil
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:, **nil
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:, **nil
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
case C.new({a: 0})
in C(a: 0)
true