Xtalにはスクリプトをコンパイルしたり実行したりする関数が多くあります。
ここでは、それらについて、基本的な動作及びどのような時にどれを使えば良いかの指針を示したいと思います。
C++及びXtalから利用できるスクリプト実行関連の関数を一覧にまとめた次の表をご覧ください。
C++ | Xtal |
---|---|
CodePtr compile_file(const StringPtr& file_name) | Code compile_file(String file_name) |
CodePtr compile(const AnyPtr& source, const StringPtr& source_name) | Code compile(Any source, String source_name) |
AnyPtr load(const StringPtr& file_name) | Any load(String file_name, Arguments args) |
CodePtr source(const char_t* src, int_t size) | - |
void exec_source(const char_t* src, int_t size) | - |
CodePtr compiled_source(const void* src, int_t size) | - |
void exec_compiled_source(const void* src, int_t size) | - |
CodePtr require_source(const StringPtr& name) | Code require_source(String name) |
AnyPtr require(const StringPtr& name) | Any require(String name) |
CodePtr eval_compile(const AnyPtr& source) | Code eval_compile(Any source) |
- | lib::file_name |
(こんなあるのか……)
データへのポインタ及びデータ長を引数として受け取ります。物理的なデータサイズではなく論理的なデータの長さという点に注意が必要です(source/exec_sourceで_UNICODEが有効かどうかによって呼び出し側の変更は不要)。
よって、データをパッキングした場合などにはこれらの関数を用いるのが便利です。
CodePtr source(const char_t* src, int_t size){ return compile_detail(xnew<PointerStream>(src, size*sizeof(char_t)), empty_string); }
メモリ上に存在するプログラム(文字列)データをコンパイルして結果を返します。CodePtrに格納されるファイル名(エラー表示などで利用される)には空文字列が利用されます。
void exec_source(const void* src, int_t size){ if(CodePtr code = source(src, size)){ code->call(); } else{ XTAL_CATCH_EXCEPT(e){ stderr_stream()->println(e); } } }
sourceが成功した場合にはプログラム引数なしでcallを呼び出し、コンパイルエラー(コンパイル済みのはずなので形式がおかしかったりした場合)のみcatchして標準エラー出力に内容を表示します。
CodePtr compiled_source(const void* src, int_t size){ StreamPtr ms = xnew<CompressDecoder>(xnew<PointerStream>(src, size)); return ptr_cast<Code>(ms->deserialize()); }
メモリ上に存在する、CompressEncoderによって圧縮されたバイトコードの先頭アドレスを引数として受け取ります。なお、CompressEncoderを介していないバイトコードを読み込みたい場合には、CompressDecoderを利用しないバージョンを自分で作る必要があると思われます。
void exec_compiled_source(const void* src, int_t size){ if(CodePtr code = compiled_source(src, size)){ code->call(); } else{ XTAL_CATCH_EXCEPT(e){ stderr_stream()->println(e); } } }
compiled_sourceが成功した場合にはプログラム引数なしでcallを呼び出し、コンパイルエラー(コンパイル済みのはずなので形式がおかしかったりした場合)のみcatchして標準エラー出力に内容を表示します。
以下の関数で内部的に使われているcompile_or_deserializeは次のようになっています。
CodePtr compile_or_deserialize(const AnyPtr& source, const StringPtr& file_name){ // Streamのサブクラスかをチェック StreamPtr stream = ptr_cast<Stream>(source); if (!stream && ptr_cast<String>(source)){ // Streamのサブクラスではないが、Stringである場合には // StringStreamでラップして以下で同様に扱えるようにする stream = XNew<StringStream>(unchecked_ptr_cast<String>(source)); } // Streamの中身がバイトコードかどうかをチェックする // 先頭4文字が"xtal"かどうかで確認しているので、 // xtal_hogehogeみたいな変数がコード先頭に存在していると良くないことになる if (stream){ u8 head[4] = {0}; stream->read(head, 4); stream->seek(0); if (head[0]=='x' && head[1]=='t' && head[2]=='a' && head[3]=='l'){ return ptr_cast<Code>(stream->deserialize()); } } // 実際にコンパイル return compile_detail(stream, file_name); }
上記コメント内の先頭4文字”xtal”についてですが、例えば次のような正しいコードをcompile(load)しようとすると、
xtal_hoge : 0;
次のようにエラーになってしまいますので注意してください。
lib::builtin::RuntimeError: XRE1009:不正なコンパイル済みXtalファイルです。
CodePtr compile(const AnyPtr& source, cnst StringPtr& source_name){ return compile_or_deserialize(source, source_name); }
sourceにはStreamのサブクラスもしくはStringのインスタンスを指定します。source_nameはコンパイルエラー時に出力される名前です(デフォルト引数は空文字列)。
file_nameにファイルへのパスを指定します。そのパスでファイルをopenし、compile_or_deserializeに投げます。
CodePtr compile_file(const StringPtr& file_name){ if(StreamPtr fs = open(file_name, XTAL_STRING("r"))){ CodePtr ret = compile_or_deserialize(fs, file_name); fs->close(); return ret; } return nul<Code>(); }
file_nameで指定されたファイルを開き、コンパイルして引数なしで実行します。メモリ上のコードに対してloadのようなことをしたい場合(あまりないと思いますが)、exec_sourceを利用してください。ただし、exec_sourceは戻り値がvoidになっていたり、内部でXTAL_CATCH_EXCEPTしているなど、使い勝手がわずかに異なっている点には注意してください。
AnyPtr load(const StringPtr& file_name){ if(CodePtr code = compile_file(file_name)){ return code->call(); } return undefined; }
戻り値は、実行されたスクリプトのtop-levelでreturnされた値となります。何もreturnしなかった場合にはundefinedが返ります。
// foo.xtal return "this is test xtal file";
xtal::load("foo.xtal")->p();
これらのコードの出力は以下のようになります。
this is test xtal file
Xtalからloadを呼び出す際には、引数を渡すことが可能です。呼び出された側からは、argという名前で参照が可能です
// foo.xtal "foo.xtal".p; size : arg.size; for (i : 0; i < size; ++i) { arg[i].p; }
// bar.xtal load("foo.xtal"); load("foo.xtal", 1, 2);
foo.xtal foo.xtal 1 2
いわゆるevalです。デバッガやゲーム内コンソール的なもので使うことができます。evalそのものについてはこちらをご覧ください。
でもなんかうまく動作してないような…… ここら辺、何かあったような気がしますが思い出せません。ご存知の方がいらっしゃいましたら教えてください。
さて、ここから先は使い方が少しトリッキーなものを解説していきます。これまで述べたもので十分便利に使うことができますので、回れ右して他の有意義な時間の使い方をしていただいても結構です。
requireした結果のCodeを引数なしで実行します。戻り値はloadと同様top-levelでのreturnの結果です。
AnyPtr require(const StringPtr& name){ if(CodePtr ret = require_source(name)){ return ret->call(); } return undefined; }
とまあ説明してみたわけですが、スクリプトからアプリケーション側のコード管理部分の間にライブラリ側のコード管理レイヤーを挟むせいで動作がわかりにくくなっており、いっそ封印しても良いレベルです。
封印する場合には、xtal/src/xtal_lib.hでLibの継承元をAutoLoaderからClassに変えれば大丈夫です。たぶん。