Squirrel での開発環境を整える

最初はかなりやりにくかったのですが、いろいろ弄ってるうちに、大分開発するのが楽になってきたので、その方法をメモしておきます。

注意

このエントリーは 2010/11/8 に書かれたもので、あなたが見ているときには古くなっている可能性があります。
Squirrel は発展途上の言語なので、多分すぐにこの内容は古くなって正しくなくなります。適時新しい情報を本家本家のフォーラムSquirrelWiki から入手して下さい。

このエントリーを読んで得られるもの

事前に用意しておくもの

  • Visual Studio(2008 以降ぐらいがいいと思う)
  • Squirrel 3 beta 3 (このバージョンから zlib/libpng license → MIT license へ変更された)

C++ から Squirrel を使おう

C++ から Squirrel のコードを呼び出したり、Squirrel から C++ のコードを呼び出したりといった連携をするためのライブラリをバインダと呼びます。
普通に SquirrelAPI だけで呼び出そうと思うとかなり辛いので、バインダを使うことを勧めます。
ただ、今も Squirrel はバージョンが上がっているので、そのままだと動かない可能性もあります。直接弄るか、動かない旨を報告してバージョンアップを待ちましょう。


Squirrel のバインダは今のところ以下の 3 つがあるようです。

Squadd

Boost.PP、Boost.TypeTraits、Boost.MPL を使っててかなりでかい力作という感じがする。ヘッダオンリー。sqstd にも依存してない。
Luabind や Boost.Python に似せてるらしいです。
あと何かここのダウンロードページに wxSquirrel というのも置いてあって、多分 wxWidgets のバインダだと思いますけどちゃんと見てないです。

SqPlus

ソースの量は少ないですが、ヘッダオンリーではなく、sqstd にも依存してます。
VM の情報をグローバルに持っていたりと個人的にはあまり好きではない作り方。
スナップショットを見てみると DXSquirrel とかいう、DirectXDirect3D あたりっぽい?)のバインダがあったりします。これは使う人には結構便利なんじゃないかなー。
というか DXSquirrel は SquirrelWiki に直接置いてあった。

Sqrat

ソースの量が少なく、Sqrat 本体は完全にヘッダオンリーで、その中の sqratScript.h が sqstd を使っています。これは include しなければ問題ないです。
また、Sqrat Import というモジュールインポートライブラリがあり、これはヘッダオンリーではなく、sqstd に依存しています。これは Squirrel の中から別のファイルをロードしてインポートするということができるので、結構便利な気がします。
あと Sqrat Thread というのがあるけど、これは多分使わないです。


追記:jkBind とかいうのもあるらしいのだけど、全然更新されてない(2006/11/29)みたいなので見なかったことに。
というかよく見たら Squadd は 2005/04/28、SqPlus は 2007/07/13、Sqrat は 2009/09/23 となってて、Sqrat が一番今後に期待できそうかなーという感じ。

Squirrel 本体のコードを追ってみよう

Squirrel のバージョンは今でもどんどん上がっているので、問題が発生したときのために、出来る限り読みやすくしておいた方がいいでしょう。
Squirrel の変数は union を使っていたりしてそのままデバッガで見ただけだとかなり読みにくいので、autoexp.dat あたりを弄りましょう。
既に先人がやってくれています。
Visual Studio Visualizers for squirrel debugging - Embedding - Squirrel - The Programming Language

Squirrelソースコードデバッグしよう

まともに開発を行うなら、ステップ実行や変数ウォッチはできないと辛いです。
今のところ、以下の 2 つがあります。

前者は Eclipse 上で動作するデバッガで、後者は Visual Studio 2008 以降で動作するデバッガです。
個人的には Visual Studio を使いたいのですが、まだ動作が安定してない(stable って書いてあるのに…)っぽいので、今のところは SQDEV でのデバッグをオススメします。まあやり方は細かいのを除けばどちらも一緒です。
また、両者は SQDBG というライブラリ(というか通信プロトコル)に依存しているので、それも手に入れます。

SQDEV のインストール

SQDEV のページの一部を Eclipse 3.6.1 向けに訳しただけです。

  1. Eclipse で、Help => Install New Software... を選択する
  2. "Work with:" に http://sqdev.sf.net/update/ と入力すると SQDev というプラグインが出てくるのでそれを選択
  3. "Next" とか "Finish" を選択してればインストールできる。

SQDEV のページには外部指定のシンタックスチェック機構とかあるのですが、使い方が分からなかったせいかちゃんとちゃんと使えなかったです。というか SQDEV はまだ Squirrel 3 に対応していないので、新しい構文使ったら赤線が出ます。そこら辺は今後に期待。

サーバ側を弄る

