こんにちは、鈴木です。
「C言語からGaucheを使おう!」シリーズです。
前回の「C言語からGaucheを使おう! (3) リスト操作」までで、Gauche の API を用いて値の生成とリストの操作ができるようになりました。
Gauche API だけで S 式を組み立てるのは大変なので、今回は Scm_EvalCString 関数で eval する方法を調べます。
初心を思い出す
ディテールを追いかけ続けていて目標を見失いそうなので、初心を思い出しておきます。
目標は「Gaucheを組み込み言語として使うこと」です。
具体的には「メインの処理を Scheme で記述し、ホスト言語(C 言語)から定義された手続きを呼び出す」を実現しようとしています。
直近では以下の内容を調べるつもりです。
- ホスト言語(C言語)で Scheme コードを組み立てて評価する(eval)
- 文字列やファイルで用意された Scheme コードを読み込む(load)
- 定義済みの手続きを引数付きで呼び出し、戻り値(評価結果の値)を取得する(apply)
そして今回は一つ目の「ホスト言語(C言語)で Scheme コードを組み立てて評価する(eval)」を調べます。
Scm_EvalCString 関数
以下の Scheme コードに相当する処理を行いたいと思います。
1 |
(eval '(+ 10 20) (interaction-environment)) |
eval 相当の処理を行う関数には Scm_Eval と Scm_EvalCString がありました。
Scm_Eval はリストとして組み立てられたオブジェクト、Scm_EvalCString は C 言語の文字列を eval する関数です。
まずは Scm_EvalCString から試してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <stdio.h> #include <gauche.h> int main() { const char *source = "(+ 10 20)"; ScmEvalPacket eval_packet; GC_INIT(); Scm_Init(GAUCHE_SIGNATURE); if(Scm_EvalCString(source, SCM_OBJ(Scm_UserModule()), &eval_packet) < 0) { printf("[%s] %s\n", SCM_STRING_CONST_CSTRING(Scm_ConditionTypeName(eval_packet.exception)), SCM_STRING_CONST_CSTRING(Scm_ConditionMessage(eval_packet.exception))); } else { printf("%ld\n", SCM_INT_VALUE(eval_packet.results[0])); } return 0; } |
先に実行結果を見てみましょう。
1 2 |
> make run 30 |
(+ 10 20) を評価したので、結果は 30 です! 当たり前ですね(^^;
ソースコードを詳しく見ていきます。
まず、source という変数に評価したい Scheme コード(S 式)を文字列として準備しています。
Scm_EvalCString の最初の引数には評価したい Scheme コードを含む文字列を指定します。
少しだけ脱線しますが、Scm_EvalCString の CString とは「C 文字列(C-String)」のこと、つまり C 言語における普通の文字列のことです。「C 文字列」とは NULL 文字終端された文字列のことで、「ゼロ終端文字列」や「NULL終端文字列」とも呼ばれます。「C 文字列」以外には「Pascal 文字列」というものもあります。「Pascal 文字列」は NULL 文字終端されておらず、バイト数と文字列データの組み合わせを持ちます。
Scm_EvalCString の 2 番目の引数は、Gauche の main.c を参考にして書いたのですが、おそらく評価する環境です。Scm_UserModule() が interaction-environment 相当のものだと予想しています。
3 番目の引数は評価結果を受け取る ScmEvalPacket のポインタです。
ScmEvalPacket 構造体
ScmEvalPacket は gauche.h で定義されています。
1 2 3 4 5 6 7 8 9 10 11 |
/* The new APIs to run Scheme code from C. Returns # of results (>=0) if operation is successful, -1 if an error is occurred and captured. All result values are available in ScmEvalPacket. Exceptions are captured and returned in the ScmEvalPacket. */ typedef struct ScmEvalPacketRec { ScmObj results[SCM_VM_MAX_VALUES]; int numResults; ScmObj exception; ScmModule *module; /* 'Current module' after evaluation */ } ScmEvalPacket; |
ScmEvalPacket の中身は以下のようになっています。
- results ... 評価結果の値
- numResults ... results に含まれる値の数
- exception ... 例外発生時の例外オブジェクト
- module ... 評価後の「現在のモジュール」
Scm_EvalCString が成功した場合は results から評価結果を取得、失敗した場合は exception から発生したエラーを取得すれば良さそうです。
エラー処理
エラー時の処理を見てみましょう。
Scm_EvalCString を呼び出しているコードを再掲します。
1 |
if(Scm_EvalCString(source, SCM_OBJ(Scm_UserModule()), &eval_packet) < 0) { |
Scm_EvalCString は処理に失敗した場合に -1 を返します。
上記コードでは「< 0」で比較していますが、Gauche のコードがそうなっていたので、「 == -1 」ではなく「 < 0 」で比較しています。
エラーが発生した場合は、以下のエラーメッセージを表示する処理が実行されます。
1 2 3 |
printf("[%s] %s\n", SCM_STRING_CONST_CSTRING(Scm_ConditionTypeName(eval_packet.exception)), SCM_STRING_CONST_CSTRING(Scm_ConditionMessage(eval_packet.exception))); |
エラーの情報は eval_packet.exception に保持されており、Scm_ConditionTypeName でエラーの種類、Scm_ConditionMessage でエラーメッセージを取得できます。
それぞれの戻り値は ScmString なので、printf で表示するときは「C言語からGaucheを使おう! (2) 基本的な値の生成」で登場した SCM_STRING_CONST_CSTRING マクロで C 文字列を取得します。
成功時の処理
成功した場合の処理を見てみましょう。
1 |
printf("%ld\n", SCM_INT_VALUE(eval_packet.results[0])); |
評価結果の値を取得して printf で表示しています。
評価結果の数は eval_packet.numResults に入っていますが、「(+ 10 20)」を評価した結果の値は 1 つだけと分かっているのでチェックは省略しました。
values で多値が返された場合は eval_packet.numResults が 1 以上になるようです。
「(+ 10 20)」の評価結果は「30」、つまり数値なので、「SCM_INT_VALUE(eval_packet.results[0])」として値を取り出しています。
結果として、「30」と出力されます。
まとめ
「初心を思い起こす」という項目に直近で調べる予定の内容を書きました。
実はそこに書いた内容は今回全て調べてしまおうと思っていたのですが、そんなに甘くはありませんでした。
ということで、次回は Scm_Eval の使い方を調べようと思います。