【2020-08-19追記】Python3版の記事を作成しました.
【2020-08-18追記】GNU Guile版の記事を作成しました.
Schemeは,そのコンパクトな言語仕様から,機能拡張用言語としてアプリケーションソフトウェアに処理系自体が組み込まれることがある.そのような仕組みは,GNU EmacsのEmacs Lispが有名だが,Schemeを主言語とするGNU Guileが組込用を想定して開発されており,また,Julia処理系には独自開発の小さなSchemeインタプリタが組み込まれてフロントエンド(パーサ)に用いられている.
この記事では,同じく組み込みSchemeライブラリとしての利用も想定されているGaucheについて,Scheme処理系APIをC言語から利用する,ごく簡単な記述サンプルをまとめている.動作確認環境は次の通り.
- Raspberry Pi 4 Model B (Raspberry Pi OSプリインストール)
- gcc (Raspbian 8.3.0-6+rpi1) 8.3.0
- Gauche 0.9.9(ソースコードからインストール)
#C言語プログラムでScheme記述を文字列として渡して評価結果を表示する例
#include <stdio.h>
#include <gauche.h>
#define FIB "(let ((n (read))) (map (lambda (x) (let f ((i x) (f1 0) (f2 1)) (if (= i 0) f1 (f (- i 1) f2 (+ f1 f2))))) n))"
int main(int argc, char **argv)
{
GC_INIT();
Scm_Init(GAUCHE_SIGNATURE);
ScmEvalPacket eRet;
if (Scm_EvalCString(FIB, SCM_NIL, &eRet) < 0) {
printf("Error in S expression\n"); return (1);
}
Scm_Printf(SCM_CUROUT, "%S\n", eRet.results[0]);
Scm_Exit(0);
return(0);
}
コンパイルおよび実行例は次の通り.
$ cc -Wall `gauche-config -I` -o gauche-fib gauche-fib.c `gauche-config -l`
$ ./gauche-fib
(0 10 20 30 40) # キーボード入力
(0 55 6765 832040 102334155) # 表示結果
#特定のSchemeファイルを読み込んで評価した後,Scheme記述を文字列として渡した評価結果を文字列として受け取る例
#include <stdio.h>
#include <gauche.h>
#define APPINITSCM "~/.appinit.scm"
#define S_NAME "(cadr (assq 'name *my-profile*))"
#define S_TWITTER "(cadr (assq 'twitter *my-profile*))"
const char* getScmString(ScmObj *obj)
{
ScmObj sStr, oPort = Scm_MakeOutputStringPort(TRUE);
Scm_Printf(SCM_PORT(oPort), "%S", obj);
sStr = Scm_GetOutputString(SCM_PORT(oPort), 0);
return Scm_GetStringConst(SCM_STRING(sStr));
}
int main(int argc, char **argv)
{
GC_INIT();
Scm_Init(GAUCHE_SIGNATURE);
ScmLoadPacket lRet;
if(Scm_Load(APPINITSCM, 0, &lRet) < 0) {
printf("There is no %s.\n", APPINITSCM);
return (1);
}
ScmEvalPacket eRet;
if (Scm_EvalCString(S_NAME, SCM_NIL, &eRet))
printf("Name: %s\n", getScmString((ScmObj *)eRet.results[0]));
if (Scm_EvalCString(S_TWITTER, SCM_NIL, &eRet))
printf("Twitter: %s\n", getScmString((ScmObj *)eRet.results[0]));
Scm_Exit(0);
return(0);
}
特定のSchemeファイルの例は次の通り.いわゆる設定ファイルを模したもの.
$ cat ~/.appinit.scm
(define *my-profile*
'((name "TAKIZAWA Yozo") (twitter "@ytaki0801")))
コンパイルおよび実行例は次の通り.
$ cc -Wall `gauche-config -I` -o gauche-initfile gauche-initfile.c `gauche-config -l`
$ ./gauche-initfile
Name: "TAKIZAWA Yozo"
Twitter: "@ytaki0801"
#特定のSchemeファイルを読み込んで評価した後,C言語内でリスト構造を作成して引数とし,手続きをapply
で実行した結果を整数値として受け取る例
#include <stdio.h>
#include <gauche.h>
#define APPINITSCM "~/.appinit.scm"
int main(int argc, char **argv)
{
GC_INIT();
Scm_Init(GAUCHE_SIGNATURE);
ScmLoadPacket lRet;
if (Scm_Load(APPINITSCM, 0, &lRet) < 0) {
printf("There is no %s.\n", APPINITSCM);
return (1);
}
int x = 40;
if (argc == 2) x = atoi(argv[1]);
ScmEvalPacket eRet;
static ScmObj proc = SCM_UNDEFINED;
SCM_BIND_PROC(proc, "fib", Scm_UserModule());
ScmObj args = SCM_LIST3(SCM_MAKE_INT(x),SCM_MAKE_INT(0),SCM_MAKE_INT(1));
if (Scm_Apply(proc, args, &eRet) < 0) { // (apply fib '(40 0 1))
printf("Error in S expression\n");
return (1);
}
printf("fib(%d) = %ld\n", x, SCM_INT_VALUE(eRet.results[0]));
Scm_Exit(0);
return(0);
}
特定のSchemeファイルの例は次の通り.新規に関数fib
を定義している.
$ cat ~/.appinit.scm
(define fib (lambda (n f1 f2) (if (= n 0) f1 (fib (- n 1) f2 (+ f1 f2)))))
コンパイルおよび実行例は次の通り.
$ cc -Wall `gauche-config -I` -o gauche-call gauche-call.c `gauche-config --static-libs`
$ ./gauche-call
fib(40) = 102334155
$ ./gauche-call 0
fib(0) = 0
$ ./gauche-call 5
fib(5) = 5
$ ./gauche-call 10
fib(10) = 55
#(以下,必要に応じて追加)
#備考
##記事に関する補足
- 本当はGNU Guileの方をまとめたかったんですが(だってそのために作られてるんだし),公式サイトの割と本格的な組み込み例か初期化サンプルしか見つからなかったので,相応の例がさっくり用意できたGaucheの方からまとめて,その例に対応するGuile記述例を整理していきたいかなとか.言い訳終わり.【2020-08-18追記】あ,GNU Guileの場合の手頃な解説ページ見つけた.古いけど.そのうちGNU Guile版も追記してタイトル変えるかも.→新規記事にしました.
- あくまでサンプル記述なので,今後もごく簡単な例しか載せない予定です.でも,これらだけでも,結構実用的なんじゃないかなあ.二番目の設定ファイル用途とか,自作アプリに静的ライブラリ+Schemeライブラリ数MB程度追加で組み込めますよ,どうですか(謎のセールス).
- もともとこの記事を書こうと思ったきっかけは,Lisp-1(Scheme) vs Lisp-2(Common Lisp)骨肉の争いをネットで見たので.個人的にはシンプルなLisp-1派だけど,それはあくまで好みの問題で,実用面ではいろんな言語やライブラリを分野向き不向きで使い分けていけばいいんじゃないかなあと.そういう意味では,Common Lispで書かれたSchemeインタプリタを開発ソフトウェアのフロントエンドにして…いや,Julia処理系みたいにバックエンドは効率と実績を重視してC/C++中心に…あれ,そういえばGNU GuileやGaucheがもともとそうじゃん.…という壮大な寄り道連鎖から本記事に至りました.寄り道過ぎる.でも,C言語ソースコードにScheme記述が混ざってるのってなんかいい感じ.正規表現ライブラリみたいで(錯乱中).
- ああ,Pythonもインタプリタ埋め込みを想定している…けど,やはりというか,インタプリタ拡張に比べてマニュアル記述が圧倒的に少ないか.APIは拡張方法を見ればわかるということかな.いやでも,Pythonはフロントエンドというよりはバックエンドかなあ(偏見).ユーザがPythonプログラミングで凝った設定や機能拡張を行うことを想定するアプリケーション…Schemeでも同じか.あ,PythonプログラムでScheme記述できるようにするってのはどうかな.ほら,lambda式や高階関数使いたかったらSchemeコード書いてくれみたいな.ダメか.
##参考文献
##更新履歴
- 2020-08-19:Python3版の記事リンク追記
- 2020-08-18:例その3を追加,記事に関する補足追記,GNU Guile版記事リンク追記
- 2020-08-17:初版公開(例その1,例その2,記事に関する補足,他)