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


Ruby Parser開発日誌 (24-19) - parse.yが生成するノードを変える ー rescueとensure

19日目: rescueとensureを対応する

今回はrescueとensureに取り組みます。 難しい部分はおそらくコンパイルするときにcatch tableを用意したりする部分だと思うのですが、その辺は既存の仕組みに乗っかれるはずなのでノードの書き換え自体はそこまで難しくないんじゃないかなぁと淡い期待を胸に進めていきましょう。

ノードを眺める

書き換え前後のノードを眺めておきましょう。

def m1
  a
rescue
  b
else
  c
ensure
  d
end

# Before
# @ NODE_ENSURE (id: 9, line: 1, location: (2,2)-(8,3))
# +- nd_head:
# |   @ NODE_RESCUE (id: 8, line: 3, location: (2,2)-(6,3))
# |   +- nd_head:
# |   |   @ NODE_VCALL (id: 3, line: 2, location: (2,2)-(2,3))*
# |   |   +- nd_mid: :a
# |   +- nd_resq:
# |   |   @ NODE_RESBODY (id: 5, line: 4, location: (3,0)-(4,3))
# |   |   +- nd_args:
# |   |   |   (null node)
# |   |   +- nd_exc_var:
# |   |   |   (null node)
# |   |   +- nd_body:
# |   |   |   @ NODE_VCALL (id: 4, line: 4, location: (4,2)-(4,3))*
# |   |   |   +- nd_mid: :b
# |   |   +- nd_next:
# |   |       (null node)
# |   +- nd_else:
# |       @ NODE_VCALL (id: 6, line: 6, location: (6,2)-(6,3))*
# |       +- nd_mid: :c
# +- nd_ensr:
#     @ NODE_VCALL (id: 7, line: 8, location: (8,2)-(8,3))*
#     +- nd_mid: :d

# After
# @ BeginNode (location: (1,0)-(9,3))
# +-- begin_keyword_loc: nil
# +-- statements:
# |   @ StatementsNode (location: (2,2)-(2,3))
# |   +-- body: (length: 1)
# |       +-- @ CallNode (location: (2,2)-(2,3))
# |           +-- CallNodeFlags: variable_call, ignore_visibility
# |           +-- receiver: nil
# |           +-- call_operator_loc: nil
# |           +-- name: :a
# |           +-- message_loc: (2,2)-(2,3) = "a"
# |           +-- opening_loc: nil
# |           +-- arguments: nil
# |           +-- closing_loc: nil
# |           +-- equal_loc: nil
# |           +-- block: nil
# +-- rescue_clause:
# |   @ RescueNode (location: (3,0)-(4,3))
# |   +-- keyword_loc: (3,0)-(3,6) = "rescue"
# |   +-- exceptions: (length: 0)
# |   +-- operator_loc: nil
# |   +-- reference: nil
# |   +-- then_keyword_loc: nil
# |   +-- statements:
# |   |   @ StatementsNode (location: (4,2)-(4,3))
# |   |   +-- body: (length: 1)
# |   |       +-- @ CallNode (location: (4,2)-(4,3))
# |   |           +-- CallNodeFlags: variable_call, ignore_visibility
# |   |           +-- receiver: nil
# |   |           +-- call_operator_loc: nil
# |   |           +-- name: :b
# |   |           +-- message_loc: (4,2)-(4,3) = "b"
# |   |           +-- opening_loc: nil
# |   |           +-- arguments: nil
# |   |           +-- closing_loc: nil
# |   |           +-- equal_loc: nil
# |   |           +-- block: nil
# |   +-- subsequent: nil
# +-- else_clause:
# |   @ ElseNode (location: (5,0)-(7,6))
# |   +-- else_keyword_loc: (5,0)-(5,4) = "else"
# |   +-- statements:
# |   |   @ StatementsNode (location: (6,2)-(6,3))
# |   |   +-- body: (length: 1)
# |   |       +-- @ CallNode (location: (6,2)-(6,3))
# |   |           +-- CallNodeFlags: variable_call, ignore_visibility
# |   |           +-- receiver: nil
# |   |           +-- call_operator_loc: nil
# |   |           +-- name: :c
# |   |           +-- message_loc: (6,2)-(6,3) = "c"
# |   |           +-- opening_loc: nil
# |   |           +-- arguments: nil
# |   |           +-- closing_loc: nil
# |   |           +-- equal_loc: nil
# |   |           +-- block: nil
# |   +-- end_keyword_loc: (7,0)-(7,6) = "ensure"
# +-- ensure_clause:
# |   @ EnsureNode (location: (7,0)-(9,3))
# |   +-- ensure_keyword_loc: (7,0)-(7,6) = "ensure"
# |   +-- statements:
# |   |   @ StatementsNode (location: (8,2)-(8,3))
# |   |   +-- body: (length: 1)
# |   |       +-- @ CallNode (location: (8,2)-(8,3))
# |   |           +-- CallNodeFlags: variable_call, ignore_visibility
# |   |           +-- receiver: nil
# |   |           +-- call_operator_loc: nil
# |   |           +-- name: :d
# |   |           +-- message_loc: (8,2)-(8,3) = "d"
# |   |           +-- opening_loc: nil
# |   |           +-- arguments: nil
# |   |           +-- closing_loc: nil
# |   |           +-- equal_loc: nil
# |   |           +-- block: nil
# |   +-- end_keyword_loc: (9,0)-(9,3) = "end"
# +-- end_keyword_loc: (9,0)-(9,3) = "end"

