以下の内容はhttps://yui-knk.hatenablog.com/entry/2026/02/11/215300より取得しました。


Ruby Parser開発日誌 (24-33) - parse.yが生成するノードを変える ー 匿名引数とforwarding

33日目: 匿名引数やforwardingがあるメソッド呼び出し

前回まではリテラルをやってきましたが、今回はメソッドの呼び出し、とくに*, **といった匿名(実)引数やforwarding(...)があるケースをやっていきます。

匿名引数

匿名な仮引数は対応していますが、実引数側は未対応です。 そのため本来であればgetlocalでないといけないところが、putnilになってしまっています。

def m(*, **, &)
  p(*)
  p(**)
  p(&)
end

# Before

# == disasm: #<ISeq:m@test.rb:1 (1,0)-(6,3)>
# local table (size: 3, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 3] "*"@0<AnonRest>[ 2] "**"@1<AnonKwrest>[ 1] "&"@2<Block>
# 0000 putself                                                          (   2)[LiCa]
# 0001 getlocal_WC_0                          "*"@0
# 0003 splatarray                             false
# 0005 opt_send_without_block                 <calldata!mid:p, argc:1, ARGS_SPLAT|FCALL>
# 0007 pop
# 0008 putself                                                          (   3)[Li]
# 0009 getlocal_WC_0                          "**"@1
# 0011 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|KW_SPLAT>
# 0013 pop
# 0014 putself                                                          (   4)[Li]
# 0015 getblockparamproxy                     "&"@2, 0
# 0018 send                                   <calldata!mid:p, argc:0, ARGS_BLOCKARG|FCALL>, nil
# 0021 leave                                                            (   6)[Re]

# After

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(6,3)>
# local table (size: 3, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 3] "*"@0<AnonRest>[ 2] "**"@1<AnonKwrest>[ 1] "&"@2<Block>
# 0000 putself                                                          (   2)[LiCa]
# 0001 putnil
# 0002 splatarray                             false
# 0004 opt_send_without_block                 <calldata!mid:p, argc:1, ARGS_SPLAT|FCALL>
# 0006 pop
# 0007 putself                                                          (   3)[Li]
# 0008 putnil
# 0009 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|KW_SPLAT>
# 0011 pop
# 0012 putself                                                          (   4)[Li]
# 0013 putnil
# 0014 send                                   <calldata!mid:p, argc:0, ARGS_BLOCKARG|FCALL>, nil
# 0017 leave                                                            (   1)[Re]

書き換え前は匿名引数であってもローカル変数アクセスを表すNODE_LVARが置かれていたのに対して、書き換え後はexpression: nilとなっているため、NULLコンパイルした結果がputnil命令が生成されるようになっています。

p(*)

# Before

# @ NODE_SPLAT (id: 6, line: 2, location: (2,4)-(2,5))
# +- nd_head:
# |   @ NODE_LVAR (id: 5, line: 2, location: (2,4)-(2,5))
# |   +- nd_vid: :*

# After

# arguments: (length: 1)
# +-- @ SplatNode (location: (2,4)-(2,5))
#     +-- expression: nil

それぞれ直していきましょう。

*実引数に対応する

まずは*からです。 匿名なsplat引数はcompile_args関数で処理されています。 expressionフィールドの値によらず常にCOMPILEをしていましたが、それだとexpressionNULLのときに意図しないバイトコードが生成されてしまうのでした。 そこでexpressionフィールドをチェックしてNULLのときはgetlocal "*"@0という命令を生成するように変更します。 getlocal命令のオペランドはローカル変数テーブルにおけるインデックスなので、IDを渡すことでインデックス解決をしてgetlocal命令を生成してくれる関数があると便利です。 そこで通常のローカル変数アクセスであるLocalVariableReadNodeコンパイル処理の部分をcompile_lvar関数として切り出しておきます。

 static int
 compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const rb_arguments_node_t *nd_args, unsigned int *dup_rest, unsigned int *flag_ptr, rb_keyword_hash_node_t **kwnode_ptr)
 {
     ...
     for (size_t i = 0; i < RB_NODE_LIST_LEN(list); i++) {
         node = list->nodes[i];

         switch (nd_type(node)) {
           case RB_SPLAT_NODE:
             if (stack_len) {
                 ADD_INSN1(ret, node, pushtoarray, INT2FIX(stack_len));
                 stack_len = 0;
             }
-            NO_CHECK(COMPILE(ret, "args (splat)", RB_NODE_SPLAT(node)->expression));
+            if (RB_NODE_SPLAT(node)->expression) {
+                NO_CHECK(COMPILE(ret, "args (splat)", RB_NODE_SPLAT(node)->expression));
+            }
+            else {
+                /* m(*) */
+                NO_CHECK(compile_lvar(iseq, ret, node, '*'));
+            }
            ...
         }
     }
 }

