こんにちは、鈴木です。
「C言語からGaucheを使おう!」シリーズも 8 回目となりました。
前回の「C言語からGaucheを使おう! (7) デバッグ用の道具を準備する」ではデバッグ出力する方法を調べました。
今回は Scm_LoadFromPort 関数で文字列として用意した Scheme プログラムを load する方法を調べます。
Scm_LoadFromPort 関数
Scm_LoadFromPort 関数を使用すると、入力ポートから Scheme プログラムをロードすることができます。
今回はリテラルで用意した文字列から文字列ポートを作成し、そこから Scm_LoadFromPort でロードします。
サンプルコードです。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #include <stdio.h> #include <gauche.h> /**  * ScmEvalPacket や ScmLoadPacket に含まれる例外情報を表示します.   */ #define PRINT_EXCEPTION(packet) \     printf("[%s] %s\n", \            SCM_STRING_CONST_CSTRING(Scm_ConditionTypeName(packet.exception)), \            SCM_STRING_CONST_CSTRING(Scm_ConditionMessage(packet.exception))); void load_from_port() {     ScmObj source = SCM_MAKE_STR(         "(define sum (lambda (xs)"         "    (apply + xs)))"         "(define average (lambda (xs)"         "    (/ (sum xs) (length xs))))"     );       ScmObj port = Scm_MakeInputStringPort(SCM_STRING(source), TRUE);     ScmLoadPacket load_packet;     if(Scm_LoadFromPort(SCM_PORT(port), 0, &load_packet) < 0) {         PRINT_EXCEPTION(load_packet);         return;     }    } void apply_something() {     ScmObj procedure;     ScmObj arguments;     ScmEvalPacket eval_packet;     /* (apply sum '((1 2 3))) */     procedure = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("sum")), SCM_BINDING_STAY_IN_MODULE);     arguments = SCM_LIST1(SCM_LIST3(SCM_MAKE_INT(1), SCM_MAKE_INT(2), SCM_MAKE_INT(3)));     if(Scm_Apply(procedure, arguments, &eval_packet) < 0) {         PRINT_EXCEPTION(eval_packet);         return;     }        Scm_Printf(SCM_CUROUT, "%S\n", eval_packet.results[0]);     /* (apply average '((1 2 3))) */     procedure = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("average")), SCM_BINDING_STAY_IN_MODULE);     arguments = SCM_LIST1(SCM_LIST3(SCM_MAKE_INT(1), SCM_MAKE_INT(2), SCM_MAKE_INT(3)));     if(Scm_Apply(procedure, arguments, &eval_packet) < 0) {         PRINT_EXCEPTION(eval_packet);         return;     }     Scm_Printf(SCM_CUROUT, "%S\n", eval_packet.results[0]); } int main() {     GC_INIT();     Scm_Init(GAUCHE_SIGNATURE);     load_from_port();     apply_something();     return 0; } | 
いくつかの関数に分かれていますが、load_from_port 関数の中で、
| 1 2 3 4 5 | (define sum (lambda (xs)     (apply + xs))) (define average (lambda (xs)     (/ (sum xs) (length xs)))) | 
という Scheme プログラムをロードしています。
そして動作確認として apply_something 関数の中で、
| 1 2 | (apply sum '((1 2 3))) (apply average '((1 2 3))) | 
相当の処理を実行しています。
まずは実行結果を見てみましょう。(※「C言語からGaucheを使おう! (1) コンパイル環境を整える」で作成した Makefile を使用します)
| 1 2 3 | > make run 6 2 | 
出力結果の 1 行目は「(apply sum '((1 2 3)))」の結果、つまり 1, 2, 3 の合計である 6 です。
2 行目は「(apply average '((1 2 3)))」の結果、つまり 1, 2, 3 の平均である 2 です。
load_from_port 関数
load_from_port 関数を詳しく見ていきましょう。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void load_from_port() {     ScmObj source = SCM_MAKE_STR(         "(define sum (lambda (xs)"         "    (apply + xs)))"         "(define average (lambda (xs)"         "    (/ (sum xs) (length xs))))"     );       ScmObj port = Scm_MakeInputStringPort(SCM_STRING(source), TRUE);     ScmLoadPacket load_packet;     if(Scm_LoadFromPort(SCM_PORT(port), 0, &load_packet) < 0) {         PRINT_EXCEPTION(load_packet);         return;     }    } | 
まず、source という変数に文字列で準備した Scheme コードを代入しています。
余談ですが、C 言語では連続する文字列リテラルは、ひとつの文字列リテラルに連結されます。つまり、「"a" "b" "c"」は「"abc"」と解釈されます。
次に Scm_MakeInputStringPort 関数を用いて、文字列から入力ポートを作成し、変数 port に代入しています。
そして、Scm_LoadFrmPort で作成した入力ポートから Scheme コードをロードしています。
これで今回やろうとしていた Scheme プログラムのロードは完了です。
apply_something 関数
apply_something 関数は動作確認のために作成した関数です。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void apply_something() {     ScmObj procedure;     ScmObj arguments;     ScmEvalPacket eval_packet;     /* (apply sum '((1 2 3))) */     procedure = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("sum")), SCM_BINDING_STAY_IN_MODULE);     arguments = SCM_LIST1(SCM_LIST3(SCM_MAKE_INT(1), SCM_MAKE_INT(2), SCM_MAKE_INT(3)));     if(Scm_Apply(procedure, arguments, &eval_packet) < 0) {         PRINT_EXCEPTION(eval_packet);         return;     }     Scm_Printf(SCM_CUROUT, "%S\n", eval_packet.results[0]);     /* (apply average '((1 2 3))) */     procedure = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("average")), SCM_BINDING_STAY_IN_MODULE);     arguments = SCM_LIST1(SCM_LIST3(SCM_MAKE_INT(1), SCM_MAKE_INT(2), SCM_MAKE_INT(3)));     if(Scm_Apply(procedure, arguments, &eval_packet) < 0) {         PRINT_EXCEPTION(eval_packet);         return;     }     Scm_Printf(SCM_CUROUT, "%S\n", eval_packet.results[0]); } | 
load_from_port でロードした Scheme プログラムに含まれている手続きを Scm_Apply で呼び出しています。
コメントにも書いていますが、「(apply sum '((1 2 3)))」と「(apply average '((1 2 3)))」相当の処理を実行しています。
まとめ
長い道のりでしたが、ひとまず Scheme プログラムをロードして C 言語から呼び出す(値の受け渡しも行う)、ということが達成できました。

 
						