| 著作一覧 |
blockをC APIで呼び出すの続き。
元の問題は、
class A
def a(&proc)
@proc = proc
end
def run_a(himself)
himself.instance_eval &@proc
end
end
の&procをどうすればC APIで実現できるかということだった。
上のaに相当するコードは割りと簡単にわかった。
if (rb_block_given_p())
{
rb_ivar_set(self, rb_intern("@proc"), rb_block_proc());
}
が、この後がいけない。上で@procに保存したのはProcのインスタンスだが、Methodと異なり、元のselfが組み込まれていてそれを外せない。したがって、元のself以外のselfのコンテキストで実行することができない。
eval.cを読むと、rb_proc_callにはselfは与えられないし(Qundefが設定される)、与えられるproc_invokeはstaticだ。
逆に考えて、元のスクリプト同様にブロックを用意するのはどうだろうか?
PUSH_BLOCKしかないのかな?
では、&はどうやっているのかと思うと、NEW_BLOCK_PASSになるようで(合ってるかな?)つまるところノードに評価時点で組み込まれてしまう。するとeval.cのblock_passに入ってくるので、この関数のstaticを外して、と考えてみるまでも無くノードが引数なわけだしそれは外部に公開されていないはずだ。
で、考えるのをやめて、
rb_ivar_set(himself, rb_intern("@_temporary_block"), proc);
rb_funcall(himself, rb_intern("instance_eval"),
1, rb_str_new2("instance_eval &@_temporary_block"));
とした。というわけで、現在のところ、&argに相当するC APIは無いでFAなのかなぁ。
VALUE rb_block_pass(VALUE self, VALUE proc);
selfをレシーバとしてprocをブロックとして与える。
rb_block_pass(self, proc);
if (!rb_block_given_p()) {
rb_raise("bug");
}
rb_funcall(self, rb_intern("instance_eval"), 0, 0);
という感じのが欲しいような気もするが、おそらく公開APIにするよりは、インタプリタを使わせるほうが良いのだろうな。
ジェズイットを見習え |