図にするとこんな感じで、今までNODE_RESCUENODE_RESBODYで表現していたものをRescueNodeで、NODE_ENSURENODE_RESCUEをネストして表現していたものをBeginNodeで、それぞれ表現するようになります。

beginがないのにBeginNodeなのかと思うかもしれませんが、以下のコードのbegin ~ endBeginNodeで表現していることを踏まえると、さきほどのコードはbeginを省略したBeginNodeだと捉えているのだと思います。

def m2
  begin
    a
  rescue
    b
  else
    c
  ensure
    d
  end
end

1つ注意が必要な点としてm1m2DefNodeのbodyに設定されるノードの種別が異なります。

+-- @ DefNode (location: (1,0)-(9,3))
    +-- name: :m1
    +-- name_loc: (1,4)-(1,6) = "m1"
    +-- receiver: nil
    +-- parameters: nil
    +-- body:
    |   @ BeginNode (location: (1,0)-(9,3))

+-- @ DefNode (location: (113,0)-(123,3))
    +-- name: :m2
    +-- name_loc: (113,4)-(113,6) = "m2"
    +-- receiver: nil
    +-- parameters: nil
    +-- body:
    |   @ StatementsNode (location: (114,2)-(122,5))

parse.yを変更する

parse.yの生成規則ではbegin ~ rescue ~ else ~ ensure ~ endのうちbeginendを除いた部分をbodystmtと呼んでいます。

bodystmt  : compstmt(stmts)[body]
            lex_ctxt[ctxt]
            opt_rescue
            k_else
            compstmt(stmts)[elsebody]
            opt_ensure
          | compstmt(stmts)[body]
            lex_ctxt[ctxt]
            opt_rescue
            opt_ensure
          ;

こうしておくことでbeginが書かれているケースとそうでないケースでbodystmt生成規則を共有することができます。

// def m # primary
// rescue
// end
primary   | defn_head[head]
            f_arglist[args]
            bodystmt
            k_end

// def m
//   begin # primary
//   rescue
//   end
// end
primary   | k_begin
            bodystmt
            k_end

再度bodystmt生成規則をみるとopt_rescue, k_else compstmt, opt_ensureが揃ってからbodystmt全体を表すノードをセットアップするようになっているので、書き換えもその順番に行っていきましょう。

bodystmt   : compstmt(stmts)[body]
                  lex_ctxt[ctxt]
                  opt_rescue
                  k_else
                    {
                        if (!$opt_rescue) yyerror1(&@k_else, "else without rescue is useless");
                        next_rescue_context(&p->ctxt, &$ctxt, after_else);
                    }
                  compstmt(stmts)[elsebody]
                    {
                        next_rescue_context(&p->ctxt, &$ctxt, after_ensure);
                    }
                  opt_ensure
                    {
                        $$ = new_bodystmt(p, $body, $opt_rescue, $elsebody, $opt_ensure, &@$);
                    /*% ripper: bodystmt!($:body, $:opt_rescue, $:elsebody, $:opt_ensure) %*/
                    }

