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


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

27日目: xstringと式展開を含んだsymbolに対応する

前回は%記法、文字列の結合、ヒアドキュメントなどの対応をしました。 今回は`cmd`のような文字列(通称xstring)と式展開を含んだsymbolに取り組みたいと思います。

parserを変更する

xstringに関連する生成規則をみていきましょう。

// '`' cmd '`' を表す
xstring     : tXSTRING_BEG xstring_contents tSTRING_END
                    {
                        $$ = new_xstring(p, heredoc_dedent(p, $2), &@$);
                    /*% ripper: $:2 %*/
                        if (p->heredoc_indent > 0) {
                        /*% ripper: heredoc_dedent!($:$, INT2NUM(%{p->heredoc_indent})) %*/
                            p->heredoc_indent = 0;
                        }
                    /*% ripper: xstring_literal!($:$) %*/
                    }
                ;

// cmd や #{var} などを表す
xstring_contents: /* none */
                    {
                        $$ = 0;
                    /*% ripper: xstring_new! %*/
                    }
                | xstring_contents string_content
                    {
                        $$ = literal_concat(p, $1, $2, &@$);
                    /*% ripper: xstring_add!($:1, $:2) %*/
                    }
                ;

なんだか文字列のときと同じような生成規則、同じようなアクションになっています。 new_xstring関数はというと、xstring_contentsの生成するノードの種類に応じてそれらをxstring用のノードに変換しています。

static NODE *
new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc)
{
    if (!node) {
        NODE *xstr = NEW_XSTR(STRING_NEW0(), loc);
        return xstr;
    }
    switch (nd_type(node)) {
      case NODE_STR:
        nd_set_type(node, NODE_XSTR);
        nd_set_loc(node, loc);
        break;
      case NODE_DSTR:
        nd_set_type(node, NODE_DXSTR);
        nd_set_loc(node, loc);
        break;
      default:
        node = NEW_DXSTR(0, 1, NEW_LIST(node, loc), loc);
        break;
    }
    return node;
}

nd_set_typeでノードのtypeだけを変更しているのは、NODE_STRNODE_XSTRNODE_DSTRNODE_DXSTRが同じ構造をしているからです。 書き換え後でいうと、StringNodeXStringNodeInterpolatedStringNodeInterpolatedXStringNodeは同じ構造なので引き続きtypeだけを変更するようにしておきましょう。

static rb_node_t *
new_xstring(struct parser_params *p, rb_node_t *node, const YYLTYPE *loc)
{
    if (!node) {
        rb_node_t *xstr = NEW_RB_X_STRING(STRING_NEW0(), loc);
        return xstr;
    }
    switch (nd_type(node)) {
      case RB_STRING_NODE:
        rb_nd_set_type(node, RB_X_STRING_NODE);
        rb_nd_set_loc(node, loc);
        break;
      case RB_INTERPOLATED_STRING_NODE:
        rb_nd_set_type(node, RB_INTERPOLATED_X_STRING_NODE);
        rb_nd_set_loc(node, loc);
        break;
      default:
        node = NEW_RB_INTERPOLATED_X_STRING(node, loc);
        break;
    }
    return node;
}

compilerを変更する