+static int
+compile_lvar(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, ID id)
+{
+    int lv, idx, ls;
+    idx = get_dyna_var_idx(iseq, id, &lv, &ls);
+
+    if (idx < 0) {
+        COMPILE_ERROR(ERROR_ARGS "unknown dvar (%"PRIsVALUE")",
+                      rb_id2str(id));
+        return COMPILE_NG;
+    }
+    ADD_GETLOCAL(ret, node, ls - idx, lv);
+    return COMPILE_OK;
+}

@@ -11918,17 +11951,12 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no
         break;
       }
       case RB_LOCAL_VARIABLE_READ_NODE: { // LVAR and DVAR
-        int lv, idx, ls;
-        rb_local_variable_read_node_t *cast = RB_NODE_LOCAL_VARIABLE_READ(node);
-        debugi("nd_vid", cast->name);
+        ID id = RB_NODE_LOCAL_VARIABLE_READ(node)->name;
+        debugi("nd_vid", id);
         if (!popped) {
-            idx = get_dyna_var_idx(iseq, cast->name, &lv, &ls);
-            if (idx < 0) {
-                COMPILE_ERROR(ERROR_ARGS "unknown dvar (%"PRIsVALUE")",
-                              rb_id2str(cast->name));
+            if (compile_lvar(iseq, ret, node, id) == COMPILE_NG) {
                 goto ng;
             }
-            ADD_GETLOCAL(ret, node, ls - idx, lv);
         }
         break;
       }

putnilだった箇所がgetlocal_WC_0 "*"@0になっています。

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(3,3)>
# local table (size: 3, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 3] "*"@0<AnonRest>[ 2] "**"@1<AnonKwrest>[ 1] "&"@2<Block>
# 0000 putself                                                          (   2)[LiCa]
# 0001 getlocal_WC_0                          "*"@0
# 0003 splatarray                             false
# 0005 opt_send_without_block                 <calldata!mid:p, argc:1, ARGS_SPLAT|FCALL>
# 0007 leave

def m(*, **, &)
  p(*)
end

*a = [1, *, 2]のようにarrayリテラルのなかに書くこともできるため、compile_array関数も修正しておきます。

@@ -5516,7 +5518,14 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *line_node, co
             else if (nd_type_p(node, RB_SPLAT_NODE)) {
                 FLUSH_CHUNK;

-                NO_CHECK(COMPILE_(ret, "array element", RB_NODE_SPLAT(node)->expression, 0));
+                if (RB_NODE_SPLAT(node)->expression) {
+                    NO_CHECK(COMPILE_(ret, "array element", RB_NODE_SPLAT(node)->expression, 0));
+                }
+                else {
+                    /* [..., *, ...] */
+                    NO_CHECK(compile_lvar(iseq, ret, node, '*'));
+                }
+

こちらも正しいバイトコードが生成されるようになりました。

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(3,3)>
# local table (size: 4, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 4] "*"@0<AnonRest>[ 3] "**"@1<AnonKwrest>[ 2] "&"@2<Block>[ 1] a@3
# 0000 getlocal_WC_0                          "*"@0                     (   2)[LiCa]
# 0002 splatarray                             true
# 0004 dup
# 0005 setlocal_WC_0                          a@3
# 0007 leave                                                            (   1)[Re]

def m(*, **, &)
  a = [*]
end

**実引数に対応する

**の場合、メソッドの引数のケースであるかhashリテラルの要素であるかに関係なくcompile_hash関数で処理されます。 compile_array関数と同様にAssocSplatNodevalueの有無で分岐するようにします。 なお以下のコードではconst NODE *kw = RB_NODE_ASSOC_SPLAT(node)->value;です。

@@ -5722,7 +5732,13 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *line_node, con
                          * This is only done for method calls and not for literal hashes,
                          * because literal hashes should always result in a new hash.
                          */
-                        NO_CHECK(COMPILE(ret, "keyword splat", kw));
+                        if (kw) {
+                            NO_CHECK(COMPILE(ret, "keyword splat", kw));
+                        }
+                        else {
+                            /* foo(**) */
+                            NO_CHECK(compile_lvar(iseq, ret, node, idPow));
+                        }
                     }
                     else {
                         /* There is more than one keyword argument, or this is not a method
@@ -5733,7 +5749,13 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *line_node, con
                         if (first_kw) ADD_INSN1(ret, line_node, newhash, INT2FIX(0));
                         else ADD_INSN(ret, line_node, swap);

-                        NO_CHECK(COMPILE(ret, "keyword splat", kw));
+                        if (kw) {
+                            NO_CHECK(COMPILE(ret, "keyword splat", kw));
+                        }
+                        else {
+                            /* foo(**) */
+                            NO_CHECK(compile_lvar(iseq, ret, node, idPow));
+                        }

                         ADD_SEND(ret, line_node, id_core_hash_merge_kwd, INT2FIX(2));
                     }

