0.9.1公開
バージョンが上がりました。
修正内容
- libオブジェクト周辺のバグを修正
- moduleに追加定義したメンバが正しく検索されないことがあるバグを修正
- 値に型情報が埋め込まれたバージョンのUncountedAnyを用意
Xtal version 0.9.1
VC7とgcc version 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125)でコンパイル、用意した全てのテストコードがパスするのを確認。
今回からライセンスを修正BSDライセンスからもっと緩いzlib/libpngライセンスに変更します。
商用ゲームでは、説明書などに著作権表記を書かなければならない、というライセンスだと抵抗があるかもしれないと思ったので、特に表記の必要の無い緩いライセンスを採用することにしました。
libオブジェクト
他のファイルに書かれたライブラリを読み込みたい。
(Cでいうinlcude、Rubyでいうrequire、Pythonでいうimportがしたい)
そんな時には libオブジェクト を使います。
libオブジェクトは特殊なオブジェクトで、lib::foo とアクセスした場合、foo.xtalを読み、そこでexportされた値を返す機能を持ってます。
// foo.xtal export "文字列をexportしました";
// test.xtal foo : lib::foo; // ここでfoo.xtalがコンパイルされ、実行される println(foo); // => 文字列をexportしました
lib::fooと書けばその度に何回もコンパイル、実行されるわけではなく、最初の実行で値は保存されるため、2回目以降は保存された値が返ります。
組み込み関数でload関数が用意されているので、それを使えば何度でもコンパイル、実行ができます。
load("foo.xtal");
libオブジェクトは階層にも対応しています。
// folder/bar.xtalをコンパイル、実行してexportされた値を取り出す bar : lib::folder::bar;
また、libオブジェクトにプログラムから登録することもできます。
登録の構文は、クラスやモジュールにメンバを追加定義するのとまったく同じです。
lib::hoge : "register"; // lib::hogeはもう格納されているため、hoge.xtalファイルは探されない println(lib::hoge); // => register // error! classやmodule同様、libオブジェクトも再定義を認めない lib::hoge : "once more"; // 次の文はそもそもコンパイルできない。代入の左辺にメンバ参照式は許可されない。 // lib::hoge = "once more";
libがファイルの検索を開始するパスは builtin::lib_path に配列として格納されています。
ここにパスを追加すれば、そのパスも検索対象となります。
lib_path.push_back("c:/hoge/huge"); println(lib_path.join(", ")); // => ., c:/hoge/huge
この状態で lib::foo と書くと
./foo.xtal、 c:/hoge/huge/foo.xtalと検索されて、最初に見つかったものがコンパイル、実行されます。
toplevelモジュール
コンパイル単位でのトップレベルの変数を保持するモジュールオブジェクトです。
静的に発見できない変数参照は、toplevelモジュールから検索されます。
println(foo); // toplevel::foo と同じ意味
そのため、toplevelにincludeすれば、暗黙的な変数参照の対象となります。
test_module : module{ foo : fun(obj){ println(obj ); } } toplevel.include(test_module); foo(1000); // toplevel::foo(1000) と同じになる
toplevelには最初からincludeされているモジュールが一つあり、それをbuiltinモジュールといいます。
これらは実はbuiltinモジュールのメンバとして存在しています。そのため暗黙的に参照できるのです。
global変数は無い
Xtalにはglobal変数というモノは存在しません。
あるファイルで定義した変数は、他のファイルにまったく影響しません。
ただし、global変数のような動作をするモノをこしらえることはできます。一例を示します。
// global.xtal // 連想配列をexportする。 export [:]; // [:]は空の連想配列リテラル
// a.xtal lib::global["foo"] = 100;
// main.xtal lib::a; // a.xtalをコンパイル、実行させる。 println(lib::global["foo"]); // => 100
型情報を値に埋め込む実装を追加
以前のエントリで言っていた型情報を値に埋め込む件を試してみました。
次のようなイメージの実装になっています。
enum Type{ TYPE_NULL = 0, TYPE_BASE = 1, TYPE_INT = 2, TYPE_FLOAT = 3, TYPE_MASK = (1<<1) | (1<<0), TYPE_SHIFT = 2 }; class Value{ union{ int_t value_; float_t fvalue_; void* pvalue_; }; public: void set_null(){ value_ = TYPE_NULL; } void set_p(void* p){ union{ int_t value; void* pvalue; } u; u.pvalue = p; u.value |= TYPE_BASE; value_ = u.value; } void set_i(int_t v){ value_ = (v<<TYPE_SHIFT) | TYPE_INT; } void set_f(float_t v){ union{ int_t value; float_t fvalue; } u; u.fvalue = v; u.value = (u.value & ~TYPE_MASK) | TYPE_FLOAT; value_ = u.value; } int_t type() const{ return value_ & TYPE_MASK; } int_t ivalue() const{ return value_ >> TYPE_SHIFT; } float_t fvalue() const{ union{ int_t value; float_t fvalue; } u; u.value = (value_ & ~TYPE_MASK); return u.fvalue; } void* pvalue() const{ union{ int_t value; void* pvalue; } u; u.value = (value_ & ~TYPE_MASK); return u.pvalue; } };
これにより実行速度、メモリ使用量ともに若干上がるはずですが、移植性と、整数、浮動少数点数の精度が2bitほど犠牲になります。
それは大抵の環境、大抵の状況では困ることにはならないと思いますが、デフォルトは埋め込まない実装にしてあります。
XTAL_USE_COMPRESSED_ANY をマクロ定義してコンパイルするとこの実装に切り替わります。