====== C++からXtalオブジェクトを操作する ====== ===== 組み込みクラスを使う ===== Xtalスクリプトに組み込み型として必要なこともあって用意されているxtal::Stringとかxtal::ArrayとかをC++でも使うことができます。 Xtalの他のオブジェクトとやりとりしない限りはそのままインスタンス化するだけです。何もありません。\\ そんな使い方なら僕はSTLあたりを使えば良いと思いますがー。 一方で、Xtalの関数やらに渡すときにはxtal::SmartPtrを使う必要があります。テンプレートクラスですが、別に意識しなくても済むようにxtal::StringPtrやxtal::ArrayPtrといったものが用意されています。タイプも面倒なので基本的にはそちらを使うことになります。 これを使う場合にはxtal::xnewを使って初期化してあげる必要があります。(コンストラクタに引数をもつ場合にはxnewを使わなくても良いのですが、とりあえずは混乱を防ぐために使っておくのが良いと思います。) また、xtal::AnyPtrは「Xtalに関係しているオブジェクトなら(基本的には)何でも入れることができる型」です。自分で定義した型も(適切な手順を踏めば)例外ではなく、XtalとC++とのやりとりのスムーズさにつながっています。これに関しては次のページで扱うことにします。 xtal::Nullやxtal::Undefinedはすでにインスタンスが用意されているので、例えば「null入れたいなー」と思ったときはxtal::nullを代入すれば良いですし、nullチェックしたければ if (any != xtal::null) とすれば良いです(まあオーバーロードされてるので if (any) だけでも良いんですけど)。 2つの文字列を結合するサンプルコードです。 void exec_xtal(){ using namespace xtal; // TPtrはxnewで初期化する、と覚えておけば良い const StringPtr str1(xnew("foo")); // Stringはconst char_t*を引数にとるコンストラクタを持つのでそのまま指定することも一応可能 const StringPtr str2("bar"); const StringPtr str3(str1->cat(str2)); // AnyPtrはなんでも入れることのできる型 AnyPtr any(xnew(10)); any = xnew(); any = str3; stdout_stream()->println(str3); stdout_stream()->println(any); } xtal::String::catは受け取った文字列との結合結果を返します。その型はStringPtrです。 xtal::AnyPtrにいろいろな型を順番に代入してみています。コンパイルも通りますし実行時にエラーも出ません。 ===== オブジェクトを取得する ===== Xtalスクリプトで定義されたオブジェクトをC++から参照する方法を紹介します。 ざっと思いつく限りでは次の通りです。 - libオブジェクトに定義する - globalオブジェクトに定義する - filelocalに定義する - top-levelからreturnする - 関数の引数に渡したオブジェクトに追加する ==== 1.libオブジェクトに定義する, 2.globalオブジェクトに定義する ==== libオブジェクト、globalオブジェクトとは、Xtal環境内でグローバルな連想配列(キーと値のペア。マップやテーブルとも呼ばれる)のことです。libとglobalは基本的には同じような動作をし、両方とも定義された変数への代入が禁止されますが、globalは再定義ができます。 // test.xtal lib::ConstValue : 100; global::ConstValue : 100; // libはこういうことができない global::ConstValue : 150; void exec_xtal(){ using namespace xtal; load("test.xtal"); const AnyPtr LibConstValue(lib()->member(Xid(ConstValue))); const AnyPtr GlobalConstValue(global()->member(Xid(ConstValue))); stdout_stream()->println(LibConstValue); stdout_stream()->println(GlobalConstValue); } xtal::lib()->member、xtal::global()->memberによって、それぞれの空間から変数の値を参照することができます。\\ Xidというのはマクロで、変数や関数の名前を指定するときにはこのようにXidでくくって指定する、と覚えてしまってかまいません。 簡単に説明すると、Xtalのクラスの実装は、変数や関数は全てAnyPtrで管理され、[文字列のハッシュ値:AnyPtr]というテーブルからひっぱってくるのがmemberなのですが、Xidというのは文字列からハッシュ値に簡単に変換するマクロです。なので、別にXidを使わずにmember("ConstValue")などとしてもうまく動きますが、Xidを使うよりも遅くなる可能性があります。 ==== 3.filelocalに定義する ==== Xtalのファイルには"filelocal"という概念が存在しています。ファイル内グローバル領域のようなもので、XtalスクリプトファイルにおいてはC++の無名名前空間みたいなものです。C++からはアクセスできますが。だいたいそんな感じです。 // test.xtal // 明示的にfilelocalに定義してみる filelocal::value1 : 100; // filelocal:: をつけると再定義、代入ができない // filelocal::value1 = 150; (compile error) // filelocal:: をつけない場合、r429ではC++からアクセス可能、r437ではアクセス不可能、r438以降ではアクセス可能 value2 : 10; value2 = 15; void exec_xtal(){ using namespace xtal; const CodePtr code(compile_file("test.xtal")); code->call(); const AnyPtr value1(code->filelocal()->member(Xid(value1))); const AnyPtr value2_1(code->filelocal()->member(Xid(value2))); const AnyPtr value2_2(code->member(Xid(value2))); stdout_stream()->println(value1); stdout_stream()->println(value2_1); stdout_stream()->println(value2_2); } C++からも、Xtalスクリプトのようにfilelocalをつけてもつけなくてもアクセスすることができます。 ==== 4.top-levelからreturnする ==== xtal::Code::callやxtal::loadの戻り値は、Xtalスクリプトのトップレベルからreturnした値になります。なにもreturnしなかった場合はfilelocalが返ります。 // test.xtal return "This is return value from Xtal script."; void exec_xtal(){ using namespace xtal; const AnyPtr ret_value(load("test.xtal")); stdout_stream()->println(ret_value); } ==== 5.関数の引数に渡したオブジェクトに追加する ==== ここでいうオブジェクトというのは、xtal::Mapやxtal::Arrayのことです。Xtalではプリミティブ型以外は参照渡しとなるので、引数にinsertやpush_backといった操作をすることで呼び出し元のオブジェクトも変更されます。 fun global::append(map){ map["foo"] = "bar!"; } void exec_xtal(){ using namespace xtal; load("test.xtal"); MapPtr map(xnew()); global()->member(Xid(append))->call(map); stdout_stream()->println(map); } ===== 関数を実行する ===== なんらかの手段でC++側にFunPtrをもってくることができたら、あとはcallするだけで関数を呼び出すことができます。 引数はf->call(param1, param2, param3, param4);といった感じで指定します。\\ 現在は13個程度の引数を指定できます。それ以上は構造体に一回代入したものを渡したり複数回にわけるなどして対処することとなります。 上のサンプルコードが引数を1つ指定した関数呼び出しのサンプルともなっています。 ===== クラスのインスタンスを生成する ===== class lib::A{}みたいに定義すると、libにはAという名前のClassPtrが定義されます。これをcallするだけでクラスのインスタンスを生成することができます。 // test.xtal // クラスを定義します class lib::TestClass{ _var : 0; // メンバ変数 initialize(x){ // コンストラクタ。Class::callではこれが呼ばれる _var = x; } // method testMethod(x){ x.p(); println(%f[testMethod call: _var = %s](_var)); } // class Func fun classFunc(){ println("classFunc is called"); } } void exec_xtal(){ using namespace xtal; const AnyPtr TestClass(lib()->member(Xid(TestClass))); stdout_stream()->println(TestClass); // lib::TestClass(100)と同じ AnyPtr instance(TestClass->call(100)); instance->send(Xid(testMethod), "testMethod called"); TestClass->member(Xid(classFunc))->call(); } クラスに定義されたメソッドと関数は、呼び出し方が異なります。メソッドはpInstance->send(Xid())と呼び出すのに対して、関数はpClass->member(Xid())->call()と呼び出します。 ===== メンバ変数にアクセスする ===== publicなメンバ変数_fooを定義すると、foo()というgetterとset_foo(x)というsetter(どちらもメンバ関数)が定義されます。 よって、これらをsendで呼び出すことでクラスのメンバ変数にアクセスすることが可能となります。 // test.xtal class Bar{ + _foo : "foo"; } lib::bar : Bar(); void exec_xtal(){ using namespace xtal; load("test.xtal"); AnyPtr bar(lib()->member(Xid(bar))); stdout_stream()->println(bar->send(Xid(foo))); stdout_stream()->println(bar->member(Xid(_foo))); // 無理 bar->send(Xid(set_foo), "changed foo"); stdout_stream()->println(bar->Xid(foo)); } _fooというメンバ変数を操作してみたサンプルです。 「_fooはインスタンスlib::barのメンバだから……」ってmemberでやろうとしても無理なので、面倒でもsendでset/getする必要があります。 ===== 型変換(1) AnyPtrとプリミティブ型・組み込み型 ===== まず、AnyPtrからプリミティブ型についてです。 ここで言うプリミティブ型とは、xtal::int_t, xtal::float_t, const xtal::char_t*((デフォルト・マルチバイト文字セットでは、それぞれint, float, charです)) を指します。 プリミティブ型への変換には、xtal::Any::to_i, xtal::Any::to_f, xtal::String::c_strが用意されています。\\ xtal::String::c_strだけString型である必要があるので、AnyPtrからの変換にはxtal::Any::to_sをかませて一回StringPtrを得る必要があります。 // プリミティブ型を要求する関数 void print_int(int i){ xtal::stdout_stream()->println(i); } void print_float(float f){ xtal::stdout_stream()->println(f); } void print_str(const char *s){ xtal::stdout_stream()->println(s); } void exec_xtal(){ using namespace xtal; const AnyPtr i(100); const AnyPtr f(10.5f); const AnyPtr s("foobar"); // intとfloatに関してはコンパイルエラーは出ないが // 正しい値ではない内部データが渡されてしまう print_int(i); print_float(f); // そのまま渡すことはできない //print_str(s); print_int(i->to_i()); print_float(f->to_f()); print_str(s->to_s()->c_str()); print_int(f->to_i()); } 次に、xtal::AnyPtrから組み込み型についてです。 まずxtal::Anyに定義されている変換用メンバ関数には、上ででてきたto_sのほかに、配列型に変換するto_a、連想配列型に変換するto_mが存在しています。 それ以外の型に関しては、xtal::ptr_cast, xtal::unchecked_ptr_castを用います。 ptr_castは、xtal::AnyPtrを受け取ってxtal::SmartPtrを返します。変換できない場合(実際の型と違うものに変換しようとした場合)にはxtal::nullを返します。\\ 一方unchecked_ptr_castは、実際には変換できない型同士だとしても無理やり変換してしまいます。もちろんおかしい場合には動かないので、「確実にこの型」といえるところにだけ使うべきです。 void exec_xtal(){ using namespace xtal; const AnyPtr any(xnew()); const MemoryStreamPtr stream(ptr_cast(any)); stream->put_s("bar"); stdout_stream()->println(stream); // 変換できない場合にはnullが返る stdout_stream()->println(ptr_cast(any)); } 価値があるコードとは思いませんが説明のためなので…… また、xtal::Any::to_*とxtal::ptr_castは全く別のものです。以下に例を示します。 void exec_xtal(){ using namespace xtal; const AnyPtr s("foobar"); stdout_stream()->println(ptr_cast(s)); stdout_stream()->println(s->to_a()); } どちらもxtal::ArrayPtrを返す操作ですが、前者はnullを返すのに対し、後者は1文字ずつ要素となる配列に変換されています。\\ **ptr_castは厳密な型変換、to_*はその型にあうような「良い感じ変換」**ということができるでしょう。 またこのto_*は自分で定義することも可能です。Xtalで定義したクラスにおいてはとにかく欲しいto_*をつくれば良く、また、C++で定義したクラスにおいては、Xtalからto_*として見えるように次のページで示すようにバインドしてあげることでうまくいきます。 プリミティブ型・組み込み型(SmartPtr)からAnyPtrへの変換に関しては、何も気にせずにコンストラクタにも渡せますし、代入することもできます。特別に気にすることはありません。