opt_rescue生成規則でRescueNodeをつくる

rescue周りの構文は今回書き換えるノードのなかでは最も複雑です。 構文の要素は以下のとおりです。

  1. E1, expr1のようにrescueする対象の例外を複数書くことができる
  2. => ex1で発生した例外を変数に代入することができる
  3. ex1のように例外をrescueしたあとで実行するコードを記述することができる
  4. rescueは複数書くことができる

書き換え前後のノードを掲載しておきます。

def m
  a
rescue E1, expr1 => ex1
  ex1
rescue E2, expr2 => ex2
  ex2
end

#+- nd_body:
#    @ NODE_RESCUE (id: 20, line: 1, location: (2,2)-(6,5))
#    +- nd_head:
#    |   @ NODE_VCALL (id: 3, line: 2, location: (2,2)-(2,3))*
#    |   +- nd_mid: :a
#    +- nd_resq:
#    |   @ NODE_RESBODY (id: 19, line: 3, location: (3,0)-(6,5))
#    |   +- nd_args:
#    |   |   @ NODE_LIST (id: 5, line: 3, location: (3,7)-(3,16))
#    |   |   +- as.nd_alen: 2
#    |   |   +- nd_head:
#    |   |   |   @ NODE_CONST (id: 4, line: 3, location: (3,7)-(3,9))
#    |   |   |   +- nd_vid: :E1
#    |   |   +- nd_head:
#    |   |   |   @ NODE_VCALL (id: 6, line: 3, location: (3,11)-(3,16))
#    |   |   |   +- nd_mid: :expr1
#    |   |   +- nd_next:
#    |   |       (null node)
#    |   +- nd_exc_var:
#    |   |   @ NODE_LASGN (id: 8, line: 3, location: (3,17)-(3,23))
#    |   |   +- nd_vid: :ex1
#    |   |   +- nd_value:
#    |   |       @ NODE_ERRINFO (id: 18, line: 3, location: (3,17)-(3,23))
#    |   +- nd_body:
#    |   |   @ NODE_LVAR (id: 9, line: 4, location: (4,2)-(4,5))*
#    |   |   +- nd_vid: :ex1
#    |   +- nd_next:
#    |       @ NODE_RESBODY (id: 17, line: 5, location: (5,0)-(6,5))
#    |       +- nd_args:
#    |       |   @ NODE_LIST (id: 11, line: 5, location: (5,7)-(5,16))
#    |       |   +- as.nd_alen: 2
#    |       |   +- nd_head:
#    |       |   |   @ NODE_CONST (id: 10, line: 5, location: (5,7)-(5,9))
#    |       |   |   +- nd_vid: :E2
#    |       |   +- nd_head:
#    |       |   |   @ NODE_VCALL (id: 12, line: 5, location: (5,11)-(5,16))
#    |       |   |   +- nd_mid: :expr2
#    |       |   +- nd_next:
#    |       |       (null node)
#    |       +- nd_exc_var:
#    |       |   @ NODE_LASGN (id: 14, line: 5, location: (5,17)-(5,23))
#    |       |   +- nd_vid: :ex2
#    |       |   +- nd_value:
#    |       |       @ NODE_ERRINFO (id: 16, line: 5, location: (5,17)-(5,23))
#    |       +- nd_body:
#    |       |   @ NODE_LVAR (id: 15, line: 6, location: (6,2)-(6,5))*
#    |       |   +- nd_vid: :ex2
#    |       +- nd_next:
#    |           (null node)

