ささだです.

(2012/06/15 22:02), Yugui wrote:
> コンパイルコストが本当に必要になるケースがはっきりしてくるまでは、まずはそれで良いかもしれません。
> じゃ、ruby_compile_main_from_string と ruby_eval_main は削除しましょう。
> 
> ここで案が2つあります。
> 1. toplevel_binding と rb_f_evalを公開する
> 2. さらに両者を合わせてruby_eval_in_main みたいなのを作る。
> 
> どっちが良いと思います?
> ちなみに、いずれにしてもさらにファイル版は必要だと思います。というのはマジックコメントとか読んで
> 適切なエンコーディングを付けたりってのは結構面倒なので。
> で、そのエンコーディングを付ける話を始めるとまたencdet話に跳んでしまうので、
> * rb_eval_string_in_main
> * rb_eval_file_in_main
> を作るのが現実的かなと思ってます。

 返事が遅くなってすみません.長いメールになりましたが,ちゃんと議論しよ
うとすると,こういうふうにまとめないといかんと思って,それで面倒がって遅
れておりました.すみません.

 中田さんに協力してもらって,現在,Ruby を組み込んだアプリから Ruby プ
ログラムを実行する方法をまとめました.下記に示すように意外とある,という
か,あまり一貫性がなく追加しているので,あまり良くないなぁと,思っている
というのがありまして,なので慎重になっている次第です.


 プログラムを組み込んだアプリから指定して実行する,という時,気にしない
といけない点がいくつかあるかと思います.次の 5 項目としてまとめました.

(a) プログラムの文字列はどうやって渡すか?
(b) トップレベルの self はどうなるか?
(c) 実行バインディングはどうなるか?
(d) __FILE__ はどうなるか?
(e) エンコーディングはどうなるか?
(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)


(1) ruby_options() を利用(-r,-e,ファイル名を指定,標準入力から入力)

 ruby(1) と同じ利用感が得られます.

 -e は,エンコーディングは下記のようになるようです.

http://jp.rubyist.net/magazine/?0025-Ruby19_m17n
> なお、標準入力から読み込んだスクリプトや、コマンドラインオプション -e
で与えたスクリプトの場合は、magic comment がなかった場合、ロケールが
script encoding として用いられます。このため、1 行スクリプトを書く場合に
までいちいち magic comment を書く必要はありません。

 プログラムの __FILE__ を好き勝手に指定する方法はありません(勝手に決ま
ります).-r,ファイル名指定では,それぞれ読み込んだファイルの名前,-e
では,"-e" になります.

まとめますと,

(a) プログラムの文字列はどうやって渡すか?

 -r:ファイル名でファイルを指定(ファイルの中身がプログラム)
 -e:実行する文字列を C 文字列で指定
 ファイル名:ファイル名でファイルを指定(ファイルの中身がプログラム)
 標準入力から:標準入力から読む

(b) トップレベルの self はどうなるか?

 main になる(ruby -e self で得られるオブジェクトを,ここでは main と呼
ぶことにします).

(c) 実行バインディングはどうなるか?

-r:新しいバインディングを作って実行します.
 例えば,ruby -rfoo -e 'p a' とあって,foo.rb に a=1 とあったとして
も,-r と -e は別々の環境を指しているので -e で a が見つからないというエ
ラーになります.

それ以外:

 TOPLEVEL_BINDING になります.

 例えば,ruby -e 'a=1' -e 'p a' とすると,プログラムが 2 回実行されます
が,同じバインディングを共有しているため,問題無く2回目の実行で 1 が出力
されます.

(d) __FILE__ はどうなるか?

 下記に固定されます.

 -r:指定されたファイル名
 -e:"-e"
 ファイル名:ファイル名
 標準入力から:"-"

(e) エンコーディングはどうなるか?

 マジックコメントを見て判断.ただし,下記の例外があり.

http://jp.rubyist.net/magazine/?0025-Ruby19_m17n
> なお、標準入力から読み込んだスクリプトや、コマンドラインオプション -e
で与えたスクリプトの場合は、magic comment がなかった場合、ロケールが
script encoding として用いられます。このため、1 行スクリプトを書く場合に
までいちいち magic comment を書く必要はありません。

(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)

 構文エラー,実行時エラー,ともにエラーが起きたらバックトレースを標準エ
ラー出力に表示して終了します.


(2) VALUE rb_eval_string(const char *str)
    VALUE rb_eval_string_protect(const char *str, int *state)
    VALUE rb_eval_string_wrap(const char *str, int *state)

 eval という名前ですが,意に反して,実は Kernel.eval ではありません.判
りづらいですね.これがまさに,Ruby を組み込んだアプリから使うことを意識
して作ったインターフェースです.多分.

 str で指定された C 文字列を実行します.-e に似ています.ただし,実行コ
ンテキストは TOPLEVEL_BINDING ではありません.self は main  です.これ
は,require された時のコンテキストと同じです.

 __FILE__ は "(eval)" 固定です.

 protect は,例外が起きたとき,外に例外が伝搬しません.state にどういう
実行状態で止まったか,ということが入ります.

 wrap は,protect の機能に加えて,load で第2引数を true にしたときに
toplevel に匿名 module が指定されますが,それと同じ挙動を行います.

 実は,yugui さんが欲しいのは,__FILE__ が指定出来ない,という点以外は
これなんじゃないかと思うのですが,どうでしょうか.TOPLEVEL_BINDING で実
行したい,というのは,本当に必要な要件なんでしょうか.


 まとめます.

(a) プログラムの文字列はどうやって渡すか?

 C 文字列で渡します.

(b) トップレベルの self はどうなるか?

 main になります.

(c) 実行バインディングはどうなるか?

 新しい binding を作って実行します.require で新しく作られるバインディ
