Ruby から Gauche を使う拡張モジュール
ちょっと今帰省準備中で忙しいのですが,ここを見てたら思い付いてしまったのでちょっと書いてみた.
参考サイト :
Ruby リファレンスマニュアル : mkmf
RubyExtensionProgrammingGuide
クラスやメソッドを定義している,extgauche.c .見ればわかりますが,エライ手抜きなので,Gauche オブジェクトは環境持ってるだけです.VM の状態とかは全インスタンスで共有されてしまいます.Ruby 側でスレッドとか使ってると非常にマズイかもしれません (ruby も gosh もインタプリタ内でグローバル変数を使ってるので,これはどうしようも無いかも.あまりオブジェクト指向の意味が無いという ← 処理系自体をオブジェクトにする方が悪い).
Makefile を作るための extconf.rb
コンパイル方法と実行方法.Ruby と Gauche それぞれの拡張モジュールを作れる環境が必要です (ようするに,mkmf が動いて,Ruby と Gauche のヘッダとライブラリが入ってれば良い).Debian なら,ruby と ruby-dev と gauche と gauche-dev が必要です.
まぁ,はてなの方の TCC 日記で,hamaji shin-ichiro さん作の tccparser を読んでいたら Ruby C API がでてきて戸惑ったので,ちょっと勉強してみようかなと.
非常にテキトーなプロトタイプなので,文字コードの変換すらしてません.Gtk と違って,Ruby のデフォルト文字コードは UTF8 では無いので,非 ascii の範囲を使うと文字化けると思います.init.scm も読み込ませてないので,use とか使えないです.
あと,Gauche の GC と Ruby の GC をちゃんと調査してないので,非常にいろいろマズイことしてるかもしれません.
また,Gauche のバージョンは,0.8.7 以前で無いと,Scm_Eval_CString などの C API が変わっていてコンパイルできないかもしれません (Debian の gauche のバージョンはいつになったら上がるのだろうか).
根本的に,Ruby のオブジェクトでは無く,単なる文字列を返してる時点でけっこう終わってますが.Scheme->Ruby へのオブジェクト変換なども真面目に実装すれば,いろいろ面白いことができるかもしれませんし,あんまり意味ないかもしれません.
# あ,外部表現が共通の部分だけならば,単に rb_eval_string() を呼ぶだけで良いのか.gauche 側で印字表現を Ruby 互換に差し替えても良いし.
帰ってきたらちゃんと書き直す (かもしれません)
それではみなさん,良いお年を.
参考サイト :
Ruby リファレンスマニュアル : mkmf
RubyExtensionProgrammingGuide
クラスやメソッドを定義している,extgauche.c .見ればわかりますが,エライ手抜きなので,Gauche オブジェクトは環境持ってるだけです.VM の状態とかは全インスタンスで共有されてしまいます.Ruby 側でスレッドとか使ってると非常にマズイかもしれません (ruby も gosh もインタプリタ内でグローバル変数を使ってるので,これはどうしようも無いかも.あまりオブジェクト指向の意味が無いという ← 処理系自体をオブジェクトにする方が悪い).
#include<ruby.h>
#include<gauche.h>
/* 参考 : http://i.loveruby.net/w/RubyExtensionProgrammingGuide.html */
struct gaucheObj {
ScmObj env;
};
static VALUE scmobj_alloc(VALUE klass) {
struct gaucheObj *o = ALLOC(struct gaucheObj);
return Data_Wrap_Struct(klass, 0, -1, o);
}
static VALUE scmobj_initialize(VALUE self) {
struct gaucheObj *o;
Data_Get_Struct(self, struct gaucheObj, o);
o->env = Scm_MakeModule(NULL, FALSE);
return Qnil;
}
VALUE gauche_eval(VALUE self, VALUE s){
char *msg, *code = STR2CSTR(s);
struct gaucheObj *o;
ScmObj result, error;
/* 出力文字列ポート開く */
ScmObj os = Scm_MakeOutputStringPort(TRUE);
Data_Get_Struct(self, struct gaucheObj, o);
/* Scheme レベルでエラーハンドリング */
/* http://alohakun.blog7.fc2.com/blog-entry-517.html */
/* 0.8.8 以降では,もっとちゃんとしたやり方があります (古いやり方) */
Scm_Define(SCM_MODULE(o->env), SCM_SYMBOL(SCM_INTERN("*input*")), SCM_MAKE_STR_COPYING(code));
Scm_Define(SCM_MODULE(o->env), SCM_SYMBOL(SCM_INTERN("*error*")), SCM_FALSE);
result = Scm_EvalCString("(guard (e (else (set! *error* e) #f)) (eval (read-from-string *input*) (current-module)))", SCM_OBJ(o->env));
error = Scm_GlobalVariableRef(SCM_MODULE(o->env), SCM_SYMBOL(SCM_INTERN("*error*")), 0);
/* 文字列を評価した結果をポートに書き込む */
if (!SCM_FALSEP(error))
Scm_Write(error, os, SCM_WRITE_DISPLAY);
else
Scm_Write(result, os, SCM_WRITE_DISPLAY);
msg = Scm_GetString(SCM_STRING(Scm_GetOutputString(SCM_PORT(os))));
/* ポート閉じる */
Scm_ClosePort(SCM_PORT(os));
return rb_str_new2(msg);
}
void Init_Gauche(void){
VALUE Gauche;
GC_INIT(); Scm_Init(GAUCHE_SIGNATURE);
Gauche = rb_define_class("Gauche", rb_cObject);
rb_define_alloc_func(Gauche, scmobj_alloc);
rb_define_private_method(Gauche, "initialize", scmobj_initialize, 0);
rb_define_method(Gauche, "eval", gauche_eval, 1);
}
Makefile を作るための extconf.rb
require "mkmf"
dir_config('gauche')
have_header('gauche.h')
have_library('gauche')
have_library('dl')
have_library('crypt')
have_library('util')
have_library('pthread')
create_makefile('Gauche')
コンパイル方法と実行方法.Ruby と Gauche それぞれの拡張モジュールを作れる環境が必要です (ようするに,mkmf が動いて,Ruby と Gauche のヘッダとライブラリが入ってれば良い).Debian なら,ruby と ruby-dev と gauche と gauche-dev が必要です.
$ ls
extconf.rb extgauche.c
$ ruby extconf.rb --with-gauche-include=/usr/lib/gauche/0.8.7/include --with-gauche-lib=/usr/lib/gauche/0.8.7/i486-pc-linux-gnu
checking for gauche.h... yes
checking for main() in -lgauche... yes
checking for main() in -ldl... yes
checking for main() in -lcrypt... yes
checking for main() in -lutil... yes
checking for main() in -lpthread... yes
creating Makefile
$ make
gcc -I. -I. -I/usr/lib/ruby/1.8/i486-linux -I. -DHAVE_GAUCHE_H -I/usr/lib/gauche/0.8.7/include -fPIC -Wall -g -fno-strict-aliasing -O2 -fPIC -c extgauche.c
In file included from /usr/lib/gauche/0.8.7/include/gauche.h:48,
from extgauche.c:9:
/usr/lib/gauche/0.8.7/include/gauche/config.h:280:1: warning: "SIZEOF_OFF_T" redefined
In file included from /usr/lib/ruby/1.8/i486-linux/ruby.h:24,
from extgauche.c:8:
/usr/lib/ruby/1.8/i486-linux/config.h:25:1: warning: this is the location of the previous definition
gcc -shared -rdynamic -Wl,-export-dynamic -L"/usr/lib/gauche/0.8.7/i486-pc-linux-gnu" -L"/usr/lib" -o Gauche.so extgauche.o -lruby1.8 -lpthread -lutil -lcrypt -ldl -lgauche -lpthread -ldl -lcrypt -lm -lc
$ cat test.rb
require "Gauche.so"
g = Gauche.new
s = g.eval("(+ 1 2)")
print s,"\n"
$ ls
Gauche.so Makefile extconf.rb extgauche.c extgauche.o mkmf.log test.rb
$ ruby test.rb
3
まぁ,はてなの方の TCC 日記で,hamaji shin-ichiro さん作の tccparser を読んでいたら Ruby C API がでてきて戸惑ったので,ちょっと勉強してみようかなと.
非常にテキトーなプロトタイプなので,文字コードの変換すらしてません.Gtk と違って,Ruby のデフォルト文字コードは UTF8 では無いので,非 ascii の範囲を使うと文字化けると思います.init.scm も読み込ませてないので,use とか使えないです.
あと,Gauche の GC と Ruby の GC をちゃんと調査してないので,非常にいろいろマズイことしてるかもしれません.
また,Gauche のバージョンは,0.8.7 以前で無いと,Scm_Eval_CString などの C API が変わっていてコンパイルできないかもしれません (Debian の gauche のバージョンはいつになったら上がるのだろうか).
根本的に,Ruby のオブジェクトでは無く,単なる文字列を返してる時点でけっこう終わってますが.Scheme->Ruby へのオブジェクト変換なども真面目に実装すれば,いろいろ面白いことができるかもしれませんし,あんまり意味ないかもしれません.
# あ,外部表現が共通の部分だけならば,単に rb_eval_string() を呼ぶだけで良いのか.gauche 側で印字表現を Ruby 互換に差し替えても良いし.
帰ってきたらちゃんと書き直す (かもしれません)
それではみなさん,良いお年を.