# |   +-- rescue_clause:
# |   |   @ RescueNode (location: (3,0)-(6,5))
# |   |   +-- exceptions: (length: 2)
# |   |   |   +-- @ ConstantReadNode (location: (3,7)-(3,9))
# |   |   |   |   +-- name: :E1
# |   |   |   +-- @ CallNode (location: (3,11)-(3,16))
# |   |   |       +-- name: :expr1
# |   |   +-- reference:
# |   |   |   @ LocalVariableTargetNode (location: (3,20)-(3,23))
# |   |   |   +-- name: :ex1
# |   |   |   +-- depth: 0
# |   |   +-- statements:
# |   |   |   @ StatementsNode (location: (4,2)-(4,5))
# |   |   |   +-- body: (length: 1)
# |   |   |       +-- @ LocalVariableReadNode (location: (4,2)-(4,5))
# |   |   |           +-- name: :ex1
# |   |   |           +-- depth: 0
# |   |   +-- subsequent:
# |   |       @ RescueNode (location: (5,0)-(6,5))
# |   |       +-- exceptions: (length: 2)
# |   |       |   +-- @ ConstantReadNode (location: (5,7)-(5,9))
# |   |       |   |   +-- name: :E2
# |   |       |   +-- @ CallNode (location: (5,11)-(5,16))
# |   |       |       +-- name: :expr2
# |   |       +-- reference:
# |   |       |   @ LocalVariableTargetNode (location: (5,20)-(5,23))
# |   |       |   +-- name: :ex2
# |   |       |   +-- depth: 0
# |   |       +-- then_keyword_loc: nil
# |   |       +-- statements:
# |   |       |   @ StatementsNode (location: (6,2)-(6,5))
# |   |       |   +-- body: (length: 1)
# |   |       |       +-- @ LocalVariableReadNode (location: (6,2)-(6,5))
# |   |       |           +-- name: :ex2
# |   |       |           +-- depth: 0
# |   |       +-- subsequent: nil

書き換え前後でどのように変わるかをまとめておきます。

  1. E1, expr1の部分はノードを列挙する構造で、書き換え前後で大きくは変わらない
  2. => ex1の部分は右辺がNODE_ERRINFOなローカル変数代入のノードから、右辺を持たないLocalVariableTargetNodeに変わる
  3. 例外をrescueしたあとで実行するコードの部分は書き換え前後で大きくは変わらない
  4. 複数rescueがあるケースはどちらもリスト構造で表現するので書き換え前後で大きくは変わらない

rescue(がネストしている部分)の生成規則は以下のとおりです。

opt_rescue  : k_rescue exc_list exc_var then
              compstmt(stmts)
              opt_rescue

1. 例外の列挙

1の例外を列挙する箇所はexc_listという生成規則になっています。

exc_list  : arg_value
              {
                  $$ = NEW_LIST($1, &@$);
              /*% ripper: rb_ary_new3(1, $:1) %*/
              }
          | mrhs
              {
                  if (!($$ = splat_array($1))) $$ = $1;
              }
          | none
          ;

mrhs      : args ',' arg_value
              {
                  $$ = last_arg_append(p, $args, $arg_value, &@$);
              /*% ripper: mrhs_add!(mrhs_new_from_args!($:args), $:arg_value) %*/
              }
          | args ',' tSTAR arg_value
              {
                  $$ = rest_arg_append(p, $args, $arg_value, &@$);
              /*% ripper: mrhs_add_star!(mrhs_new_from_args!($:args), $:arg_value) %*/
              }
          | tSTAR arg_value
              {
                  $$ = NEW_SPLAT($arg_value, &@$, &@tSTAR);
              /*% ripper: mrhs_add_star!(mrhs_new!, $:arg_value) %*/
              }
          ;

exc_list | mrhsのときの生成規則ではsplat_array関数を呼んで、rescue *[expr] => exの形のときにrescue expr => exに相当するノードを生成する最適化が入っています。 ノードの書き換え後はこの最適化をしなくなるので生成規則も修正します。

exc_list  : arg_value
              {
                  $$ = NEW_RB_ARRAY($1, &@$);
              /*% ripper: rb_ary_new3(1, $:1) %*/
              }
          | mrhs
          | none
          ;

2. 変数の宣言

=> ex1の部分は右辺を持たないLocalVariableTargetNodeにする必要があります。

exc_var   : tASSOC lhs
              {
                  $$ = $2;
              /*% ripper: $:2 %*/
              }
          | none
          ;

生成規則をみてみると該当する箇所はlhsになっています。 lhsというのはa = 1などの左辺を表す非終端機号で、これはTargetNodeではなくWriteNodeを生成します。 TargetNodeを生成するのはa, b = fooなどの左辺を表すmlhs_nodeです。