ングと同じです.

 rb_eval_string_wrap() の場合,定数のトップレベルが匿名モジュールになり
ます.

(d) __FILE__ はどうなるか?

 "(eval)" になります.

(e) エンコーディングはどうなるか?

 マジックコメントを見ます.
(無いとどうなんだろう...)

(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)

VALUE rb_eval_string(const char *str)
外側に伝搬します.アプリ側でトラップ出来るようにしておかないと死にます.

VALUE rb_eval_string_protect(const char *str, int *state)
VALUE rb_eval_string_wrap(const char *str, int *state)
エラーが起きたら state に 0 以外の値が入っています.


(3) void rb_load(VALUE fname, int wrap)
    void rb_load_protect(VALUE fname, int wrap, int *state)

 ファイル名を String で渡す,ということ以外は (2) と変わりません.

(a) プログラムの文字列はどうやって渡すか?

 ファイル名でファイルを指定(ファイルの中身がプログラム).

(b) トップレベルの self はどうなるか?

 main になります.wrap が !0 の場合,定数のトップレベルが匿名モジュール
になります.

(c) 実行バインディングはどうなるか?

 新しい binding を作って実行します.require で新しく作られるバインディ
ングと同じです.
 
(d) __FILE__ はどうなるか?

 渡されたファイル名と同じになります(多分).

(e) エンコーディングはどうなるか?

 マジックコメントを見ます.

(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)

void rb_load(VALUE fname, int wrap)
  外側に伝搬します.設定していないと死にます(多分).

void rb_load_protect(VALUE fname, int wrap, int *state)
 ここで食い止めます.state を見るとエラーの有無をチェック出来ます.


(4) その他

 その他,Kernel.eval,Kernel.instance_eval,Kernel.class_eval などを用
いる,という手段もありますが,どちらかというと拡張ライブラリから呼び出す
ような手段であり,設定も面倒なので省略します.いや,それらで済むのなら,
それでいいのですが.

 ruby.h などで非公開の関数ですが rb_iseq_compile() + rb_iseq_eval() と
いうのもあるかと思ったんですが,よくよく調べてみると,これらをきちんと使
うのは準備が大変だということがわかりました.

 なお,上記で「準備しないと死ぬ」とあるのは,例外の伝搬先が設定されてい
ないと死ぬ,という感じです.普通に組込み用途でカジュアルに使わない方がい
いですね.


 とりあえず,現状認識はこの通りです.


 では,yugui さんご提案のインターフェースを見てます.

> * rb_eval_string_in_main
> * rb_eval_file_in_main

(a) プログラムの文字列はどうやって渡すか?

 不明.C 文字列だろうか.

(b) トップレベルの self はどうなるか?

 main です.

(c) 実行バインディングはどうなるか?

 TOPLEVEL_BINDING です.

(d) __FILE__ はどうなるか?

 不明.引数で渡すんだろうか.

(e) エンコーディングはどうなるか?

 多分,プログラム文字列に書いてあるのを読むんじゃないかと思う.

(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)

 不明.



 ちなみに,組込みで有名な mruby さんのインターフェースを調べてみます.

int mrb_compile_file(mrb_state*,FILE*);
int mrb_compile_string(mrb_state*,char*);
int mrb_compile_nstring(mrb_state*,char*,size_t);

 ぱっと見た感じ,ファイルや C 文字列を渡すと,これらの関数は返値 n を返
し,コンパイル結果が mrb_state->ipre[n] に格納されます.

mrb_value mrb_run(mrb_state*, struct RProc*, mrb_value);

 そして,mrb_run で,格納されたコンパイル結果を実行しています(多分).
第二引数に,コンパイル結果 mrb_state->irep[n] を mrb_proc_new() に渡して
Proc オブジェクトを作って(多分),第三引数にその Proc の初期値を渡して
実行,という感じでしょうか.多分.違ってたらすみません.

 まとめます.

(a) プログラムの文字列はどうやって渡すか?

 C 文字列や FILE*.

(b) トップレベルの self はどうなるか?

 どうなんだろう,これ.サンプル見ても判らなかったんだけど,main が勝手
に入るのかなぁ.

(c) 実行バインディングはどうなるか?

 そもそも binding が存在しません.

(d) __FILE__ はどうなるか?

 どうなるんでしょうか? そもそもあるの?

$ bin/mruby -e 'p __FILE__'
"(null)"
$ bin/mruby t.rb # t.rb には p __FILE__ と書いてある
"t.rb"

あるようでした.いつの間に設定されてたんだろう.mrb_compile_* だと,何が
入るんだろう.

(e) エンコーディングはどうなるか?

 エンコーディングが存在しません(多分.あったらすみません)

(f) エラーが起きたらどうなるか?(構文エラー,実行時エラー)

 どうなるんでしょう? mrb_run() の外まで伝搬するのかなぁ.

 これでサーベイを終わります.JRuby とかも調べ出すといいんでしょうが,
ちょっとしんどくなってきました.



 こう見ると,VALUE rb_eval_string_protect(const char *str, int *state)
が,__FILE__ を指定出来ない(そして名前が Kernel.eval を想起させる悪い名
前である),という点以外はいけてるんじゃないかと思うのですが,どうでしょ
うか.

 アプリケーション組込みアプリが,TOPLEVEL_BINDING 上で動作する,という
のは,実は想定していませんでした(なので無かった).require 相当が動けば
いいと思っていました.

 やりたいときは,

    $exec_string にプログラムをセットしてから,
    rb_eval_string_protected("eval($exec_string, TOPLEVEL_BINDING)"...);

なんて手もあるかもしれません,がそれはあんまりかな.

-- 
// SASADA Koichi at atdot dot net