[ruby-core:117041] [Ruby master Feature#20318] Pattern matching `case ... in` support for triple-dot arguments
From:
"nobu (Nobuyoshi Nakada) via ruby-core" <ruby-core@...>
Date:
2024-03-03 05:16:42 UTC
List:
ruby-core #117041
Issue #20318 has been updated by nobu (Nobuyoshi Nakada).
A patch for @ko1 style.
Probably the code generation would be more efficient in compile.c.
```diff
commit dcf97d47ad721bfbdae230234056df8a02044c7d
Author: Nobuyoshi Nakada (nobu) <nobu@ruby-lang.org>
AuthorDate: 2024-03-03 14:13:23 +0900
Commit: Nobuyoshi Nakada (nobu) <nobu@ruby-lang.org>
CommitDate: 2024-03-03 14:13:23 +0900
[Feature #20318] Method dispatch per `in`
diff --git a/parse.y b/parse.y
index de90ee797ff..6623a5bffa0 100644
--- a/parse.y
+++ b/parse.y
@@ -1581,6 +1581,7 @@ static NODE *new_args_forward_call(struct parser_params*, NODE*, const YYLTYPE*,
static int check_forwarding_args(struct parser_params*);
static void add_forwarding_args(struct parser_params *p);
static void forwarding_arg_check(struct parser_params *p, ID arg, ID all, const char *var);
+static NODE *new_method_case_args(struct parser_params *p);
static const struct vtable *dyna_push(struct parser_params *);
static void dyna_pop(struct parser_params*, const struct vtable *);
@@ -2740,7 +2741,7 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2)
%type <node> literal numeric simple_numeric ssym dsym symbol cpath
%type <node_def_temp> defn_head defs_head k_def
%type <node_exits> block_open k_while k_until k_for allow_exits
-%type <node> top_compstmt top_stmts top_stmt begin_block endless_arg endless_command
+%type <node> top_compstmt top_stmts top_stmt begin_block endless_arg endless_command method_body
%type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call
%type <node> expr_value expr_value_do arg_value primary_value rel_expr
%type <node_fcall> fcall
@@ -2980,6 +2981,14 @@ bodystmt : compstmt[body]
}
;
+method_body : bodystmt
+ | p_case_body[body]
+ {
+ $$ = NEW_CASE3(new_method_case_args(p), $body, &@body);
+ /*% ripper: case!(Qnil, $:body) %*/
+ }
+ ;
+
compstmt : stmts terms?
{
$$ = void_stmts(p, $1);
@@ -4604,7 +4613,7 @@ primary : literal
{
push_end_expect_token_locations(p, &@head.beg_pos);
}
- bodystmt
+ method_body[bodystmt]
k_end
{
restore_defun(p, $head);
@@ -4619,7 +4628,7 @@ primary : literal
{
push_end_expect_token_locations(p, &@head.beg_pos);
}
- bodystmt
+ method_body[bodystmt]
k_end
{
restore_defun(p, $head);
@@ -15543,6 +15552,33 @@ new_args_forward_call(struct parser_params *p, NODE *leading, const YYLTYPE *loc
return arg_blk_pass(args, block);
}
+static NODE *
+new_method_case_args(struct parser_params *p)
+{
+ if (!local_id(p, idFWD_ALL)) {
+ compile_error(p, "not defined with ...");
+ return Qnone;
+ }
+
+ const YYLTYPE *loc = &NULL_LOC;
+ NODE *rest = NEW_LVAR(idFWD_REST, loc);
+#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS
+ NODE *kwrest = NEW_LVAR(idFWD_KWREST, loc);
+#endif
+ NODE *block = NEW_LVAR(idFWD_BLOCK, loc);
+
+ NODE *args = NEW_SPLAT(rest, loc);
+#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS
+ NODE *kwsplat = list_append(p, NEW_LIST(0, loc), kwrest);
+ args = NEW_ARGSPUSH(args, NEW_HASH(kwsplat, loc), loc);
+#endif
+ args = NEW_ARGSCAT(args, NEW_SPLAT(block, loc), loc);
+
+ NODE *cond = NEW_AND(NEW_CALL(rest, idEmptyP, 0, loc),
+ NEW_CALL(block, '!', 0, loc), loc);
+ return NEW_IF(cond, kwrest, args, loc);
+}
+
static NODE *
numparam_push(struct parser_params *p)
{
```
----------------------------------------
Feature #20318: Pattern matching `case ... in` support for triple-dot arguments
https://bugs.ruby-lang.org/issues/20318#change-107111
* Author: bradgessler (Brad Gessler)
* Status: Open
----------------------------------------
# Premise
Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this:
```ruby
def foo(*args, **kwargs, &block)
case { args:, kwargs:, block: }
in args: [name]
puts name
in args: [first_name, last_name]
puts "Hi there #{first_name} #{last_name}"
in kwargs: {greeting:}
puts "Hello #{greeting}"
else
puts "No match: #{args}"
end
end
foo "Hi"
foo "Brad", "Gessler"
foo greeting: "Brad"
```
Or an array like this:
```ruby
def bar(*args, **kwargs, &block)
case [args, kwargs, block]
in [name], {}, nil
puts name
in [first_name, last_name], {}, nil
puts "Hi there #{first_name} #{last_name}"
in [], {greeting:}, nil
puts "Hello #{greeting}"
else
puts "No match: #{args}, #{kwargs}"
end
end
bar "Howdy"
bar "Bradley", "Gessler"
bar greeting: "Bradley"
```
# Proposal
I'd like to propose the same thing, but for `...`, like this:
```ruby
def foo(...)
case ...
in args: [name]
puts name
in args: [first_name, last_name]
puts "Hi there #{first_name} #{last_name}"
in kwargs: {greeting:}
puts "Hello #{greeting}"
else
puts "No match: #{args}"
end
end
foo "Hi"
foo "Brad", "Gessler"
foo greeting: "Brad"
```
One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names.
The array would look like this:
```ruby
def bar(...)
case ...
in [name], {}, nil
puts name
in [first_name, last_name], {}, nil
puts "Hi there #{first_name} #{last_name}"
in [], {greeting:}, nil
puts "Hello #{greeting}"
else
puts "No match: #{args}, #{kwargs}"
end
end
bar "Howdy"
bar "Bradley", "Gessler"
bar greeting: "Bradley"
```
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/