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:}
といってもImplicitNodeはLocalVariableReadNodeないし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:}
まとめ
今日の成果です。
- hashリテラルに対応した
-
パターンマッチングでは
HashPatternNodeを使うようになるので、そちらとも区別がつきます。↩