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


Ruby Parser開発日誌 (24-32) - parse.yが生成するノードを変える ー 数値とRange

32日目: 数値とRange

前回はarrayの対応をしたので、今回は数値リテラルとRangeリテラルの対応をしたいと思います。

数値リテラル

Rubyには4つの数値リテラルがあります。

これらのうち特に整数と有理数については値を多倍長整数で保持することも考えられますが、いったんは現状と同じで文字列として保持することにして先に進みたいと思います。 そうするとparse.yではマクロを変更する程度の修正ですみます。

@@ -9460,16 +9471,16 @@ set_number_literal(struct parser_params *p, enum yytokentype type, int suffix, i

     switch (type) {
       case tINTEGER:
-        set_yylval_node(NEW_INTEGER(strdup(tok(p)), base, &_cur_loc));
+        set_yylval_node(NEW_RB_INTEGER(rb_parser_encoding_string_new2(tok(p)), base, &_cur_loc));
         break;
       case tFLOAT:
-        set_yylval_node(NEW_FLOAT(strdup(tok(p)), &_cur_loc));
+        set_yylval_node(NEW_RB_FLOAT(rb_parser_encoding_string_new2(tok(p)), &_cur_loc));
         break;
       case tRATIONAL:
-        set_yylval_node(NEW_RATIONAL(strdup(tok(p)), base, seen_point, &_cur_loc));
+        set_yylval_node(NEW_RB_RATIONAL(rb_parser_encoding_string_new2(tok(p)), base, seen_point, &_cur_loc));
         break;
       case tIMAGINARY:
-        set_yylval_node(NEW_IMAGINARY(strdup(tok(p)), base, seen_point, numeric_type, &_cur_loc));
+        set_yylval_node(NEW_RB_IMAGINARY(rb_parser_encoding_string_new2(tok(p)), base, seen_point, numeric_type, &_cur_loc));
         (void)numeric_type;     /* for ripper */
         break;
       default:

またコンパイラもノードからRubyのオブジェクトへ変換する関数を修正しておけばよいでしょう。

@@ -5347,20 +5347,20 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq)
     switch (nd_type(node)) {
       case RB_INTEGER_NODE:
         {
-            VALUE lit = rb_node_integer_literal_val(node);
+            VALUE lit = rb_node_integer_literal_val2(node);
             if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit);
             return lit;
         }
       case RB_FLOAT_NODE:
         {
-            VALUE lit = rb_node_float_literal_val(node);
+            VALUE lit = rb_node_float_literal_val2(node);
             if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit);
             return lit;
         }
       case RB_RATIONAL_NODE:
-        return rb_ractor_make_shareable(rb_node_rational_literal_val(node));
+        return rb_ractor_make_shareable(rb_node_rational_literal_val2(node));
       case RB_IMAGINARY_NODE:
-        return rb_ractor_make_shareable(rb_node_imaginary_literal_val(node));
+        return rb_ractor_make_shareable(rb_node_imaginary_literal_val2(node));

ちゃんと動いているようです。

p 1
#=> 1
p 1.0
#=> 1.0
p 42r
#=> (42/1)
p 42i
#=> (0+42i)

10.times {p it}
#=> 0
#=> 1
#=> 2
#=> 3
#=> 4
#=> 5
#=> 6
#=> 7
#=> 8
#=> 9

Rangeリテラル

Rangeは1..3のように終端を含む書き方と1...3のように終端を含まない書き方があります。 ノードの書き換え前は前者をNODE_DOT2、後者をNODE_DOT3で表していました。 書き換え後はどちらもRangeNodeで表すようになり、終端を含まない場合はRangeFlags: exclude_endというフラグが立つようになります。

# Before

# @ NODE_DOT2 (id: 5, line: 3, location: (3,0)-(3,4))*
# +- nd_beg:
# |   @ NODE_INTEGER (id: 3, line: 3, location: (3,0)-(3,1))
# |   +- val: 1
# +- nd_end:
# |   @ NODE_INTEGER (id: 4, line: 3, location: (3,3)-(3,4))
# |   +- val: 3
1..3

# @ NODE_DOT3 (id: 2, line: 1, location: (1,0)-(1,5))*
# +- nd_beg:
# |   @ NODE_INTEGER (id: 0, line: 1, location: (1,0)-(1,1))
# |   +- val: 1
# +- nd_end:
# |   @ NODE_INTEGER (id: 1, line: 1, location: (1,4)-(1,5))
# |   +- val: 3
1...3

# After

# @ RangeNode (location: (1,0)-(1,4))
# +-- RangeFlags: nil
# +-- left:
# |   @ IntegerNode (location: (1,0)-(1,1))
# |   +-- value: 1
# +-- right:
# |   @ IntegerNode (location: (1,3)-(1,4))
# |   +-- value: 3
1..3

# @ RangeNode (location: (1,0)-(1,5))
# +-- RangeFlags: exclude_end
# +-- left:
# |   @ IntegerNode (location: (1,0)-(1,1))
# |   +-- value: 1
# +-- right:
# |   @ IntegerNode (location: (1,4)-(1,5))
# |   +-- value: 3
1...3

parse.yでは引き続きNEW_RB_DOT2, NEW_RB_DOT3と区別したマクロを使うようにして、最終的に呼ばれる関数のなかでフラグを立てるかどうか決めるようにします。

static rb_range_node_t *
rb_new_node_dot2_new(struct parser_params *p, rb_node_t *nd_beg, rb_node_t *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc)
{
    rb_range_node_t *n = RB_NEW_NODE_NEWNODE((enum rb_node_type)RB_RANGE_NODE, rb_range_node_t, loc);
    n->left = nd_beg;
    n->right = nd_end;
    n->operator_loc = *operator_loc;

    return n;
}

static rb_range_node_t *
rb_new_node_dot3_new(struct parser_params *p, rb_node_t *nd_beg, rb_node_t *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc)
{
    rb_range_node_t *n = RB_NEW_NODE_NEWNODE((enum rb_node_type)RB_RANGE_NODE, rb_range_node_t, loc);
    rb_node_set_fl(n, RB_RANGE_FLAGS_EXCLUDE_END);
    n->left = nd_beg;
    n->right = nd_end;
    n->operator_loc = *operator_loc;

    return n;
}

コンパイラではノードの種類に応じて第5引数を変えてcompile_dots関数を呼び出していました。

case NODE_DOT2:
  CHECK(compile_dots(iseq, ret, node, popped, FALSE));
  break;
case NODE_DOT3:
  CHECK(compile_dots(iseq, ret, node, popped, TRUE));
  break;

ノードの書き換え後はRangeNodeにまとめられたのでcompile_dots関数のなかでノードのフラグを見るようにします。

case RB_RANGE_NODE: {
  CHECK(compile_dots(iseq, ret, node, popped));
  break;
}
-compile_dots(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped, const int excl)
+compile_dots(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
 {
+    const int excl = rb_node_get_fl(node) & RB_RANGE_FLAGS_EXCLUDE_END;
     VALUE flag = INT2FIX(excl);

簡単なスクリプトで確認してみます。

a = 0; b = 10

p(1..3)
#=> 1..3
p(1...3)
#=> 1...3
p(a..b)
#=> 0..10

まとめ

今日の成果です。




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

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