40日目: Variable patternと後置if, unlessに対応する
前回はValue patternの対応をしました。 今回はVariable patternと後置if, unlessに取り組みます。
Variable pattern
Variable patternもしくはvariable captureとは、パターンマッチングによるローカル変数の束縛のことです。
以下のコードではa = v相当の処理が行われます。
def m(v) case v # Before # # @ NODE_IN (id: 8, line: 3, location: (3,2)-(6,9)) # +- nd_head: # | @ NODE_LASGN (id: 5, line: 3, location: (3,5)-(3,6)) # | +- nd_vid: :a # | +- nd_value: # | (null node) # After # # @ InNode (location: (3,2)-(4,5)) # +-- pattern: # | @ LocalVariableTargetNode (location: (3,5)-(3,6)) # | +-- name: :a # | +-- depth: 0 in a a else :else end end
対応するノードは書き換え前がNODE_LASGNで、書き換え後がLocalVariableTargetNodeです。
parse.yではアクションで呼び出す関数を変更してTargetNodeが生成されるようにします。
@@ -6372,7 +6372,7 @@ p_variable : tIDENTIFIER { error_duplicate_pattern_variable(p, $1, &@1); /*% ripper: var_field!($:1) %*/ - $$ = assignable(p, $1, 0, &@$); + $$ = assignable_target(p, $1, &@$); } ;
Variable patternの場合のバイトコードはsetlocalとjumpからなります。
# `case v` # # 0000 putnil ( 3)[LiCa] # 0001 getlocal_WC_0 v@0 ( 2) # 0003 dup ( 3) # `in a` # # 0004 setlocal_WC_0 a@1 # 0006 jump 13 # `else` # # 0008 pop ( 6) # 0009 pop # 0010 putobject :else[Li] # 0012 leave ( 8)[Re] # `in a`のbody # # 0013 adjuststack 2 ( 3) # 0015 getlocal_WC_0 a@1 ( 4)[Li] # 0017 leave ( 8)[Re] def m(v) case v in a a else :else end end
書き換え前のコンパイラをみると以下の3つを行なっています。
- Alternative patternのときに変数の束縛を禁止するためのチェックをする
setlocal命令を生成するためにindexとlevelを解決するsetlocal命令とjump命令を生成する
case NODE_LASGN: { struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); ID id = RNODE_LASGN(node)->nd_vid; int idx = ISEQ_BODY(body->local_iseq)->local_table_size - get_local_var_idx(iseq, id); if (in_alt_pattern) { const char *name = rb_id2name(id); if (name && strlen(name) > 0 && name[0] != '_') { COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")", rb_id2str(id)); return COMPILE_NG; } } ADD_SETLOCAL(ret, line_node, idx, get_lvar_level(iseq)); ADD_INSNL(ret, line_node, jump, matched); break; } case NODE_DASGN: { int idx, lv, ls; ID id = RNODE_DASGN(node)->nd_vid; idx = get_dyna_var_idx(iseq, id, &lv, &ls); if (in_alt_pattern) { const char *name = rb_id2name(id); if (name && strlen(name) > 0 && name[0] != '_') { COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")", rb_id2str(id)); return COMPILE_NG; } } if (idx < 0) { COMPILE_ERROR(ERROR_ARGS "NODE_DASGN: unknown id (%"PRIsVALUE")", rb_id2str(id)); return COMPILE_NG; } ADD_SETLOCAL(ret, line_node, ls - idx, lv); ADD_INSNL(ret, line_node, jump, matched); break; }
ノードの修正にあわせてcompile_lasgn_lhs関数を使うように書き換えます。
case RB_LOCAL_VARIABLE_TARGET_NODE: { ID id = RB_NODE_LOCAL_VARIABLE_TARGET(node)->name; if (in_alt_pattern) { const char *name = rb_id2name(id); if (name && strlen(name) > 0 && name[0] != '_') { COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")", rb_id2str(id)); return COMPILE_NG; } } CHECK(compile_lasgn_lhs(iseq, ret, node, id)); ADD_INSNL(ret, line_node, jump, matched); break; }
buildして動作を確認します。
def m(v) case v in a a else :else end end p m(:t) #=> :t p m(:f) #=> :f p m(:e) #=> :e
後置ifと後置unless
パターンマッチングではpatternのあとにifやunlessを書くことができます。
def m(v, cond) case v in a if cond a else :else end end p m(:t, true) #=> :t p m(:t, false) #=> :else
書き換え前のノードはNODE_IF、書き換え後のノードはIfNodeです。
書き換え前後で構造が変化しないのでparse.yの修正は不要です。
# Before # # @ NODE_IN (id: 11, line: 3, location: (3,2)-(6,9)) # +- nd_head: # | @ NODE_IF (id: 8, line: 3, location: (3,5)-(3,14))* # | +- nd_cond: # | | @ NODE_LVAR (id: 7, line: 3, location: (3,10)-(3,14)) # | | +- nd_vid: :cond # | +- nd_body: # | | @ NODE_LASGN (id: 6, line: 3, location: (3,5)-(3,6)) # | | +- nd_vid: :a # | | +- nd_value: # | | (null node) # After # # @ InNode (location: (3,2)-(4,5)) # +-- pattern: # | @ IfNode (location: (3,5)-(3,14)) # | +-- predicate: # | | @ LocalVariableReadNode (location: (3,10)-(3,14)) # | | +-- name: :cond # | +-- statements: # | | @ StatementsNode (location: (3,5)-(3,6)) # | | +-- body: (length: 1) # | | +-- @ LocalVariableTargetNode (location: (3,5)-(3,6)) # | | +-- name: :a in a if cond
生成されるバイトコードはifのbodyに当たるsetlocal aを実行してから条件部分のgetlocal condとbranchifを行うようになっています。
# `case v` # # 0000 putnil ( 3)[LiCa] # 0001 getlocal_WC_0 v@0 ( 2) # 0003 dup ( 3) # `in a if cond` # # 0004 setlocal_WC_0 a@2 # 0006 getlocal_WC_0 cond@1 # 0008 branchif 15 # `else ...` # # 0010 pop ( 6) # 0011 pop # 0012 putobject :else[Li] # 0014 leave ( 8)[Re] # `in a if cond`のbody # # 0015 adjuststack 2 ( 3) # 0017 getlocal_WC_0 a@2 ( 4)[Li] # 0019 leave ( 8)[Re] def m(v, cond) case v in a if cond a else :else end end
compile.cの変更は構造体に合わせて修正をするだけです。
case RB_IF_NODE: case RB_UNLESS_NODE: { LABEL *match_failed; match_failed = unmatched; CHECK(iseq_compile_pattern_match(iseq, ret, (NODE *)RB_NODE_IF(node)->statements, unmatched, in_single_pattern, in_alt_pattern, base_index, use_deconstructed_cache)); CHECK(COMPILE(ret, "case in if", RB_NODE_IF(node)->predicate)); if (in_single_pattern) { LABEL *match_succeeded; match_succeeded = NEW_LABEL(line); ADD_INSN(ret, line_node, dup); if (nd_type_p(node, NODE_IF)) { ADD_INSNL(ret, line_node, branchif, match_succeeded); } else { ADD_INSNL(ret, line_node, branchunless, match_succeeded); } ADD_INSN1(ret, line_node, putobject, rb_fstring_lit("guard clause does not return true")); // (1) ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (1) */)); // (2) ADD_INSN1(ret, line_node, putobject, Qfalse); ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2 /* (1), (2) */)); ADD_INSN(ret, line_node, pop); ADD_INSN(ret, line_node, pop); ADD_LABEL(ret, match_succeeded); } if (nd_type_p(node, RB_IF_NODE)) { ADD_INSNL(ret, line_node, branchunless, match_failed); } else { ADD_INSNL(ret, line_node, branchif, match_failed); } ADD_INSNL(ret, line_node, jump, matched); break; }
実際にコードを実行して挙動を確認します。
def m(v, cond) case v in a if cond a else :else end end p m(:t, true) #=> :t p m(:t, false) #=> :else
まとめ
- Variable patternに対応した
- 後置ifと後置unlessに対応した
パターンマッチング全体の進捗は以下の通りです。
Value patternp_primitive ("str",1,:symなど)range_expr (1...3など)p_var_ref (^varなど)p_expr_ref (^(cmd 1, 2)など)p_const (A,::A,A::Bなど)
Variable pattern- Array pattern
- Hash pattern
- Find pattern
- Alternative pattern
- As pattern
後置ifと後置unless