ではexc_var : tASSOC mlhs_nodeにすればいいかというと、そうもいかない理由があります。 lhsmlhs_nodeを比較してみると生成規則の定義は全くおなじで、ごく一部のアクションだけが異なることがわかります。

mlhs_node : user_or_keyword_variable
          | primary_value '[' opt_call_args rbracket
          | primary_value call_op ident_or_const
              {
                  anddot_multiple_assignment_check(p, &@2, $2);
                  $$ = attrset(p, $1, $2, $3, &@$);
              /*% ripper: field!($:1, $:2, $:3) %*/
              }
          | primary_value tCOLON2 tIDENTIFIER
          | primary_value tCOLON2 tCONSTANT
          | tCOLON3 tCONSTANT
          | backref
          ;

lhs       : user_or_keyword_variable
          | primary_value '[' opt_call_args rbracket
          | primary_value call_op ident_or_const
              {
                  $$ = attrset(p, $1, $2, $3, &@$);
              /*% ripper: field!($:1, $:2, $:3) %*/
              }
          | primary_value tCOLON2 tIDENTIFIER
          | primary_value tCOLON2 tCONSTANT
          | tCOLON3 tCONSTANT
          | backref
          ;

anddot_multiple_assignment_check関数は&.を弾くための関数です。 確認をしてみるとたしかにmultiple assignmentでは&.を左辺に書くことができませんが、rescue Ex => varvarには&.を書くことができます。

a, b&.c = foo
# -e:1: &. inside multiple assignment destination
# ruby: compile error (SyntaxError)
def m
rescue E1 => expr1.a
  ex1
rescue E2 => expr2&.a
  ex2
end
# valid code

mlhs_nodeを使うことはできないので、非終端機号としてはlhsを使いつつアクションではwrite2target関数を用いてノードを変換することにします。

exc_var   : tASSOC lhs
              {
                  $$ = write2target(p, $2);
              /*% ripper: $:2 %*/
              }
          | none
          ;

write2targetの実装で注意が必要なのはメソッド呼び出しと定数に関するノードの変換です。

メソッド呼び出しの場合はa[i]s.fでTargetNodeが異なる点に注意します。

WriteNode TargetNode
a[i] CallNode IndexTargetNode
s.f CallNode CallTargetNode

定数の場合はConstantWriteConstantPathWriteに分けて考えます。 ConstantWriteConstantTargetに変換します。 ConstantPathWriteの場合は直下のConstantPathを1つ潰してConstantPathTargetを生成します。

WriteNode TargetNode
::A ConstantPathWrite(ConstantPath(:A)) ConstantPathTarget(:A)
A ConstantWrite(:A) ConstantTarget(:A)
::A::B ConstantPathWrite(ConstantPath(:B, ConstantPath(:A))) ConstantPathTarget(:B, ConstantPath(:A))
A::B ConstantPathWrite(ConstantPath(:B, ConstantRead(:A))) ConstantPathTarget(:B, ConstantRead(:A))
a::B ConstantPathWrite(ConstantPath(:B, Call)) ConstantPathTarget(:B, Call)

実装はざっくりこんな感じです。

static rb_node_t *
write2target(struct parser_params *p, rb_node_t *node)
{
    switch (RB_NODE_TYPE(node)) {
      case RB_LOCAL_VARIABLE_WRITE_NODE: {
        rb_local_variable_write_node_t *cast = RB_NODE_LOCAL_VARIABLE_WRITE(node);
        return NEW_LTARGET(cast->name, nd_code_loc(cast));
      }
      ...
      case RB_CALL_NODE: {
        rb_call_node_t *cast = RB_NODE_CALL(node);
        if (rb_node_get_fl(node) & RB_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) {
            // `struct.field =`
            return NEW_RB_CALL_TARGET(cast->receiver, cast->name, rb_node_get_fl(cast), nd_code_loc(cast));
        }
        else {
            // `a[0] =`
            return NEW_RB_INDEX_TARGET(cast->receiver, cast->arguments, rb_node_get_fl(cast), nd_code_loc(cast));
        }
      }
      default:
        rb_bug("unexpected node: %s", rb_node_type_to_str(RB_NODE_TYPE(node)));
        UNREACHABLE_RETURN(0);
    }
}

