こんにちは、鈴木です。
「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 言語から呼び出す(値の受け渡しも行う)、ということが達成できました。