compile.cはというと文字列をスタックに積んで`メソッドを呼びだすようなバイトコードを生成します。

      case NODE_XSTR:{
        ADD_CALL_RECEIVER(ret, node);
        VALUE str = rb_node_str_string_val(node);
        ADD_INSN1(ret, node, putobject, str);
        RB_OBJ_WRITTEN(iseq, Qundef, str);
        ADD_CALL(ret, node, idBackquote, INT2FIX(1));

        if (popped) {
            ADD_INSN(ret, node, pop);
        }
        break;
      }
      case NODE_DXSTR:{
        ADD_CALL_RECEIVER(ret, node);
        compile_dstr(iseq, ret, node);
        ADD_CALL(ret, node, idBackquote, INT2FIX(1));

        if (popped) {
            ADD_INSN(ret, node, pop);
        }
        break;
      }

特に難しいことはないので素直に書き換えます。

      case RB_X_STRING_NODE: {
        ADD_CALL_RECEIVER(ret, node);
        VALUE str = rb_node_str_string_val2(node);
        ADD_INSN1(ret, node, putobject, str);
        RB_OBJ_WRITTEN(iseq, Qundef, str);
        ADD_CALL(ret, node, idBackquote, INT2FIX(1));

        if (popped) {
            ADD_INSN(ret, node, pop);
        }
        break;
      }
      case RB_INTERPOLATED_X_STRING_NODE: {
        ADD_CALL_RECEIVER(ret, node);
        compile_dstr(iseq, ret, node);
        ADD_CALL(ret, node, idBackquote, INT2FIX(1));

        if (popped) {
            ADD_INSN(ret, node, pop);
        }
        break;
      }

生成されるノードとバイトコードを確認しておきます。 よさそうですね。

# +-- @ XStringNode (location: (1,0)-(1,5))*
# |   +-- unescaped: "cmd"
#
# 0000 putself                                                          (   1)[Li]
# 0001 putobject                              "cmd"
# 0003 opt_send_without_block                 <calldata!mid:`, argc:1, FCALL|ARGS_SIMPLE>
# 0005 pop
`cmd`

# +-- @ InterpolatedXStringNode (location: (2,0)-(2,14))*
# |   +-- parts: (length: 3)
# |   |   +-- @ StringNode (location: (2,1)-(2,4))
# |   |   |   +-- unescaped: "cmd"
# |   |   +-- @ EmbeddedStatementsNode (location: (2,4)-(2,10))
# |   |   +-- @ StringNode (location: (2,10)-(2,13))
# |   |       +-- unescaped: "cmd"
#
# 0006 putself                                                          (   2)[Li]
# 0007 putobject                              "cmd"
# 0009 putself
# 0010 opt_send_without_block                 <calldata!mid:var, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0012 dup
# 0013 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
# 0015 anytostring
# 0016 putobject                              "cmd"
# 0018 concatstrings                          3
# 0020 opt_send_without_block                 <calldata!mid:`, argc:1, FCALL|ARGS_SIMPLE>
# 0022 pop
`cmd#{var}cmd`

# +-- @ XStringNode (location: (3,0)-(3,2))*
#     +-- unescaped: ""
#
# 0023 putself                                                          (   3)[Li]
# 0024 putobject                              ""
# 0026 opt_send_without_block                 <calldata!mid:`, argc:1, FCALL|ARGS_SIMPLE>
# 0028 leave
``

式展開を含んだsymbolに対応する

xstringの対応がさくっと終わったので、式展開を含んだsymbolもやってしまいましょう。 式展開を含んだsymbolというのは例えば以下のような書き方で作ることができます。

var = "b"

p :"a#{var}c"
#=> :abc

関連する生成規則とそこで呼ばれている関数の定義は以下のとおりです。 いままで見てきたstringやxstringと同じような構造になっていることがわかります。

dsym       : tSYMBEG string_contents tSTRING_END
                    {
                        SET_LEX_STATE(EXPR_END);
                        $$ = dsym_node(p, $2, &@$);
                    /*% ripper: dyna_symbol!($:2) %*/
                    }
                ;


static NODE*
dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
{
    if (!node) {
        return NEW_RB_SYMBOL(STR_NEW0(), loc);
    }

    switch (nd_type(node)) {
      case NODE_DSTR:
        nd_set_type(node, NODE_DSYM);
        nd_set_loc(node, loc);
        break;
      case NODE_STR:
        node = str_to_sym_node(p, node, loc);
        break;
      default:
        node = NEW_DSYM(0, 1, NEW_LIST(node, loc), loc);
        break;
    }
    return node;
}

dsym_node関数はいままでと同じように書き換えればよいでしょう。

static rb_node_t*
dsym_node(struct parser_params *p, rb_node_t *node, const YYLTYPE *loc)
{
    if (!node) {
        return NEW_RB_SYMBOL(STR_NEW0(), loc);
    }

    switch (nd_type(node)) {
      case RB_INTERPOLATED_STRING_NODE:
        rb_nd_set_type(node, RB_INTERPOLATED_SYMBOL_NODE);
        nd_set_loc(node, loc);
        break;
      case RB_STRING_NODE:
        node = str_to_sym_node(p, node, loc);
        break;
      default:
        node = NEW_RB_INTERPOLATED_SYMBOL(node, loc);
        break;
    }
    return node;
}