k_else compstmtからElseNodeをつくる

ElseNodeを生成する必要があるのでbodystmtのアクションで生成してnew_bodystmt関数に渡すようにします。 位置情報の修正などはあとでやりましょう。

bodystmt  : compstmt(stmts)[body]
            lex_ctxt[ctxt]
            opt_rescue
            k_else
            compstmt(stmts)[elsebody]
            opt_ensure
              {
                  $elsebody = NEW_RB_ELSE($elsebody, &NULL_LOC, &@k_else, &NULL_LOC);
                  $$ = new_bodystmt(p, $body, $opt_rescue, $elsebody, $opt_ensure, &@$);
              /*% ripper: bodystmt!($:body, $:opt_rescue, $:elsebody, $:opt_ensure) %*/
              }

opt_ensure生成規則でEnsureNodeをつくる

ensure ...の部分はopt_ensure生成規則に切り出されているので、そのアクションでEnsureNodeを作ればいいでしょう。 stmtsのチェックをしてからEnsureNodeを作るようにしたいので少し順番を入れ替えています1

 opt_ensure     : k_ensure stmts terms?
                     {
                         p->ctxt.in_rescue = $1.in_rescue;
-                        $$ = $2;
-                        void_expr(p, void_stmts(p, $$));
+                        void_expr(p, void_stmts(p, $2));
+                        $$ = NEW_RB_ENSURE($2, &@$);
                     /*% ripper: ensure!($:2) %*/
                     }

rescue, ensure, elseに対応したので生成されるノードを確認しておきましょう。

class C
  a
rescue E => ex
  ex
else
  c
ensure
  d
end

# ./miniruby --parser=parse.y --dump=p ../../test.rb

# @ ProgramNode (location: (1,0)-(9,3))
# +-- locals: []
# +-- statements:
#     @ StatementsNode (location: (1,0)-(9,3))
#     +-- body: (length: 1)
#         +-- @ ClassNode (location: (1,0)-(9,3))*
#             +-- locals: [:ex]
#             +-- class_keyword_loc: (1,0)-(1,5) = ""
#             +-- constant_path:
#             |   @ ConstantReadNode (location: (1,6)-(1,7))
#             |   +-- name: :C
#             +-- inheritance_operator_loc: nil
#             +-- superclass: nil
#             +-- body:
#             |   @ BeginNode (location: (1,7)-(8,3))
#             |   +-- begin_keyword_loc: nil
#             |   +-- statements:
#             |   |   @ StatementsNode (location: (2,2)-(2,3))
#             |   |   +-- body: (length: 1)
#             |   |       +-- @ CallNode (location: (2,2)-(2,3))*
#             |   |           +-- CallNodeFlags: variable_call
#             |   |           +-- receiver: nil
#             |   |           +-- call_operator_loc: nil
#             |   |           +-- name: :a
#             |   |           +-- message_loc: nil
#             |   |           +-- opening_loc: nil
#             |   |           +-- arguments: nil
#             |   |           +-- closing_loc: nil
#             |   |           +-- equal_loc: nil
#             |   |           +-- block: nil
#             |   +-- rescue_clause:
#             |   |   @ RescueNode (location: (3,0)-(4,4))
#             |   |   +-- keyword_loc: (0,-1)-(0,-1) = ""
#             |   |   +-- exceptions: (length: 1)
#             |   |   |   +-- @ ConstantReadNode (location: (3,7)-(3,8))
#             |   |   |       +-- name: :E
#             |   |   +-- operator_loc: nil
#             |   |   +-- reference:
#             |   |   |   @ LocalVariableTargetNode (location: (3,12)-(3,14))
#             |   |   |   +-- name: :ex
#             |   |   |   +-- depth: 0
#             |   |   +-- then_keyword_loc: nil
#             |   |   +-- statements:
#             |   |   |   @ StatementsNode (location: (4,2)-(4,4))
#             |   |   |   +-- body: (length: 1)
#             |   |   |       +-- @ LocalVariableReadNode (location: (4,2)-(4,4))*
#             |   |   |           +-- name: :ex
#             |   |   |           +-- depth: 0
#             |   |   +-- subsequent: nil
#             |   +-- else_clause:
#             |   |   @ ElseNode (location: (0,-1)-(0,-1))
#             |   |   +-- else_keyword_loc: (5,0)-(5,4) = ""
#             |   |   +-- statements:
#             |   |   |   @ StatementsNode (location: (6,2)-(6,3))
#             |   |   |   +-- body: (length: 1)
#             |   |   |       +-- @ CallNode (location: (6,2)-(6,3))*
#             |   |   |           +-- CallNodeFlags: variable_call
#             |   |   |           +-- receiver: nil
#             |   |   |           +-- call_operator_loc: nil
#             |   |   |           +-- name: :c
#             |   |   |           +-- message_loc: nil
#             |   |   |           +-- opening_loc: nil
#             |   |   |           +-- arguments: nil
#             |   |   |           +-- closing_loc: nil
#             |   |   |           +-- equal_loc: nil
#             |   |   |           +-- block: nil
#             |   |   +-- end_keyword_loc: nil
#             |   +-- ensure_clause:
#             |   |   @ EnsureNode (location: (7,0)-(8,3))
#             |   |   +-- ensure_keyword_loc: (0,-1)-(0,-1) = ""
#             |   |   +-- statements:
#             |   |   |   @ StatementsNode (location: (8,2)-(8,3))
#             |   |   |   +-- body: (length: 1)
#             |   |   |       +-- @ CallNode (location: (8,2)-(8,3))*
#             |   |   |           +-- CallNodeFlags: variable_call
#             |   |   |           +-- receiver: nil
#             |   |   |           +-- call_operator_loc: nil
#             |   |   |           +-- name: :d
#             |   |   |           +-- message_loc: nil
#             |   |   |           +-- opening_loc: nil
#             |   |   |           +-- arguments: nil
#             |   |   |           +-- closing_loc: nil
#             |   |   |           +-- equal_loc: nil
#             |   |   |           +-- block: nil
#             |   |   +-- end_keyword_loc: (0,-1)-(0,-1) = ""
#             |   +-- end_keyword_loc: nil
#             +-- end_keyword_loc: (9,0)-(9,3) = ""
#             +-- name: :C