**がメソッドの引数に渡されるケース、配列の要素になるケース、Hashの要素になるケースのすべてで正しいバイトコードが生成されるようになりました。

# == disasm: #<ISeq:m@../../test.rb:9 (9,0)-(13,3)>
# local table (size: 5, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 5] "*"@0<AnonRest>[ 4] "**"@1<AnonKwrest>[ 3] "&"@2<Block>[ 2] h@3        [ 1] a@4
# 0000 putself                                                          (  10)[LiCa]
# 0001 getlocal_WC_0                          "**"@1
# 0003 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|KW_SPLAT>
# 0005 pop
# 0006 putspecialobject                       1                         (  11)[Li]
# 0008 newhash                                0
# 0010 getlocal_WC_0                          "**"@1
# 0012 opt_send_without_block                 <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>
# 0014 setlocal_WC_0                          h@3
# 0016 newarray                               0                         (  12)[Li]
# 0018 putspecialobject                       1
# 0020 newhash                                0
# 0022 getlocal_WC_0                          "**"@1
# 0024 opt_send_without_block                 <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>
# 0026 pushtoarraykwsplat
# 0027 dup
# 0028 setlocal_WC_0                          a@4
# 0030 leave
def m(*, **, &)
  p(**)
  h = {**}
  a = [**]
end

&実引数に対応する

&setup_args関数で処理されているので、そこにexpressionの有無による分岐を追加します。

@@ -7362,7 +7390,13 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, const rb_arguments_node_t *
         else {
             *flag |= VM_CALL_ARGS_BLOCKARG;

-            NO_CHECK(COMPILE(arg_block, "block", block->expression));
+            if (block->expression) {
+                NO_CHECK(COMPILE(arg_block, "block", block->expression));
+            }
+            else {
+                /* m(&) */
+                NO_CHECK(compile_lvar(iseq, arg_block, block, '&'));
+            }
         }

これで正しいバイトコードが生成されるようになりました。

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(3,3)>
# local table (size: 3, argc: 0 [opts: 0, rest: 0, post: 0, block: 2, kw: -1@-1, kwrest: 1])
# [ 3] "*"@0<AnonRest>[ 2] "**"@1<AnonKwrest>[ 1] "&"@2<Block>
# 0000 putself                                                          (   2)[LiCa]
# 0001 getblockparamproxy                     "&"@2, 0
# 0004 send                                   <calldata!mid:p, argc:0, ARGS_BLOCKARG|FCALL>, nil
# 0007 leave                                                            (   1)[Re]

def m(*, **, &)
  p(&)
end

仮引数におけるforwarding(...)

仮引数の対応をしたときにforwarding(...)については後回しにしていました。 そのためlocal tableに"*"@0などの余分なスペースができています。

def m(...)
end

# Before

# == disasm: #<ISeq:m@test.rb:1 (1,0)-(2,3)>
# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] "..."@0
# 0000 putnil                                                           (   1)[Ca]
# 0001 leave                                                            (   2)[Re]

# After

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(2,3)>
# local table (size: 4, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 4] "*"@0      [ 3] "**"@1     [ 2] "&"@2      [ 1] "..."@3
# 0000 putnil                                                           (   1)[Ca]
# 0001 leave                                  [Re]

これはiseq_set_local_table関数の分岐に入っていないのが原因なので、その部分を修正します。