dsymをコンパイルする

compile.cでは式を評価してintern命令を実行するようなバイトコードを生成します。 これもノードの書き換え前後で大きく変わらないのでさくっと書き換えます。

// Before
case NODE_DSYM:{
  compile_dstr(iseq, ret, node);
  if (!popped) {
      ADD_INSN(ret, node, intern);
  }
  else {
      ADD_INSN(ret, node, pop);
  }
  break;
}

// After
case RB_INTERPOLATED_SYMBOL_NODE: {
  compile_dstr(iseq, ret, node);
  if (!popped) {
      ADD_INSN(ret, node, intern);
  }
  else {
      ADD_INSN(ret, node, pop);
  }
  break;
}

コードを実行して問題なく動くことを確認します。

var = "b"

p :"sym"
#=> :sym
p :"a#{var}c"
#=> :abc
p :""
#=> :""

dsymが引数のキーになっているとき

parse.yでdsym_node関数を呼び出しているのはdsym生成規則だけではありません。 assoc生成規則のなかにもdsym_node関数を呼び出している規則があります1

assoc | tSTRING_BEG string_contents tLABEL_END arg_value
        {
            YYLTYPE loc = code_loc_gen(&@1, &@3);
            $$ = NEW_RB_ASSOC(dsym_node(p, $2, &loc), $4, &@$, &NULL_LOC);
        /*% ripper: assoc_new!(dyna_symbol!($:2), $:4) %*/
        }

これは例えばメソッドの引数のキーとしてdsymを書くことができるということを表しています。

def m(abc: nil)
  p abc
end

var = "b"
m("a#{var}c": true)
#=> true

このようなケースでも期待した通りのノードとバイトコードが生成されていることを確認しておきます。

def m(abc: nil)
  p abc
end

var = "b"

# @ CallNode (location: (6,6)-(19,1))*
# +-- receiver: nil
# +-- name: :m
# +-- arguments:
# |   @ ArgumentsNode (location: (6,2)-(6,18))
# |   +-- ArgumentsNodeFlags: nil
# |   +-- arguments: (length: 1)
# |       +-- @ KeywordHashNode (location: (6,2)-(6,18))
# |           +-- KeywordHashNodeFlags: nil
# |           +-- elements: (length: 1)
# |               +-- @ AssocNode (location: (6,2)-(6,18))
# |                   +-- key:
# |                   |   @ InterpolatedSymbolNode (location: (2,6)-(13,10))
# |                   |   +-- parts: (length: 3)
# |                   |   |   +-- @ StringNode (location: (6,3)-(6,4))
# |                   |   |   |   +-- unescaped: "a"
# |                   |   |   +-- @ EmbeddedStatementsNode (location: (6,4)-(6,10))
# |                   |   |   +-- @ StringNode (location: (6,10)-(6,11))
# |                   |   |       +-- unescaped: "c"
# |                   +-- value:
# |                   |   @ TrueNode (location: (6,14)-(6,18))
#
# 0007 putself                                                          (   6)[Li]
# 0008 putobject                              "a"
# 0010 getlocal_WC_0                          var@0
# 0012 dup
# 0013 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
# 0015 anytostring
# 0016 putobject                              "c"
# 0018 concatstrings                          3
# 0020 intern
# 0021 putobject                              true
# 0023 newhash                                2
# 0025 opt_send_without_block                 <calldata!mid:m, argc:1, FCALL|KW_SPLAT>
# 0027 leave

m("a#{var}c": true)

まとめ

今日の成果です。

  • xstringの対応をした
  • 式展開を含むsymbol(dsym)の対応をした

  1. p_kw_label | tSTRING_BEG string_contents tLABEL_ENDというパターンマッチングに関連する生成規則でもdsym_node関数を呼び出していますが、これについてはパターンマッチングのときに見ることにします。



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

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