よさそうですね。

begin ... endとmodifier rescue

関連する構文にbegin ... endと後置rescueがあるので、それらも書き換えておきましょう。

まずはbegin ... endから。 これはNEW_BEGINでノードを生成していたのをやめるだけです。

primary | k_begin
            {
                CMDARG_PUSH(0);
            }
          bodystmt
          k_end
            {
                CMDARG_POP();
                set_line_body($3, @1.end_pos.lineno);
                // $$ = NEW_BEGIN($3, &@$);
                $$ = $3;
                nd_set_line($$, @1.end_pos.lineno);
            /*% ripper: begin!($:3) %*/
            }

続いて後置rescueですが、こちらはRescueModifierNodeという専用のノードが追加されました。 a rescue bと書いた場合には、左のaexpressionフィールドに、右のbrescue_expressionにセットされます。 RescueModifierNodeを生成するマクロを定義して、それを用いてノードを生成するようにすればよいでしょう。

                 | stmt modifier_rescue after_rescue stmt
                     {
                         p->ctxt.in_rescue = $3.in_rescue;
                         NODE *resq;
                         YYLTYPE loc = code_loc_gen(&@2, &@4);
-                        resq = NEW_RESBODY(0, 0, remove_begin($4), 0, &loc);
-                        $$ = NEW_RESCUE(remove_begin($1), resq, 0, &@$);
+                        $$ = NEW_RB_RESCUE_MODIFIER(remove_begin($1), remove_begin($4), &@$);
                     /*% ripper: rescue_mod!($:1, $:4) %*/
                     }

ちょっと長くなったので今日はここまで。

まとめ

今日の成果です。

  • begin ... rescue ... ensure ... else ... endのノードを書き換えた
  • 後置rescueのノードを書き換えた

  1. 現時点でvoid_stmtsvoid_exprは書き換え前のノードを想定して動いています。構造体が違ってたまたまnode typeのenumが一致しないので動いています。動かなくなるまではそのまま進みましょう。



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

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