@@ -2497,13 +2497,13 @@ iseq_set_local_table(rb_iseq_t *iseq, const rb_ast_id_table_t *tbl, const NODE *
     unsigned int size = tbl ? tbl->size : 0;
     unsigned int offset = 0;

-    if (node_args) {
-        struct rb_args_info *args = &RNODE_ARGS(node_args)->nd_ainfo;
+    if (node_args && nd_type_p(node_args, RB_PARAMETERS_NODE)) {
+        const NODE *kw_rest = RB_NODE_PARAMETERS(node_args)->keyword_rest;

         // If we have a function that only has `...` as the parameter,
         // then its local table should only be `...`
         // FIXME: I think this should be fixed in the AST rather than special case here.
-        if (args->forwarding && args->pre_args_num == 0 && !args->opt_args) {
+        if (kw_rest && nd_type_p(kw_rest, RB_FORWARDING_PARAMETER_NODE)) {
             CHECK(size >= 3);
             size -= 3;
             offset += 3;

修正後は[ 1] "..."@0だけがlocal tableに存在するようになります。

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(2,3)>
# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] "..."@0
# 0000 putnil                                                           (   1)[Ca]
# 0001 leave                                  [Re]

def m(...)
end

実引数におけるforwarding(...)

まずはノードの修正からです。 ノードの書き換え前はNODE_BLOCK_PASS(&)の下にNODE_SPLAT(*)とNODE_HASH(**)が置かれているというなんとも派手な構造になっています。 またNODE_BLOCK_PASSにはforwardingというフラグがあります1

# @ NODE_FCALL (id: 4, line: 2, location: (2,2)-(2,8))*
# +- nd_mid: :p
# +- nd_args:
#     @ NODE_BLOCK_PASS (id: 10, line: 2, location: (2,3)-(2,8))
#     +- forwarding: 1 (forwarding)
#     +- nd_head:
#     |   @ NODE_ARGSPUSH (id: 13, line: 2, location: (2,3)-(2,8))
#     |   +- nd_head:
#     |   |   @ NODE_SPLAT (id: 11, line: 2, location: (2,4)-(2,7))
#     |   |   +- nd_head:
#     |   |   |   @ NODE_LVAR (id: 5, line: 2, location: (2,4)-(2,7))
#     |   |   |   +- nd_vid: :*
#     |   +- nd_body:
#     |       @ NODE_HASH (id: 12, line: 2, location: (2,4)-(2,7))
#     |       +- nd_brace: 0 (keyword argument)
#     |       +- nd_head:
#     |           @ NODE_LIST (id: 6, line: 2, location: (2,4)-(2,7))
#     |           +- as.nd_alen: 2
#     |           +- nd_head:
#     |           |   (null node)
#     |           +- nd_head:
#     |           |   @ NODE_LVAR (id: 7, line: 2, location: (2,4)-(2,7))
#     |           |   +- nd_vid: :**
#     +- nd_body:
#     |   @ NODE_LVAR (id: 9, line: 2, location: (2,4)-(2,7))
#     |   +- nd_vid: :&

def m(...)
  p(...)
end

ノードの書き換え後はForwardingArgumentsNodeというノードで実引数の...を表現するようになります。

# After
#
# arguments:
# @ ArgumentsNode (location: (2,4)-(2,7))
# +-- ArgumentsNodeFlags: contains_forwarding
# +-- arguments: (length: 1)
#     +-- @ ForwardingArgumentsNode (location: (2,4)-(2,7))

parse.yではnew_args_forward_callという関数で...に対応するノードを作っているのでこの関数を修正します。

paren_args : '(' opt_call_args rparen
                    {
                        $$ = $2;
                    /*% ripper: arg_paren!($:2) %*/
                    }
                | '(' args ',' args_forward rparen
                    {
                        if (!check_forwarding_args(p)) {
                            $$ = 0;
                        }
                        else {
                            $$ = new_args_forward_call(p, $2, &@4, &@$);
                        /*% ripper: arg_paren!(args_add!($:2, $:4)) %*/
                        }
                    }
                | '(' args_forward rparen
                    {
                        if (!check_forwarding_args(p)) {
                            $$ = 0;
                        }
                        else {
                            $$ = new_args_forward_call(p, 0, &@2, &@$);
                        /*% ripper: arg_paren!($:2) %*/
                        }
                    }
                ;

p(1, ...)のように...以前にも引数がある場合にはそこにForwardingArgumentsNodeを追加し、そうでない場合にはForwardingArgumentsNodeを1つだけもつArgumentsNodeを返します。

// Before
static NODE *
new_args_forward_call(struct parser_params *p, NODE *leading, const YYLTYPE *loc, const YYLTYPE *argsloc)
{
    NODE *rest = NEW_LVAR(idFWD_REST, loc);
    NODE *kwrest = list_append(p, NEW_LIST(0, loc), NEW_LVAR(idFWD_KWREST, loc));
    rb_node_block_pass_t *block = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, loc), argsloc, &NULL_LOC);
    NODE *args = leading ? rest_arg_append(p, leading, rest, argsloc) : NEW_SPLAT(rest, loc, &NULL_LOC);
    block->forwarding = TRUE;
    args = arg_append(p, args, new_hash(p, kwrest, loc), argsloc);
    return arg_blk_pass(args, block);
}

// After
static rb_arguments_node_t *
new_args_forward_call(struct parser_params *p, rb_array_node_t *leading, const YYLTYPE *loc, const YYLTYPE *argsloc)
{
    rb_arguments_node_t *nd_args;

    if (!leading) return NEW_RB_ARGUMENTS(NEW_RB_FORWARDING_ARGUMENTS(loc), argsloc);

    nd_args = array2arguments(p, leading);
    return arg_append2(p, nd_args, NEW_RB_FORWARDING_ARGUMENTS(loc), argsloc);
}

コンパイラのほうはというと、いままではsetup_args関数で...の部分をコンパイルしていました。

static VALUE
setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn,
           unsigned int *flag, struct rb_callinfo_kwarg **keywords)
{
    ...
    if (argn && nd_type_p(argn, NODE_BLOCK_PASS)) {
        DECL_ANCHOR(arg_block);
        INIT_ANCHOR(arg_block);

        if (RNODE_BLOCK_PASS(argn)->forwarding && ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.forwardable) {
            // ここで`...`のケースを処理している
            int idx = ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->local_table_size;// - get_local_var_idx(iseq, idDot3);

            RUBY_ASSERT(nd_type_p(RNODE_BLOCK_PASS(argn)->nd_head, NODE_ARGSPUSH));
            const NODE * arg_node =
                RNODE_ARGSPUSH(RNODE_BLOCK_PASS(argn)->nd_head)->nd_head;

            int argc = 0;

            // Only compile leading args:
            //   foo(x, y, ...)
            //       ^^^^
            if (nd_type_p(arg_node, NODE_ARGSCAT)) {
                argc += setup_args_core(iseq, args, RNODE_ARGSCAT(arg_node)->nd_head, &dup_rest, flag, keywords);
            }

            *flag |= VM_CALL_FORWARDING;

            ADD_GETLOCAL(args, argn, idx, get_lvar_level(iseq));
            setup_args_splat_mut(flag, dup_rest, initial_dup_rest);
            return INT2FIX(argc);
        }
        else {
            *flag |= VM_CALL_ARGS_BLOCKARG;

            NO_CHECK(COMPILE(arg_block, "block", RNODE_BLOCK_PASS(argn)->nd_body));
        }

        if (LIST_INSN_SIZE_ONE(arg_block)) {
            LINK_ELEMENT *elem = FIRST_ELEMENT(arg_block);
            if (IS_INSN(elem)) {
                INSN *iobj = (INSN *)elem;
                if (iobj->insn_id == BIN(getblockparam)) {
                    iobj->insn_id = BIN(getblockparamproxy);
                }
            }
        }
        ret = INT2FIX(setup_args_core(iseq, args, RNODE_BLOCK_PASS(argn)->nd_head, &dup_rest, flag, keywords));
        ADD_SEQ(args, arg_block);
    }
    else {
        ret = INT2FIX(setup_args_core(iseq, args, argn, &dup_rest, flag, keywords));
    }
    setup_args_splat_mut(flag, dup_rest, initial_dup_rest);
    return ret;
}

if (argn && nd_type_p(argn, NODE_BLOCK_PASS))という条件になっているのは、先ほど見たとおり...実引数があるときはNODE_BLOCK_PASSが生成されるためです。 今回の変更でForwardingArgumentsNodeが生成されるようになったので、...に関する処理を1つ上に引き上げます。

static VALUE
setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, const rb_arguments_node_t *argn,
           const rb_block_argument_node_t *block, unsigned int *flag, struct rb_callinfo_kwarg **keywords)
{
    ...
    if (forwarding && ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.forwardable) {
        // ここで`...`のケースを処理している
        int idx = ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->local_table_size;// - get_local_var_idx(iseq, idDot3);
        int argc = 0;

        // Only compile leading args:
        //   foo(x, y, ...)
        //       ^^^^
        argc += setup_args_core(iseq, args, argn, &dup_rest, flag, keywords);
        *flag |= VM_CALL_FORWARDING;

        ADD_GETLOCAL(args, argn, idx, get_lvar_level(iseq));
        setup_args_splat_mut(flag, dup_rest, initial_dup_rest);
        return INT2FIX(argc);
    }

    // BlockNode is also stored in `block` however it should be handled by `compile_iter`
    if (block && nd_type_p(block, RB_BLOCK_ARGUMENT_NODE)) {
        DECL_ANCHOR(arg_block);
        INIT_ANCHOR(arg_block);

        *flag |= VM_CALL_ARGS_BLOCKARG;

        if (block->expression) {
            NO_CHECK(COMPILE(arg_block, "block", block->expression));
        }
        else {
            /* m(&) */
            NO_CHECK(compile_lvar(iseq, arg_block, block, '&'));
        }

        if (LIST_INSN_SIZE_ONE(arg_block)) {
            LINK_ELEMENT *elem = FIRST_ELEMENT(arg_block);
            if (IS_INSN(elem)) {
                INSN *iobj = (INSN *)elem;
                if (iobj->insn_id == BIN(getblockparam)) {
                    iobj->insn_id = BIN(getblockparamproxy);
                }
            }
        }
        ret = INT2FIX(setup_args_core(iseq, args, argn, &dup_rest, flag, keywords));
        ADD_SEQ(args, arg_block);
    }
    else {
        ret = INT2FIX(setup_args_core(iseq, args, argn, &dup_rest, flag, keywords));
    }
    setup_args_splat_mut(flag, dup_rest, initial_dup_rest);
    return ret;
}

実際にバイトコードを生成してみます。

# == disasm: #<ISeq:m@../../test.rb:1 (1,0)-(3,3)>
# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] "..."@0
# 0000 putself                                                          (   2)[LiCa]
# 0001 getlocal_WC_0                          "..."@0
# 0003 sendforward                            <calldata!mid:p, argc:0, FCALL|FORWARDING>, nil
# 0006 leave                                                            (   1)[Re]

def m(...)
  p(...)
end

よさそうですね。

まとめ

今日の成果です。

  • 実引数の*, **, &を修正した
  • 仮引数および実引数の...を対応した

ここまでの進捗

前回の進捗確認から少し間が空いたのであらためて進捗を確認しておきましょう。

yui-knk.hatenablog.com

前回と比べて対応した機能は以下のとおりです。

  • 24日目: alias, nth ref ($1), back ref ($&)
  • 25-26日目: string, interpolationを含むstring
  • 27日目: xstring, interpolationを含むxstring, interpolationを含むsymbol
  • 28日目: regex, interpolationを含むregex
  • 29日目: match演算子(=~)とlambda
  • 30日目: hash
  • 31日目: array
  • 32日目: integer, float, rational, imaginary, range
  • 33日目: 匿名引数とforwarding
  • いつの間にか実装していた: symbol

リテラル・変数の参照と代入・各種定義が完了していて、残りはメソッド呼び出しの一部・制御構文の一部・パターンマッチング・その他(undefとdefined?)というところまできました。

機能の一覧とその進捗は以下のとおりです。 引き続き頑張っていきましょう。

  • リテラル
    • nil
    • true
    • false
    • self
    • __LINE__
    • __FILE__
    • __ENCODING__
    • array
    • hash
    • string
    • xstring
    • symbol
    • integer
    • float
    • rational
    • imaginary
    • regex
    • range
    • interpolationを含むstring
    • interpolationを含むxstring
    • interpolationを含むsymbol
    • interpolationを含むregex
    • lambda
  • 変数の参照と代入
  • 定義
    • メソッド定義
      • multiple assignment (def m((a, b)))
      • forwarding (...)
    • クラス定義 / モジュール定義 / シングルトンクラス定義
  • メソッド呼び出し
    • いわゆる通常のメソッド呼び出し
    • attr assignment (struct.field = foo)
    • array assignment with operator (ary[1] += foo)
    • attr assignment with operator (struct.field += foo)
    • assignment with && / || operator (foo &&= bar)
    • constant declaration with operator (A::B ||= 1)
    • match演算子(=~)
    • 匿名引数とforwarding
  • 制御構文
    • if
    • unless
    • flipflop
    • case when
    • while / until
    • for
    • retry / rescue / ensure
    • && / || / and / or
    • yield
    • return
    • super
    • break / next / redo
    • BEGIN
    • END
  • パターンマッチング
  • その他
    • alias
    • undef
    • defined?

  1. これがないとp(*, **, &)と区別がつかなくなります。



以上の内容はhttps://yui-knk.hatenablog.com/entry/2026/02/11/215300より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14