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


Ruby Parser開発日誌 (24-30) - parse.yが生成するノードを変える ー hashリテラル

30日目: hashリテラル

前回は正規表現リテラルの流れから=~演算子を対応し、時間があったのでlambdaについても対応したのでした。 今回はhashリテラルの対応をしたいと思います。

parserの変更

じつはhashリテラルの対応はほとんど完了しています。

primary      | tLBRACE assoc_list '}'
                 {
                     $$ = new_hash(p, $2, &@$);
                     RNODE_HASH($$)->nd_brace = TRUE;
                 /*% ripper: hash!($:2) %*/
                 }

static rb_node_t *
new_hash(struct parser_params *p, rb_array_node_t *hash, const YYLTYPE *loc)
{
    if (hash) warn_duplicate_keys(p, hash);
    return hash ? array2hash(p, hash, loc) : NEW_RB_HASH(loc);
}

nd_braceというフラグはh= {k: :v}のようなhashリテラルm(k: :v)[nil, k: :v]といったキーワードを区別するためのフラグでした。 ノードの書き換えにより前者はHashNode、後者はKeywordHashNodeになるためノードのタイプから区別がつくようになります1。 そのためこのフラグへの書き込みは削除してしまいましょう。

@@ -4869,7 +4869,6 @@ primary           : inline_primary
             | tLBRACE assoc_list '}'
                 {
                     $$ = new_hash(p, $2, &@$);
-                    RNODE_HASH($$)->nd_brace = TRUE;
                 /*% ripper: hash!($:2) %*/
                 }

生成されるノードも問題なさそうです。

c = nil

# @ HashNode (location: (2,0)-(2,22))*
# +-- elements: (length: 2)
# |   +-- @ AssocNode (location: (2,1)-(2,8))
# |   |   +-- key:
# |   |   |   @ SymbolNode (location: (2,1)-(2,3))
# |   |   |   +-- unescaped: "a"
# |   |   +-- value:
# |   |   |   @ TrueNode (location: (2,4)-(2,8))
# |   +-- @ AssocNode (location: (2,10)-(2,21))
# |       +-- key:
# |       |   @ SymbolNode (location: (2,10)-(2,12))
# |       |   +-- unescaped: "b"
# |       +-- value:
# |       |   @ FalseNode (location: (2,16)-(2,21))
{a: true, :b => false}

# @ HashNode (location: (3,0)-(3,26))*
# +-- elements: (length: 3)
# |   +-- @ AssocNode (location: (3,1)-(3,8))
# |   |   +-- key:
# |   |   |   @ SymbolNode (location: (3,1)-(3,3))
# |   |   |   +-- unescaped: "a"
# |   |   +-- value:
# |   |   |   @ TrueNode (location: (3,4)-(3,8))
# |   +-- @ AssocNode (location: (3,10)-(3,21))
# |   |   +-- key:
# |   |   |   @ SymbolNode (location: (3,10)-(3,12))
# |   |   |   +-- unescaped: "b"
# |   |   +-- value:
# |   |   |   @ FalseNode (location: (3,16)-(3,21))
# |   +-- @ AssocNode (location: (3,23)-(3,25))
# |       +-- key:
# |       |   @ SymbolNode (location: (3,23)-(3,25))
# |       |   +-- unescaped: "c"
# |       +-- value:
# |       |   @ ImplicitNode (location: (3,23)-(3,25))
# |       |   +-- value:
# |       |       @ LocalVariableReadNode (location: (3,23)-(3,25))
# |       |       +-- name: :c
{a: true, :b => false, c:}

hashをcompileする

compile.cでは引数周りのコンパイル時に実装したcompile_hash関数を呼び出せばよいでしょう。

      case RB_HASH_NODE: {
        CHECK(compile_hash(iseq, ret, node, &RB_NODE_HASH(node)->elements, FALSE, popped) >= 0);
        break;
      }

実際にコンパイルしてみましょう。

# 0000 duphash                                {a: true, b: false}       (   1)[Li]
{a: true, :b => false}

# 0000 putobject                              :m                        (   4)[Li]
# 0002 putself
# 0003 opt_send_without_block                 <calldata!mid:obj, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0005 opt_send_without_block                 <calldata!mid:m, argc:0, ARGS_SIMPLE>
# 0007 newhash                                2
{m: obj.m}

# 0000 putobject                              :m                        (   4)[Li]
# 0002 putself
# 0003 opt_send_without_block                 <calldata!mid:obj, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0005 opt_send_without_block                 <calldata!mid:m, argc:0, ARGS_SIMPLE>
# 0007 newhash                                2
{a: true, **kw}

c = nil
{a: true, :b => false, c:}
#=> ../../test.rb:2: iseq_compile_each: unknown node (RB_IMPLICIT_NODE)

{a: true, :b => false, c:}のケースではあたらしくImplicitNodeで表現するようになったので、その対応が必要です。

c = nil

#  @ HashNode (location: (62,0)-(62,8))*
#  +-- elements: (length: 2)
#  |   +-- @ AssocNode (location: (62,1)-(62,3))
#  |   |   +-- key:
#  |   |   |   @ SymbolNode (location: (62,1)-(62,3))
#  |   |   |   +-- unescaped: "c"
#  |   |   +-- value:
#  |   |   |   @ ImplicitNode (location: (62,1)-(62,3))
#  |   |   |   +-- value:
#  |   |   |       @ LocalVariableReadNode (location: (62,1)-(62,3))
#  |   |   |       +-- name: :c
#  |   +-- @ AssocNode (location: (62,5)-(62,7))
#  |       +-- key:
#  |       |   @ SymbolNode (location: (62,5)-(62,7))
#  |       |   +-- unescaped: "d"
#  |       +-- value:
#  |       |   @ ImplicitNode (location: (62,5)-(62,7))
#  |       |   +-- value:
#  |       |       @ CallNode (location: (62,5)-(62,7))
#  |       |       +-- receiver: nil
#  |       |       +-- name: :d
#  |       |       +-- block: nil
{c:, d:}

といってもImplicitNodeLocalVariableReadNodeないしCallNodeないし適切なノードを保持しているので、valueに入っているノードをコンパイルすればいいでしょう。

      case RB_IMPLICIT_NODE: {
        CHECK(COMPILE_(ret, "hash value (implicit)", RB_NODE_IMPLICIT(node)->value, popped));
        break;
      }

生成されるバイトコードも問題なさそうです。

# == disasm: #<ISeq:<main>@../../test.rb:1 (1,0)-(2,8)>
# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] c@0
# 0000 putnil                                                           (   1)[Li]
# 0001 setlocal_WC_0                          c@0
# 0003 putobject                              :c                        (   2)[Li]
# 0005 getlocal_WC_0                          c@0
# 0007 putobject                              :d
# 0009 putself
# 0010 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0012 newhash                                4
# 0014 leave

c = nil
{c:, d:}

まとめ

今日の成果です。


  1. パターンマッチングではHashPatternNodeを使うようになるので、そちらとも区別がつきます。



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

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