From: "nobu (Nobuyoshi Nakada) via ruby-core" Date: 2024-03-03T05:16:42+00:00 Subject: [ruby-core:117041] [Ruby master Feature#20318] Pattern matching `case ... in` support for triple-dot arguments 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) AuthorDate: 2024-03-03 14:13:23 +0900 Commit: Nobuyoshi Nakada (nobu) 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 literal numeric simple_numeric ssym dsym symbol cpath %type defn_head defs_head k_def %type block_open k_while k_until k_for allow_exits -%type top_compstmt top_stmts top_stmt begin_block endless_arg endless_command +%type top_compstmt top_stmts top_stmt begin_block endless_arg endless_command method_body %type bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call %type expr_value expr_value_do arg_value primary_value rel_expr %type 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/