BASIC/簡易言語によるScheme実装を考える会

追記: 図のコメントが間違ってたので修正
teslawireではゲームロジックの記述は基本的にSchemeで行おうと思っている。というのは、ネイティブコードを受け付けない2Dゲームエンジン、要するに

への移植をスムースに進めるために適当な高級言語が必要で、こういう時に使うのはSchemeと相場が決まっているため。
... この手の言語もだいぶ収斂が進んでしまった気もする。もうちょっと環境を追加したいが良い2Dエンジンが見つかっていない。(Unityは別枠で移植する予定)

NScLisper の構造

イムリーに数年振りの更新が有ったNScLisperはNScripter上に実現された(Scheme風の)Lispで、NScripterの伝統的なゲームVM( http://d.hatena.ne.jp/mjt/20111226/p1 )の制約の中でGC付きのLispを実現している。

... 意外とGC付きのこの手のLispは少なく、Mal( https://github.com/kanaka/mal/ )でもGCは基本的に省略されている。
NScripterはいわゆるADVゲーム用のスクリプトエンジンなので、↑の同人誌で書かれているような制約が適用される。

  • 整数演算のみ、ビット演算なし
  • 整数と文字列で別々の変数領域
  • 整数と文字列それぞれで最大4096個の変数が使用可能
  • 関数なし、ラベルによるgoto/gosubのみ。
  • 変数名なし。変数のラベルとしては数値のみが使用できる。

(ちなみにNScripter2ではLuaとBASICをサポートし、もっと高度なスクリプトが書けるようになっている - このBASICの仕様はかなりプチコンに近い)
必然的に、NScLisperは4096個までのオブジェクトしか使用できない(実際にはもっと少い)。関数が存在しないことについては、一部の変数領域をスタックとして使用することで解決している。

(たぶんソースコードのコメントの方がわかりやすい - https://github.com/zick/Magical-Language-Lyrical-Lisp/blob/master/0.txt )
NScripterの重要な言語機能としては変数の間接参照が可能であることが挙げられる。NScripterの変数番号は0〜4095が使用できるが、全ての命令で1段までの間接参照(= 変数による変数番号の指定)が行えるため、擬似的なポインタを実現することができる。逆に、これが無いとスタックすら実現できない。
NScripterは32bit符号付き整数を使用するが、NScLisperの数値はGCのマークビットとタグ、符号ビットの分引かれて25bitsになる。いわゆるbignumは存在しない。
同人誌で説明されていない重要な機能追加としてはGVARの導入が有る:

NScLisperのデザインはアクションゲームを作るには少々厳しいが、大いに参考になる。

(各種言語の簡単な概要)

ターゲットになる処理系3つの特徴をごく簡単に説明すると:

プチコン3号はNintendo 3DSで動作するBASIC言語処理系で、行番号の無い比較的モダンなBASICとゲームBASICとしての特徴を持つ。Nintendo 3DSでしか動作しないが、WiiU版の提供も予定されている。統合されているレンダリングエンジンはコンソールを意識した独自のもの。

HSP3はWindows向けのインタプリタと各種環境向けのC言語トランスレータを持つ。HSP3.4以降のHGIMG4に統合されているレンダリングエンジンはGameplay( http://www.gameplay3d.io/ )ベースのもの。文法はBASIC風。

GMLはメジャーな2DゲームエンジンであるGameMakerのスクリプト言語で、いくつかのプラットフォームやコンソール向けには言語トランスレータを持つ。ゲームエンジンは当然GameMakerそのもの。以前はDelphi製のインタプリタだったが現状は謎。文法は比較的C風だが、個々のゲームオブジェクトに設定するアクションスクリプトであったという歴史的事情により1関数1ファイルというかなり独特なスコープルールが有る。

SmileBASICとHSP3 / GameMaker Language(GML)の機能的な違い

いづれの処理系もBASICを意識した機能性を備えているものの、細かい違いは多い。

  • GMLにはgotoが無い

GMLには構文としてのラベルが存在しないため、当然gotoも存在しない。switchのようなC言語風構文は備えるので少々意外な気もする。
ついでに、GMLソースコード中で関数が宣言できず特殊コメントとして設定したタイトルを指定して関数のようにプログラムを呼ぶ機能だけがある。幸いこの機能は16個までの引数渡しと1個のreturnが可能。

  • HSPだけがlabel as valueを行える

今回のユースケースにはあまり実用的価値が無いが、HSPにはlabel as valueとして使用できる"ラベル型"変数が存在する。SmileBASICでも文字列変数をラベルとして使用できるが、多分実行時のラベル引きが有るのでそれほど早くないと思われる(試していない)。

  • SmileBASICは動的に配列を生成できない

これはかなり困る。SmileBASICはメモリ制約のある環境を意識しているのか、動的に配列を生成することができない。要するにSchemeで言うところのvector手続きというかCで言うところのmallocというかを簡単に実装できないため、工夫が必要になる。
あとSmileBASICは配列長を変更するためにはpush/popするしか無いという微妙な制約もある。例えばGMLではa[10] = 0のようにして配列を確保した後、a = 0のように通常変数として何かを代入することで拡張された配列を解放することができる。
SmileBASICの文字列は一種のu16配列であり、実はA$="HOGE"のような配列でない文字列変数も、A$[2]のように配列としてアクセスすることはできる。当然文字列は動的に確保できるため、動的な配列を文字列で代用することは一応可能となっている。(遅そう)

  • HSPとSmileBASICは型無し配列が無い

HSPとSmileBASICの配列は昔懐しのDIM命令で宣言する配列であり、文字列型配列には数値を代入することはできないし、その逆も同様となっている。これらの処理系ではNScLisperと同様のタグ付き整数によるオブジェクト表現が必要になる。
GMLでは文字列と数値は1つの配列中に同居させることができる。が、一般的に使えるtypeofが無い(is_stringの類は有るが、listのような組込みデータ構造(ds)に相当品が無い)ため結局型情報をどこかに持たせる必要は有る。

次: SmileBASICでの実装を考える

次はSmileBASICでの実装を考える。たぶんGMLHSPに比べてより強い制約があり、SmileBASIC上で実現できることは残りの2つでも実現できるという予想。。あと残りの2つはPC上で動作するため、プロセッシングパワーにもそれなりの余裕が期待される。
アクションゲームで使う上で実用的な処理系にするためには、多分配列変数を本当にメモリに見立ててヒープ管理をする実装は不可避なのではないかという気がしている。連続する配列変数の要素をいくつかバンドルしてSchemeオブジェクトとして扱ったり、bytevectorを32bit整数配列上に実現する等いくつかの興味深いトピックがある。
また、当然、ベースとなるゲームエンジンの機能を使用するためのFFIも避けて通ることはできない。。