必要なファイルは sqrdbg.h, sqrdbg.cpp, sqdbgserver.h, sqdbgserver.cpp, serialize_state.inl です。自分の書いているプロジェクトへ追加するか、lib 形式か何かにして呼び出せるようにしておきましょう。main.cpp は参考用です。
サーバ側(スクリプトを実行する側)は、sqdbg を使って、クライアント(デバッガ側)の接続を待ちます。

HSQUIRRELVM v;
// ここら辺で sq_open で VM を作って、必要であれば sq_setprintfunc を設定したり、
// sq_setcompilererrorhandler や sq_seterrorhandler を使って各種エラーハンドラを設定する。

// コンパイル時にデバッグ情報を含める。これをしないとデバッグできない。
sq_enabledebuginfo(v, SQTrue);

// リモートデバッガの初期化
HSQREMOTEDBG rdbg = sq_rdbg_init(v, 1234, SQTrue);

const SQChar* fileName;
const SQChar* source;
int sourceLength;
// ここら辺で fileName から Squirrel のスクリプトを読んで source へ設定する

// クライアントの接続を待つ
if (SQ_SCCEEDED(sq_rdbg_waitforconnections(rdbg))
{
    // コンパイルして実行
    // このソースはクライアント側でデバッグすることができる
    sq_compilebuffer(v, source, sourceLength, fileName, SQTrue);
    sq_pushroottable(v);
    sq_call(v, 1, SQFalse, SQTrue);
    // 必要に応じてファイルを読み替えて実行したりとか、
    // さっきのバインダを使って関数やクラスを公開したりとかする
}

// 全部の処理が終わったら終了処理
sq_rdbg_shutdown(rdbg);
sq_close(v);

そんなに難しくないです。Squirrel のコードを普通にコンパイルして実行する前に sq_enabledebuginfo と sq_rdbg_init を実行して sq_rdbg_waitforconnections で待つだけですね。
注意としては、fileName の中身です。これは Eclipseデバッグするときは(少なくとも自分の場合は)パス名を一切含まないファイル名のみの名前になっていました。この名前がクライアント側で使用している名前と一致していない場合はちゃんと動かないので注意して下さい。
クライアント側で使用している名前は sqdbgserver.cpp にある SQDbgServer::ParseMsg 関数の

                scprintf(_SC("added bp %d %s\n"),bp._line,bp._src.c_str());

あたりの出力を見れば分かると思います。

デバッガを起動する

細かいのは SQDEV のページの画像を見れば分かると思います。
サーバ側が sq_rdbg_waitforconnections で待ってるはずなので、こっちはデバッガを開始すると同時にそこへ接続しに行きます。
Autorun Interpreter にチェックを入れておけば、サーバ側も勝手に起動します。
これでデバッグできる!と思ったのですが、そうもいかないようで。

Squirrel 3 に対応する

SQDBG は Squirrel 3 に対応していないため、このままだとちゃんと動きません。
sqdbg/serialize_state.nut を開いて、以下のように修正します。

--- serialize_state.nut.old	2005-10-02 21:17:20.000000000 +0900
+++ serialize_state.nut.new	2010-11-07 05:05:08.652375000 +0900
@@ -13,7 +13,7 @@
 	["weakref"] = null,
 }
 
