評価器の仕組みそれ自体については前章までで既に完結している。本章ではそ
の全てにパーサまでを加えた﹁広義の評価器﹂としての全体像を検証しよう。
対象となるのはeval
・Module#module_
eval
・Object#instance_eval
の三つであ
る。
eval
eval
については既に話したが、ここではより細かい話をしようと思う。
eval
を使うと実行時にその場で文字列をコンパイルし、評価することができる。
返り値はそのプログラムの最後の式の値だ。
p eval("1 + 1") # 2
eval
する文字列の中からはそのスコープの変数も参照できる。
lvar = 5
@ivar = 6
p eval("lvar + @ivar") # 11
ここまで読んできた読者なら﹁そのスコープ﹂という言葉を安直に読み飛ばせ
なくなっているだろう。例えば定数の﹁スコープ﹂はどうなっているんだ、な
んてことが気にならないだろうか。筆者は気になる。結論から言うと、基本的
にeval
の外の環境をそのまま引き継ぐと思っていい。
またメソッド定義もクラス定義もできる。
def a
eval('class C; def test() puts("ok") end end')
end
a() # クラスCとC#testを定義する
C.new.test # okと表示される
さらに、前章で少し言及したが、第二引数にProc
を渡すとその環境で評価できる、
def new_env
n = 5
Proc.new { nil } # このメソッドの環境をオブジェクトにして返す
end
p eval('n * 3', new_env()) # 15
module_eval
とinstance_eval
Proc
をeval
の第二引数に渡すとその環境で評価できた。mod
ule_eval
と
instance_eval
はその限定版︵あるいはショートカット︶である。
module_eval
ではモジュール文やクラス文の内部にいるかのような環境で
評価できる。
lvar = "toplevel lvar" # スコープ確認用のローカル変数
module M
end
M.module_eval(<<'EOS') # こういうときこそヒアドキュメント
p lvar # 参照できる
p self # Mと表示される
def ok # M#okを定義する
puts 'ok'
end
EOS
instance_eval
は特異クラス文でself
がそのオブジェクトになった環境で
評価できる。
lvar = "toplevel lvar" # スコープ確認用のローカル変数
obj = Object.new
obj.instance_eval(<<'EOS')
p lvar # 参照できる
p self # #<Object:0x40274f5c>と表示される
def ok # obj.okを定義する
puts 'ok'
end
EOS
またこのmodule_eval
とinstance_eval
はイテレータとして使うことも
できて、その場合はブロックがそれぞれの環境で評価される。例えば
obj = Object.new
p obj # #<Object:0x40274fac>
obj.instance_eval {
p self # #<Object:0x40274fac>
}
というように。
ただ文字列を使う場合とブロックを使う場合とでは
ローカル変数まわりの挙動が違う。例えばメソッドa
でブロックを作り
メソッドb
でins
tance_eval
したら、ブロックはa
のローカル変数を
参照する。メソッドa
で文字列を作りメソッドb
でinstance_eval
したら、
その文字列の中からはb
のローカル変数を参照する。ローカル変数の
スコープはあくまで﹁コンパイル時に﹂決まるので、毎回コンパイルする
文字列と、ファイルロード時にコンパイルされてしまうブロックでは結果が
違うわけだ。
eval
eval()
Rubyのeval
は引数のあるなしで場合分けが多いので、呼び出し形式は
eval(prog_string, some_block)
と限定しよう。すると実際のインターフェイス関数rb_f_eval
()
はほとんど意味
がなくなるので、もう一つ下の関数のeval()
から見ていくことにする。
eval()
の関数プロトタイプは
static VALUE
eval(VALUE self, VALUE src, VALUE scope, char *file, int line);
で、scope
が第二引数のProc
。file
とline
はeval
する文字列が
置いてあると仮定するファイル名・行番号である。では中身を見てみよう。
▼eval()
︵簡約版︶
4984 static VALUE
4985 eval(self, src, scope, file, line)
4986 VALUE self, src, scope;
4987 char *file;
4988 int line;
4989 {
4990 struct BLOCK *data = NULL;
4991 volatile VALUE result = Qnil;
4992 struct SCOPE * volatile old_scope;
4993 struct BLOCK * volatile old_block;
4994 struct RVarmap * volatile old_dyna_vars;
4995 VALUE volatile old_cref;
4996 int volatile old_vmode;
4997 volatile VALUE old_wrapper;
4998 struct FRAME frame;
4999 NODE *nodesave = ruby_current_node;
5000 volatile int iter = ruby_frame->iter;
5001 int state;
5002
5003 if (!NIL_P(scope)) { /* 今は常に真 */
5009 Data_Get_Struct(scope, struct BLOCK, data);
5010 /* dataからBLOCKを積む */
5011 frame = data->frame;
5012 frame.tmp = ruby_frame; /* GCよけ */
5013 ruby_frame = &(frame);
5014 old_scope = ruby_scope;
5015 ruby_scope = data->scope;
5016 old_block = ruby_block;
5017 ruby_block = data->prev;
5018 old_dyna_vars = ruby_dyna_vars;
5019 ruby_dyna_vars = data->dyna_vars;
5020 old_vmode = scope_vmode;
5021 scope_vmode = data->vmode;
5022 old_cref = (VALUE)ruby_cref;
5023 ruby_cref = (NODE*)ruby_frame->cbase;
5024 old_wrapper = ruby_wrapper;
5025 ruby_wrapper = data->wrapper;
5032 self = data->self;
5033 ruby_frame->iter = data->iter;
5034 }
5045 PUSH_CLASS();
5046 ruby_class = ruby_cbase; /* == ruby_frame->cbase */
5047
5048 ruby_in_eval++;
5049 if (TYPE(ruby_class) == T_ICLASS) {
5050 ruby_class = RBASIC(ruby_class)->klass;
5051 }
5052 PUSH_TAG(PROT_NONE);
5053 if ((state = EXEC_TAG()) == 0) {
5054 NODE *node;
5055
5056 result = ruby_errinfo;
5057 ruby_errinfo = Qnil;
5058 node = compile(src, file, line);
5059 if (ruby_nerrs > 0) {
5060 compile_error(0);
5061 }
5062 if (!NIL_P(result)) ruby_errinfo = result;
5063 result = eval_node(self, node);
5064 }
5065 POP_TAG();
5066 POP_CLASS();
5067 ruby_in_eval--;
5068 if (!NIL_P(scope)) { /* 今は常に真 */
5069 int dont_recycle = ruby_scope->flags & SCOPE_DONT_RECYCLE;
5070
5071 ruby_wrapper = old_wrapper;
5072 ruby_cref = (NODE*)old_cref;
5073 ruby_frame = frame.tmp;
5074 ruby_scope = old_scope;
5075 ruby_block = old_block;
5076 ruby_dyna_vars = old_dyna_vars;
5077 data->vmode = scope_vmode; /* 可視性スコープの変更を保存 */
5078 scope_vmode = old_vmode;
5079 if (dont_recycle) {
/* ……SCOPE・BLOCK・VARSをコピーする…… */
5097 }
5098 }
5104 if (state) {
5105 if (state == TAG_RAISE) {
/* ……例外オブジェクトを準備…… */
5121 rb_exc_raise(ruby_errinfo);
5122 }
5123 JUMP_TAG(state);
5124 }
5125
5126 return result;
5127 }
(eval.c)
前置きもなしにこの関数をいきなり見せられたら﹁ぐぁっ﹂となるだろうが、
ここまでeval.c
の関数を撃破してきた我々にとってはもはや敵ではない。
この関数はひたすらスタックを退避・復帰しているだけだ。注意すべきところ
は以下の三点だけである。
●珍しくFRAME
も︵コピー・プッシュではなく︶置き換えている
●ruby_cref
はruby_frame->cbase
で代用︵?︶している
●scope_vmode
だけは単純な復帰ではなくdata
に影響を与える
そしてメイン部分は真ん中あたりにあるcompile()
とeval
_node()
だ。
eval_node()
なんてもう忘れられていそうだが、引数node
の評価を開始する関
数である。ruby_run
()
でも使われていた。
compile()
はこうだ。
▼compile()
4968 static NODE*
4969 compile(src, file, line)
4970 VALUE src;
4971 char *file;
4972 int line;
4973 {
4974 NODE *node;
4975
4976 ruby_nerrs = 0;
4977 Check_Type(src, T_STRING);
4978 node = rb_compile_string(file, src, line);
4979
4980 if (ruby_nerrs == 0) return node;
4981 return 0;
4982 }
(eval.c)
ruby_nerrs
はyyerror()
の中でインクリメントされる変数である。
つまりこの変数が非ゼロならパースエラーが起こったことを示す。また
rb_compile_string()
は既に第二部で扱っている。Rubyの文字列を構文木に
コンパイルする関数だった。
ここで一つ問題になってくるのがローカル変数である。第12章﹃構文木の構築﹄で
見た通り、ローカル変数はlvtbl
を使って管理されているのだった。しか
しいまは既にSCOPE
︵と、もしかしたらVARS
も︶が存在しているのだから、
それに上書き追加する形でパースしなければならない。これが実はeval()
の
核心であり、最悪に難しいところなのだ。再びparse.y
に戻ってこの探索を
完結させることにしよう。
top_local
ローカル変数の管理テーブルstruct local_vars
を積むときに使うのは
local_push() local_pop()
という
関数だったが、実はparse.y
には管理用テーブルを積む関数がもう一組ある。
top_local_init()
とtop_lo
cal_setup()
だ。それはこんな感じで呼ばれて
いる。
▼top_local_init()
の呼ばれかた
program : { top_local_init(); }
compstmt
{ top_local_setup(); }
もちろん実際には他にもいろいろやっているのだが、今はどうでもいいので全
部カットした。そしてその内容はこうだ。
▼top_local_init()
5273 static void
5274 top_local_init()
5275 {
5276 local_push(1);
5277 lvtbl->cnt = ruby_scope->local_tbl?ruby_scope->local_tbl[0]:0;
5278 if (lvtbl->cnt > 0) {
5279 lvtbl->tbl = ALLOC_N(ID, lvtbl->cnt+3);
5280 MEMCPY(lvtbl->tbl, ruby_scope->local_tbl, ID, lvtbl->cnt+1);
5281 }
5282 else {
5283 lvtbl->tbl = 0;
5284 }
5285 if (ruby_dyna_vars)
5286 lvtbl->dlev = 1;
5287 else
5288 lvtbl->dlev = 0;
5289 }
(parse.y)
つまりruby_scope
からlvtbl
にlocal_tbl
をコピーしておく。
ブロックローカル変数についてはあとでまとめて見たほうがいいので、
とりあえず普通のローカル変数に集中しよう。
続いてt
op_local_setup()
だ。
▼top_local_setup()
5291 static void
5292 top_local_setup()
5293 {
5294 int len = lvtbl->cnt; /* パース後のローカル変数の数 */
5295 int i; /* パース前のローカル変数の数 */
5296
5297 if (len > 0) {
5298 i = ruby_scope->local_tbl ? ruby_scope->local_tbl[0] : 0;
5299
5300 if (i < len) {
5301 if (i == 0 || (ruby_scope->flags & SCOPE_MALLOC) == 0) {
5302 VALUE *vars = ALLOC_N(VALUE, len+1);
5303 if (ruby_scope->local_vars) {
5304 *vars++ = ruby_scope->local_vars[-1];
5305 MEMCPY(vars, ruby_scope->local_vars, VALUE, i);
5306 rb_mem_clear(vars+i, len-i);
5307 }
5308 else {
5309 *vars++ = 0;
5310 rb_mem_clear(vars, len);
5311 }
5312 ruby_scope->local_vars = vars;
5313 ruby_scope->flags |= SCOPE_MALLOC;
5314 }
5315 else {
5316 VALUE *vars = ruby_scope->local_vars-1;
5317 REALLOC_N(vars, VALUE, len+1);
5318 ruby_scope->local_vars = vars+1;
5319 rb_mem_clear(ruby_scope->local_vars+i, len-i);
5320 }
5321 if (ruby_scope->local_tbl &&
ruby_scope->local_vars[-1] == 0) {
5322 free(ruby_scope->local_tbl);
5323 }
5324 ruby_scope->local_vars[-1] = 0; /* NODEはもういらない */
5325 ruby_scope->local_tbl = local_tbl();
5326 }
5327 }
5328 local_pop();
5329 }
(parse.y)
local_vars
はスタックにあったりヒープにあったりするので多少ややこしくなっ
てはいるが、ruby_scope
のlocal_
tbl
とlocal_vars
をアップデートしているだ
けである︵S
COPE_MALLOC
が立っているとlocal_vars
はmal
loc()
割り当て︶。
またここでalloca()
を使っても意味がないので強制的にmalloc()
割り当てに
変更するしかない。
ブロックローカル変数
ところでブロックローカル変数はどうなっているのだろうか。
それを考えるにはまずパーサのエントリポイントyycompile()
に
戻らなければならない。
▼ruby_dyna_vars
の退避
static NODE*
yycompile(f, line)
{
struct RVarmap *vars = ruby_dyna_vars;
:
n = yyparse();
:
ruby_dyna_vars = vars;
}
単なる退避・復帰のようにも見えるのだが、ruby_dyna_va
rs
をクリアしてはい
ないところがポイントだ。つまり評価器で作ったRVarmap
のリンクにパーサでも
直接要素を追加していくことになる。
しかしruby_dyna_vars
はパーサと評価器で構造が違ったはずだ。リンクが常に
単線なのは問題なさそうだが、ヘッダ︵id=0
のRVarmap
︶の付きかたの違いは
どうするのだろう。
そこで役に立つのがtop_local_init()
にあったloc
al_push(1)
の﹁1﹂であ
る。local_push()
の引数が真になるとruby_dyna_vars
の最初のヘッダを付
けなくなる。つまり、図1のようになる。これでeval
文字列の
中から外のスコープのブロックローカル変数を参照できることが確認できた。
![(dynavars)](images/ch_anyeval_dynavars.jpg)
図1: eval
中のruby_dyna_vars
いや、確かに参照はできるけどもruby_dyna_vars
はパーサでは全部解放してい
たはずじゃあないのか、評価器で作ったリンクを解放されたらどうする……と
いうことに気付いてしまった人は次のところを読んで安心してもらいたい。
▼yycompile()
−ruby_dyna_vars
の解放
2386 vp = ruby_dyna_vars;
2387 ruby_dyna_vars = vars;
2388 lex_strterm = 0;
2389 while (vp && vp != vars) {
2390 struct RVarmap *tmp = vp;
2391 vp = vp->next;
2392 rb_gc_force_recycle((VALUE)tmp);
2393 }
(parse.y)
評価器で作ったリンク︵vars
︶まで来たらちゃんとループが
止まるようになっているのだ。
instance_eval
全体像
Module#module_eval
の実体はrb_mod_mod
ule_eval()
、
Object#instance_eval
の実体はrb_obj_instance_eval()
である。
▼rb_mod_module_eval() rb_obj_ins
tance_eval()
5316 VALUE
5317 rb_mod_module_eval(argc, argv, mod)
5318 int argc;
5319 VALUE *argv;
5320 VALUE mod;
5321 {
5322 return specific_eval(argc, argv, mod, mod);
5323 }
5298 VALUE
5299 rb_obj_instance_eval(argc, argv, self)
5300 int argc;
5301 VALUE *argv;
5302 VALUE self;
5303 {
5304 VALUE klass;
5305
5306 if (rb_special_const_p(self)) {
5307 klass = Qnil;
5308 }
5309 else {
5310 klass = rb_singleton_class(self);
5311 }
5312
5313 return specific_eval(argc, argv, klass, self);
5314 }
(eval.c)
この二つのメソッドは﹁self
とclass
を置き換えるメソッド﹂として共通化で
きるのでその部分がspecific_eval()
でまとめられている。この先のことも含
めて図示してみよう︵図2︶。
括弧付きは関数ポインタでの呼び出しだ。
![(speceval)](images/ch_anyeval_speceval.jpg)
図2: コールグラフ
instance_eval
にしてもmodule_eval
にしてもブロックと文字列の両方を受けら
れるので、それぞれ固有の処理をするようeval
とyield
に別れる。ただしその
ほとんどの部分はまた共通なので、その部分がexec_under()
としてくくり出し
てあるわけだ。
しかし読む側からすると 2\times 2=4 通りを同時に相手にする
ことになってしまうわけで、それは得策ではない。だからここでは
(一)instance_eval
で、
(二)文字列を引数に取るとき
だけを考えてrb_obj_instance_eval()
以下の関数を全てインライン展開、
定数畳み込みをかけたものを読むことにする。
併合後
全部まとめたらこうなった。
併合前と比べると随分わかりやすくなっている。
▼specific_eval()
−instance_eval
、e
val
、文字列
static VALUE
instance_eval_string(self, src, file, line)
VALUE self, src;
const char *file;
int line;
{
VALUE sclass;
VALUE result;
int state;
int mode;
sclass = rb_singleton_class(self);
PUSH_CLASS();
ruby_class = sclass;
PUSH_FRAME();
ruby_frame->self = ruby_frame->prev->self;
ruby_frame->last_func = ruby_frame->prev->last_func;
ruby_frame->last_class = ruby_frame->prev->last_class;
ruby_frame->argc = ruby_frame->prev->argc;
ruby_frame->argv = ruby_frame->prev->argv;
if (ruby_frame->cbase != sclass) {
ruby_frame->cbase = rb_node_newnode(NODE_CREF, sclass, 0,
ruby_frame->cbase);
}
PUSH_CREF(sclass);
mode = scope_vmode;
SCOPE_SET(SCOPE_PUBLIC);
PUSH_TAG(PROT_NONE);
if ((state = EXEC_TAG()) == 0) {
result = eval(self, src, Qnil, file, line);
}
POP_TAG();
SCOPE_SET(mode);
POP_CREF();
POP_FRAME();
POP_CLASS();
if (state) JUMP_TAG(state);
return result;
}
オブジェクトの特異クラスをCLASS
とCREF
とruby_fra
me->cbase
に
プッシュする。ということらしい。主処理はev
al()
一発である。いつもと
違ってFRAME
を構造体コピーで初期化したりしていないのが珍しいが、
それもたいした違いではない。
併合前
読みやすくなったと筆者は言ってはいるが、もしかしたら併合前から簡単だっ
たのかもしれない。併合前のものと比べてどのあたりが簡単にされているのか
検証してみよう。
まずspecific_eval()
だ。この関数はRubyとのインターフェイス部分の
コードを共通化するためのものなので、ほとんどが引数のパースである。
それを全部削るとこうなる。
▼specific_eval()
︵簡約版︶
5258 static VALUE
5259 specific_eval(argc, argv, klass, self)
5260 int argc;
5261 VALUE *argv;
5262 VALUE klass, self;
5263 {
5264 if (rb_block_given_p()) {
5268 return yield_under(klass, self);
5269 }
5270 else {
5294 return eval_under(klass, self, argv[0], file, line);
5295 }
5296 }
(eval.c)
この通りブロックがあるかどうかで完璧に二通りに分かれており、
それぞれのルートのコードが影響を与えあったりすることはない。だから
読むときは片方ずつ読むべきだ。併合版ではまずこの点が改善されている。
またyield_under()
を読むときはfile
とline
が関係ないので、
yield
ルートを主体に併合した場合はこの引数のパースについては
全く考えなくていいことが明確になるだろう。
次にeval_under()
とeval_under_i()
を見てみよう。
▼eval_under()
5222 static VALUE
5223 eval_under(under, self, src, file, line)
5224 VALUE under, self, src;
5225 const char *file;
5226 int line;
5227 {
5228 VALUE args[4];
5229
5230 if (ruby_safe_level >= 4) {
5231 StringValue(src);
5232 }
5233 else {
5234 SafeStringValue(src);
5235 }
5236 args[0] = self;
5237 args[1] = src;
5238 args[2] = (VALUE)file;
5239 args[3] = (VALUE)line;
5240 return exec_under(eval_under_i, under, under, args);
5241 }
5214 static VALUE
5215 eval_under_i(args)
5216 VALUE *args;
5217 {
5218 return eval(args[0], args[1], Qnil, (char*)args[2], (int)args[3]);
5219 }
(eval.c)
この関数では引数を一つにするために配列args
を経由して渡している。
このargs
はeval_under()
からeval_unde
r_i()
に渡すための一時的な
コンテナなんだろうな、
と予想はできるが、本当にそうなのかはわからない。もしかしたら
exec_und
er()
の中でargs
を加工したりしているのかもしれない。
コードの共通化の方法としてはこれは非常に正しいやりかただが、
読む側からするとこういう間接的な渡しが入ると理解しづらいものだ。
特にfile
とline
にはコンパイラを胡麻化すために無駄なキャストが入っている
ので本当の型がなんなのか想像しにくい。このあたりは併合版では全て消滅し
ているので迷わずに済む。
ただ併合したり展開したりするほうが常にわかりやすいかというとそうでもな
い。例えばexec_under()
を呼ぶときは第二引数も第三引数もunder
を渡して
いるが、exec_under()
の側ではパラメータ変数を両方ともunder
に展開して
しまっていいのだろうか。と言うのは、
exec_under()
の第二・第三引数とは実はプッシュすべきCLASS
とCREF
を示して
いるのである。CLA
SS
とCREF
は﹁別のもの﹂なので、別の変数を使うほうがい
いかもしれない。先の併合版でもここだけは
VALUE sclass = .....;
VALUE cbase = sclass;
としようかと思ったのだが、いきなりこれだけ変数を残しても違和感があるか
と思い、sclass
で展開しておいた。つまり文章の流れ上の都合でしかない。
これまで何回も引数を展開したり関数を展開したりしてきたが、そのたびに
展開する理由をしつこく説明してきた。即ち
●とりうるパターンが数パターンに限られているとき
●動作が微妙に変わるとき
である。
﹁とにかく手でいろいろなものを展開すれば簡単になるんだ﹂
なんてことを言っているわけでは、決してない。
どんなときでも優先するのは自分にとっての理解しやすさであって、手順
を守ることではない。展開してしまったほうがわかりやすいなら、展開する。
展開しない、あるいは逆に手続きにまとめたほうがわかりやすいと感じるなら
そうしよう。ruby
の場合は元がちゃんと書けているので展開するばかりだった
が、下手な人の書いたソースなら関数にくくりまくるほうがわかりやすくなる
ことも多いはずだ。
御意見・御感想・誤殖の指摘などは
青木峰郎 <aamine@loveruby.net>
までお願いします。
﹃Rubyソースコード完全解説﹄
はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.