From 21863470d965b8cc299b1f82417c70d5d26f8ab2 Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Sun, 21 Mar 2021 15:12:54 +0900 Subject: [PATCH] Pattern matching pin operator against expression [Feature #17411] This commit is based on the patch by @nobu. --- NEWS.md | 7 +++++++ compile.c | 1 + doc/syntax/pattern_matching.rdoc | 1 + parse.y | 18 ++++++++++++++---- test/ripper/test_sexp.rb | 8 ++++++++ test/ruby/test_pattern_matching.rb | 23 +++++++++++++++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index d6208199be..f8d2179a88 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,12 @@ since the **3.0.0** release, except for bug fixes. Note that each entry is kept to a minimum, see links for details. ## Language changes +* Pin operator now takes an expression. [[Feature #17411]] + + ```ruby + Prime.each_cons(2).lazy.find_all{_1 in [n, ^(n + 2)]}.take(3).to_a + #=> [[3, 5], [5, 7], [11, 13]] + ``` ## Command line options @@ -90,5 +96,6 @@ Excluding feature bug fixes. [Feature #16806]: https://bugs.ruby-lang.org/issues/16806 [Feature #17312]: https://bugs.ruby-lang.org/issues/17312 [Feature #17327]: https://bugs.ruby-lang.org/issues/17327 +[Feature #17411]: https://bugs.ruby-lang.org/issues/17411 [Bug #17423]: https://bugs.ruby-lang.org/issues/17423 [Feature #17479]: https://bugs.ruby-lang.org/issues/17479 diff --git a/compile.c b/compile.c index 78870ee051..230b800529 100644 --- a/compile.c +++ b/compile.c @@ -6211,6 +6211,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c case NODE_NIL: case NODE_COLON2: case NODE_COLON3: + case NODE_BEGIN: CHECK(COMPILE(ret, "case in literal", node)); ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); ADD_INSNL(ret, line, branchif, matched); diff --git a/doc/syntax/pattern_matching.rdoc b/doc/syntax/pattern_matching.rdoc index 9f6954f1cb..69756369fb 100644 --- a/doc/syntax/pattern_matching.rdoc +++ b/doc/syntax/pattern_matching.rdoc @@ -450,6 +450,7 @@ Approximate syntax is: value_pattern: literal | Constant | ^variable + | ^(expression) variable_pattern: variable diff --git a/parse.y b/parse.y index 10cd1000d9..e24d26e6e1 100644 --- a/parse.y +++ b/parse.y @@ -1196,7 +1196,7 @@ static int looking_at_eol_p(struct parser_params *p); %type p_case_body p_cases p_top_expr p_top_expr_body %type p_expr p_as p_alt p_expr_basic p_find %type p_args p_args_head p_args_tail p_args_post p_arg -%type p_value p_primitive p_variable p_var_ref p_const +%type p_value p_primitive p_variable p_var_ref p_expr_ref p_const %type p_kwargs p_kwarg p_kw %type keyword_variable user_variable sym operation operation2 operation3 %type cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg @@ -3994,7 +3994,7 @@ p_top_expr : p_top_expr_body | p_top_expr_body modifier_if expr_value { /*%%%*/ - $$ = new_if(p, $3, remove_begin($1), 0, &@$); + $$ = new_if(p, $3, $1, 0, &@$); fixpos($$, $3); /*% %*/ /*% ripper: if_mod!($3, $1) %*/ @@ -4002,7 +4002,7 @@ p_top_expr : p_top_expr_body | p_top_expr_body modifier_unless expr_value { /*%%%*/ - $$ = new_unless(p, $3, remove_begin($1), 0, &@$); + $$ = new_unless(p, $3, $1, 0, &@$); fixpos($$, $3); /*% %*/ /*% ripper: unless_mod!($3, $1) %*/ @@ -4066,6 +4066,7 @@ p_lparen : '(' {$$ = push_pktbl(p);}; p_lbracket : '[' {$$ = push_pktbl(p);}; p_expr_basic : p_value + | p_variable | p_const p_lparen p_args rparen { pop_pktbl(p, $2); @@ -4400,8 +4401,8 @@ p_value : p_primitive /*% %*/ /*% ripper: dot3!($1, Qnil) %*/ } - | p_variable | p_var_ref + | p_expr_ref | p_const | tBDOT2 p_primitive { @@ -4462,6 +4463,15 @@ p_var_ref : '^' tIDENTIFIER } ; +p_expr_ref : '^' tLPAREN expr_value ')' + { + /*%%%*/ + $$ = NEW_BEGIN($3, &@$); + /*% %*/ + /*% ripper: begin!($3) %*/ + } + ; + p_const : tCOLON3 cname { /*%%%*/ diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index 22ee418abb..b4e70fa4f5 100644 --- a/test/ripper/test_sexp.rb +++ b/test/ripper/test_sexp.rb @@ -478,6 +478,14 @@ eot [__LINE__, %q{ case 0; in "a\x0":a1, "a\0":a2; end }] => nil, # duplicated key name + + [__LINE__, %q{ case 0; in ^(0+0); end } ] => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:begin, [:binary, [:@int, "0", [1, 13]], :+, [:@int, "0", [1, 15]]]], + [[:void_stmt]], + nil]], } pattern_matching_data.each do |(i, src), expected| define_method(:"test_pattern_matching_#{i}") do diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index b18ef68316..c494550574 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -402,6 +402,29 @@ END end end + def test_pin_operator_expr_pattern + assert_block do + case 'abc' + in ^(/a/) + true + end + end + + assert_block do + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end + end + + assert_block do + case 0 + in ^(0+0) + true + end + end + end + def test_array_pattern assert_block do [[0], C.new([0])].all? do |i|