名前と名前表

ID

メソッドやクラス、変数などインタプリタの中にはいろいろなところで 名前を扱う必要がある。だが名前を文字列で持っているとそれだけでも メモリを食うし、生成・解放がつきまとうので非常に扱うのが面倒になる。 それで Ruby の内部では名前を扱う時には文字列ではなく文字列と一対一に 対応する「ID」を使う。

IDunsigned int (ruby.h) で、文字列から ID への変換は parse.y に ある rb_intern() で行う。やっていることはわりと単純で、Ruby で書けば 次のようなコードで表せる。

@sym_tbl = {}
@last_id = 0

def rb_intern( str )
  id = @sym_tbl[str] and return id
  @last_id += 1
  @sym_tbl[str] = @last_id
  @last_id
end

実際には @sym_tbl はファイルスタティック、@last_id は関数スタティック の変数だ。

基本はこうなのだが、一部の文字列、たとえば $@ が先頭につく文字列は 各所でそれと判別する必要が出てくるので、特別に高速化の工夫がされている。 ID の下位 3 ビット(8 通り)がそのためのフラグに使われていて、その内訳は 以下のよう。

#define ID_LOCAL    0x01
#define ID_INSTANCE 0x02
#define ID_GLOBAL   0x03
#define ID_ATTRSET  0x04
#define ID_CONST    0x05

(parse.y)

ID_ATTRSET は「page=」のように最後がイコールになっている文字列だ。 このタイプの ID は、最後の「=」を除いた文字列の IDID_ATTRSET を セットしたものになる。ようするに、「any」の IDと「any=」の ID は 機械的に変換できる(rb_id_attrset)

また、演算子 ("+""-") は特別扱いで、yaccが 決めた定数になる。yacc が決める定数は、一文字のシンボルに対しては その文字コードそのままで、その他のシンボルには 256 から登場順に振られる。 これを利用して、実際には使われないトークン LAST_TOKEN を最後に定義して おき、Ruby が独自に決める ID はその次の値から使うようになっている。

ちなみに ID は Ruby レベルでは Symbol で表されて、シンボルは :symbol'symbol'.intern で得られるのだった。intern のほうは string.c にあるので ついでに見てみよう。

static VALUE
rb_str_intern(str)
    VALUE str;
{
    ID id;

    if (strlen(RSTRING(str)->ptr) != RSTRING(str)->len)
	rb_raise(rb_eArgError, "string contains `\\0'");
    id = rb_intern(RSTRING(str)->ptr);
    return ID2SYM(id);
}

(string.c)

ID2SYMintSymbol に変換するマクロ。1.4 以前はこれが INT2FIX だった。Symbol も特殊なオブジェクト形式をしているのだが、その詳細に ついてはオブジェクトの章を参照のこと。

st.c

インタプリタでは名前が多く出てくるが、名前があるならそれに対応する値も なくてはいけない。その対応はいろいろな形式で表せるだろうが、Ruby では これにハッシュを使っている。その実装が st_table である。このライブラリの詳しい解析はしないが、 簡単なリファレンスだけつけておく。

int st_insert(st_table *table, char *key, char *value)

ハッシュに keyvalue の組を追加する。 古いライブラリなので void* のかわりに char* を使っている。

void st_add_direct(st_table *table, char *key, char *value)

st_insert() と似ているが、同じハッシュ値を持つエントリーに対する 「同値検査」を省略する。key がまだ登録されていないことがはっきり している場合には、少し高速に登録できる。

int st_lookup(st_table *table, char *key, char **value)

key に対応する値をみつけて value にポインタを書きこむ。 返り値は見つかったかどうかの真偽値。

int st_is_member(st_table *table, char *key)

keytable に登録されているかどうか調べる。

void st_foreach(st_table table, enum st_retval (*func)(), char *arg)

Hash#each,delete_if などの実体。ハッシュ内の全てのキーと値、arg を 引数にして、func を実行する。func の返り値 enum st_retvalST_CONTINUE ST_STOP ST_DELETE のどれか。どれも見ためどおりの働きをする。

int st_delete(st_table *table, char **key, char **value),

*key に対応する値をテーブルから削除し、*key*value に登録時のキーと 値を書きこむ。返り値は削除したかどうか。

int st_delete_safe(st_table *table, char **key, char **value, char *never)

st_delete() と似ているが、その場ですぐに削除するのではなく never を 書きこんでおく。st_cleanup_safe() で本当に削除できる。 Ruby では never には Qundef を使う。

void st_cleanup_safe(st_table table, char *never)

never と同じ値を持つエントリーを削除する。

st_table *st_init_table(struct st_hash_type type), ..._with_size()

st_table を作成する。_with_size はサイズを指定して生成する。 struct st_hash_type はハッシュ値を得る関数と、同値判定を行う関数を持つ

st_table *st_init_numtable(), ..._with_size()

int のハッシュを作成する。 st_init_table()int 用の操作関数を渡しているだけ。

st_table *st_init_strtable(), ..._with_size()

文字列用のハッシュを作成する。 st_init_table() に文字列用の操作関数を渡しているだけ。

void st_free_table(st_table *table)

table を解放する。キー、値は解放されない。

st_table *st_copy(st_table *old_table)

Hash#dup の実体。 old_table と同じ内容の st_table を新たに作成して返す。