-function build_refs(t):(objs_reg)
+function build_refs(t)
 {
 	if(t == ::getroottable())
 		return;
@@ -23,7 +23,7 @@
 		if(!(t in objs_reg.refs)) {
 			objs_reg.refs[t] <- objs_reg.maxid++;
 		
-		    iterateobject(t,function(o,i,val):(objs_reg)
+		    iterateobject(t,function(o,i,val)
 		    {
 			    build_refs(val);
 			    build_refs(i);
@@ -33,7 +33,7 @@
 	}
 }
 
-function getvalue(v):(objs_reg)
+function getvalue(v)
 {
 	switch(::type(v))
 	{
@@ -74,7 +74,7 @@
 	["weakref"]="w"  
 }
 
-function pack_type(type):(packed_types)
+function pack_type(type)
 {
 	if(type in packed_types)return packed_types[type]
 	return type
@@ -109,7 +109,7 @@
 			
 }
 
-function build_tree():(objs_reg)
+function build_tree()
 {
 	foreach(i,o in objs_reg.refs)
 	{

単に ":(xxx)" となっているのを削除しただけですね。この部分は Squirrel 3 では不要になっています。
で、このソース自体は sqdbg は使っていないので、これをバイナリデータにしたのを sqdbg/serialize_state.inl へ上書きします。バイナリエディタとか適当に使ってやってください。


これで無事デバッガが動くはずです。

さらに改造する

sqdbg を使っていると、時々止まったりして残念な気分になることがあります。
例えばこんなケース

Main <- class {
    // この部分をメイン側から毎フレーム呼んで貰うことにする
    update() {
        ...
    }
}

こうやって、update() の中でステップオーバーをしつつ update() の外に出ると、そのまま止まることなく次の update() が呼ばれて動き続けます。
これは sqdbgserver.cpp の SQDbgServer::Hook 関数にある以下の部分が原因です。

    case eDBG_StepOver:
        switch(type){
        case _SC('l'):
            if(_nestedcalls==0) {
                Break(line,src,_SC("step"));
                BreakExecution();
            }
            break;
        case _SC('c'):
            _nestedcalls++;
            break;
        case _SC('r'):
            if(_nestedcalls==0){
                _nestedcalls=0;
                
            }else{
                _nestedcalls--;
            }
            break;
        }
        break;

ステップオーバーをして関数を抜けた場合、case _SC('r') が実行されます。中断された時点では _nestedcalls は 0 になっているため、これは 0 のままになります。で、次のフレームで update() が呼ばれると、case _SC('c') が実行され、_nestedcalls が 1 になります。で、1行実行される毎に case _SC('l') が実行されるわけですが、_nestedcalls は 1 になっているので永遠に中断されません。非常に残念です。
あと、なぜか Break を呼び出すときに "step" を指定すると少しよく分からない動作をした(よく調べてないです)ので、全部 "breakpoint" に変えました(ただ VS のデバッガを使ったときは step にしないとちゃんと動かなかったです)。以下のようになります。

--- sqdbgserver.cpp.old	2005-10-02 21:17:26.000000000 +0900
+++ sqdbgserver.cpp.new	2010-11-08 12:43:04.011750000 +0900
@@ -205,6 +206,15 @@
 
 void SQDbgServer::Hook(int type,int line,const SQChar *src,const SQChar *func)
 {
+	if (type == _SC('c'))
+	{
+		_nestedcalls++;
+	}
+	if (type == _SC('r'))
+	{
+		_nestedcalls--;
+	}
+
 	switch(_state){
 	case eDBG_Running:
 		if(type==_SC('l') && _breakpoints.size()) {
@@ -216,33 +226,21 @@
 		}
 		break;
 	case eDBG_Suspended:
-		_nestedcalls=0;
+		break;
 	case eDBG_StepOver:
 		switch(type){
 		case _SC('l'):
-			if(_nestedcalls==0) {
-				Break(line,src,_SC("step"));
+			if(_nestedcalls<=_breaknest) {
+				Break(line,src,_SC("breakpoint"));
 				BreakExecution();
 			}
 			break;
-		case _SC('c'):
-			_nestedcalls++;
-			break;
-		case _SC('r'):
-			if(_nestedcalls==0){
-				_nestedcalls=0;
-				
-			}else{
-				_nestedcalls--;
-			}
-			break;
 		}
 		break;
 	case eDBG_StepInto:
 		switch(type){
 		case _SC('l'):
-			_nestedcalls=0;
-			Break(line,src,_SC("step"));
+			Break(line,src,_SC("breakpoint"));
 			BreakExecution();
 			break;
 		
@@ -250,19 +248,10 @@
 		break;
 	case eDBG_StepReturn:
 		switch(type){
-		case _SC('l'):
-			break;
-		case _SC('c'):
-			_nestedcalls++;
-			break;
 		case _SC('r'):
-			if(_nestedcalls==0){
-				_nestedcalls=0;
+			if(_nestedcalls<_breaknest) {
 				_state=eDBG_StepOver;
-			}else{
-				_nestedcalls--;
 			}
-			
 			break;
 		}
 		break;
@@ -433,6 +422,7 @@
 
 void SQDbgServer::BreakExecution()
 {
+	_breaknest = _nestedcalls;
 	_state=eDBG_Suspended;
 	while(_state==eDBG_Suspended){
 		if(SQ_FAILED(sq_rdbg_update(this)))
@@ -537,7 +527,7 @@
 		sq_createslot(_v,-3);
 	}
 	sq_rawset(_v,-3);
-	if(SQ_SUCCEEDED(sq_call(_v,1,SQTrue))){
+	if(SQ_SUCCEEDED(sq_call(_v,1,SQTrue,SQTrue))){
 		if(SQ_SUCCEEDED(sqstd_getblob(_v,-1,(SQUserPointer*)&sz)))
 			SendChunk(sz);
 	}

これで自分的には満足する動きになりました。

sqdbg を組み込みで使う

とにかくソケット通信さえできればいいので、例えば携帯とかそういうのを使っている場合、sqdbg を改造してその環境が持っているソケット通信の機能を使って、デバッガのある PC と通信すればいいです。
まだ試してませんが、これができるようになれば組み込み環境でどんどん使っていけそうですね。