メソッドやクラス、変数などインタプリタの中にはいろいろなところで
名前を扱う必要がある。だが名前を文字列で持っているとそれだけでも
メモリを食うし、生成・解放がつきまとうので非常に扱うのが面倒になる。
それで Ruby の内部では名前を扱う時には文字列ではなく文字列と一対一に
対応する「ID
」を使う。
ID
は unsigned 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
は、最後の「=」を除いた文字列の ID
に ID_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)
ID2SYM
は int
を Symbol
に変換するマクロ。1.4
以前はこれが INT2FIX
だった。Symbol
も特殊なオブジェクト形式をしているのだが、その詳細に
ついてはオブジェクトの章を参照のこと。
インタプリタでは名前が多く出てくるが、名前があるならそれに対応する値も
なくてはいけない。その対応はいろいろな形式で表せるだろうが、Ruby では
これにハッシュを使っている。その実装が
st_table
である。このライブラリの詳しい解析はしないが、
簡単なリファレンスだけつけておく。
int st_insert(st_table *table, char *key, char *value)
ハッシュに key
と value
の組を追加する。
古いライブラリなので 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)
key
が table
に登録されているかどうか調べる。
void st_foreach(st_table table, enum st_retval (*func)(), char *arg)
Hash#each,delete_if
などの実体。ハッシュ内の全てのキーと値、arg
を
引数にして、func
を実行する。func
の返り値 enum st_retval
は ST_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
を新たに作成して返す。