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モジュールといいます。

  • 組み込み関数(load関数やgc関数など)
  • 組み込みクラス(IntクラスやFloatクラス、Arrayクラスなど)
  • 組み込みモジュール(Iteratorモジュール、mathモジュールなど)

これらは実は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 をマクロ定義してコンパイルするとこの実装に切り替わります。