著作一覧 |
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にするよりは、インタプリタを使わせるほうが良いのだろうな。
ジェズイットを見習え |