クラス実装
組み込みクラス
Riceのクラスは、大きく三つに分類されます。
基本クラス
ユーザ定義クラス
組み込みクラス
基本クラスは、Rice.dll内で定義されているクラスです。
ユーザ定義クラスは、ユーザがRice言語で定義したクラスです。
組み込みクラスは、それ以外のクラスです。
プロジェクトにRiceを組み込んだだけでは、基本クラスが使えるだけです。この状態では、ユーザ定義クラス(Riceプログラム)からアプリケーションを操作することは出来ません。
アプリケーションを操作するためには、ユーザにその方法を与えなければなりません。そして、組み込みクラスこそが、その方法になります。
組み込みクラスは、アプリケーションの実装言語で実装された、Riceで使うためのクラスです。その主な目的は、アプリケーションのフロントエンドを記述することです。
現在のRiceは、.NET Frameworkだけをターゲットとしていますので、C#かVisual Basicで実装されたRice言語のためのクラスともいえます。
組み込みクラスは、アプリケーションのフロントエンドを記述するだけではありません。例えば、ファイルIO、データベースアクセスや画像解析。組み込みクラスを使うことでRice言語の機能を自由に拡張出来ます。
以下の説明は、あなたのプロジェクトに直接、組み込みクラスのコードを記述する方法です。つまり、静的な組み込みです。
動的な組み込み。つまり、アプリケーションの実行時に組み込みクラスを埋め込む方法は、次章で説明します。
組み込みクラス実装のテンプレート
以下に、組み込みクラス実装のテンプレート(C#)を示します。
1: | //組み込みクラスのC#テンプレート |
2: | using Rice; |
3: | |
4: | namespace YourProjectNamespace { |
5: | public class Rtemplate : Rtype { |
6: | public const string TYPENAME = "template"; |
7: | public static Rtype InstanceGetter() { return new Rtemplate(); } |
8: | public Rtemplate() { _typename = TYPENAME; } |
9: | public override Rtype _replica(VirtualMachine vm) { return new Rtemplate(); } |
10: | public override Rbool Fitted(VirtualMachine vm) { return new Rbool(true); } |
11: | |
12: | public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
13: | //フィッタの定義方法は後述します。 |
14: | } |
15: | |
16: | public override bool _setter(string signature, VirtualMachine vm, Rtype arg) { |
17: | //セッタの定義方法は後述します。 |
18: | } |
19: | |
20: | public override Rtype _getter(string signature, VirtualMachine vm) { |
21: | //ゲッタの定義方法は後述します。 |
22: | } |
23: | |
24: | public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
25: | //メソッドの定義方法は後述します。 |
26: | } |
27: | } |
28: | } |
それでは、以下で組み込みクラスの定義の必須要素について説明します。
using ディレクティブ
Rice名前空間を導入します。
名前空間
テンプレートの名前空間"YourProjectNamespace"は、あなたのプロジェクトの名前空間と差し換えてください。
クラス名
クラス名は、仮に"Rtemplate"にしてあります。適当なクラス名と差し換えてください。組み込みクラスとして使うクラスはRice.dllで定義しているRtype型の派生クラスである必要があります。
筆者は、組み込みクラスのクラス名には、接頭辞として大文字のRを使用しています。
Riceプログラム上のクラス名
上記のテンプレートでは、const stringとしてRiceプログラム上のクラス名が指定されています。
public const string TYPENAME = "template";
これを、適当な名前と差し換えてください。組み込みクラスをRiceに登録する際にこの名前を使用します。
他のクラス名と重複しないように注意してください。
デリゲート
組み込みクラスのインスタンス生成はユーザ定義クラスと違い、クラス名と対応付けられたデリゲートによって行われます。デリゲートが返すインスタンスが、Riceプログラムの宣言やnew式で得られるインスタンスです。
上記のテンプレートでは、以下の様に定義されています。
public static Rtype InstanceGetter() { return new Rtemplate(); }
このデリゲートは、引数を取りません。したがって、デリゲート内でデフォルトコンストラクタを呼び出すのが、通常の実装になると思います。
筆者は、デリゲートの名前として、InstanceGetterの使用を推奨します。
このデリゲートは、PlainProtoTypeGetter型のデリゲートとして、rice.csで定義されています。
コンストラクタ
組み込みクラスの全てのコンストラクタで、絶対に_typenameメンバ変数を設定してください。
_typenameメンバ変数は、Rtype型で定義されている、string型のprotectedメンバ変数です。_typenameメンバ変数が、Riceプログラム上のTypeNameゲッタで返されるクラス名となります。
上記のテンプレートでも、以下の様に設定しています。
public Rtemplate() { _typename = TYPENAME; }
TYPENAME定数を使用することで、登録クラス名とRice上のクラス名の一致を保証しています。
_replica()メンバ関数
ユーザ定義クラスのインスタンス生成は、オリジナルインスタンスのコピーを返すことによって行われます。これをレプリカ演算と呼んでいます。
あなたの組み込みクラスがユーザ定義クラスのフィールドに使われた場合、そのユーザ定義クラスのレプリカ演算はフィールドのレプリカ演算を試みます。
組み込みクラスに対するレプリカ演算は、_replica(VirtualMachine vm)メンバ関数の呼び出しです。
上記のテンプレートでは、以下の様に定義されています。
public override Rtype _replica(VirtualMachine vm) { return new Rtemplate(); }
_replica(VirtualMachine vm)メンバ関数は、デリゲートが返すインスタンスと同じインスタンスを返すように実装するべきです。つまり、デフォルトコンストラクタを呼び出すべきです。
Fitted()メンバ関数
Fitted(VirtualMachine vm)メンバ関数は、Fittedゲッタの呼び出しに対して呼び出されるメンバ関数です。
インスタンスが初期化されているかが重要な場合は、このメンバ関数でユーザにインスタンスが初期化されているかを示すことが出来ます。
上記のテンプレートでは、以下の様に定義されています。
public override Rbool Fitted(VirtualMachine vm) { return new Rbool(true); }
上記のテンプレートは、メンバ変数を持たないので初期化が必要ありません。したがって、常にtrueを返すように実装してあります。
あなたの組み込みクラスがメンバ変数を持ち、メンバ変数が設定されているかどうかが重要な場合は、メンバ変数の設定状況に応じて適当な値を返すように実装してください。
_fitter()メンバ関数
public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue)メンバ関数は、フィッタの呼び出しに対して呼び出されるメンバ関数です。
例として、Rtemplateクラスの_fitter()メンバ関数を実装してみます。
1: | public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
2: | switch(signature) { |
3: | case TYPENAME + "()": { |
4: | //デフォルトコンストラクタ。 |
5: | //デフォルトコンストラクタで特別な初期化処理が必要無ければ記述する必要はない。 |
6: | //デフォルトコンストラクタが無い場合はシステムが自動的にデフォルトコンストラクタを生成する。 |
7: | //初期化処理をここに記述。 |
8: | return true; |
9: | } |
10: | case TYPENAME + "(" + Rint.TYPENAME + ")": { |
11: | Rint arg = (Rint)argqueue.Dequeue(); |
12: | //初期化処理をここに記述。 |
13: | return true; |
14: | } |
15: | case TYPENAME + "(" + Rstring.TYPENAME + "," + Rstring.TYPENAME + ")": { |
16: | Rstring firstArg = (Rstring)argqueue.Dequeue(); |
17: | Rstring secondArg = (Rstring)argqueue.Dequeue(); |
18: | //初期化処理をここに記述。 |
19: | return true; |
20: | } |
21: | default: |
22: | return false; |
23: | } |
24: | } |
第一引数でフィッタの呼び出し名(シグネチャ)が渡されます。
例えば、Riceソースコードに以下のような記述があったとします。
template templateInstance = new template(10);
このRiceソースコードの右辺のnew式が新しいtemplateクラス(C#のRtemplate型)のインスタンスを生成しフィッタを呼び出します。つまり、そのRtemplate型のインスタンスの_fitter()メンバ関数が呼び出されます。
この時、new式は"template(int)"という呼び出し名を作成し、_fitter()メンバ関数へ第一引数として渡します。new template();なら"template()"、new template("string", "string");なら"template(string,string)"が呼び出し名として渡されます。
上記の実装例では、switch文で適当な処理に分岐させていますが、呼び出し名を使って正しい初期化処理が実行できるなら、どんな実装でもかまいません。
初期化処理が成功したら、trueを返して_fitter()メンバ関数を終了させます。
trueを返した場合はフィッタの成功を示します。falseを返すことは呼び出し名に対応するフィッタが存在しないことを示します。何らかの理由でフィッタが失敗した場合は例外をスローしてください。
第二引数は、仮想機械です。これは、現在のフィッタ呼出を実行している仮想機械です。
Riceに固有の例外を送出する場合は、この仮想機械を経由して例外のインスタンスを取得します。
第三引数で実際の引数が渡されます。
呼び出し名(シグネチャ)の引数の並び順でQueue<Rtype>型にRtype派生型のインスタンスが格納されて渡されます。
上記の例の様に、Queueから取り出した後、適当なRtype派生型にキャストして下さい。
なお、引数が無い場合は、空のQueue<Rtype>型が渡されます。nullでないことに注意してください。
引数の型、数、並び順等が違えば、フィッタを任意の数だけ定義できます。
_setter()メンバ関数
public override bool _setter(string signature, VirtualMachine vm, Rtype arg)メンバ関数は、セッタの呼び出しに対して呼び出されるメンバ関数です。
例として、Rtemplateクラスの_setter()メンバ関数を実装してみます。
1: | public override bool _setter(string signature, VirtualMachine vm, Rtype arg) { |
2: | switch(signature) { |
3: | case "A(" + Rint.TYPENAME + ")": { |
4: | Rint intArg = (Rint)arg; |
5: | //セッタの処理をここに記述 |
6: | return true; |
7: | } |
8: | case "A(" + Rstring.TYPENAME + ")": { |
9: | Rstring stringArg = (Rstring)arg; |
10: | //セッタの処理をここに記述 |
11: | return true; |
12: | } |
13: | case "B(" + Rint.TYPENAME + ")": { |
14: | Rint intArg = (Rint)arg; |
15: | //セッタの処理をここに記述 |
16: | return true; |
17: | } |
18: | default: |
19: | return false; |
20: | } |
21: | } |
第一引数でセッタの呼び出し名(シグネチャ)が渡されます。
例えば、Riceソースコードに以下のような記述があったとします。
template templateInstance = new template(10);
templateInstance.A = 20;
二行目の左辺のセッタ名で終わる代入文により、templateInstance(C#のRtemplate型のインスタンス)の_setter()メンバ関数が呼び出されます。
この時、代入文は、"A(int)"という呼び出し名を作成し、_setter()メンバ関数へ第一引数として渡します。templateInstance.A = "any string";なら"A(string)"、templateInstance.B = 20;なら"B(int)"が呼び出し名として渡されます。
上記の実装例では、switch文で適当な処理に分岐させていますが、呼び出し名を使って正しい処理が実行できるなら、どんな実装でもかまいません。
セッタの処理が成功したら、trueを返して_setterメンバ関数を終了させます。
trueを返した場合はセッタの成功を示します。falseを返すことは該当するセッタが無いことを示します。何らかの理由でセッタが失敗した場合は例外をスローしてください。
第二引数は、仮想機械です。現在のセッタ呼出を実行している仮想機械が渡されます。
Riceに固有の例外を送出する場合は、この仮想機械を経由して例外のインスタンスを取得します。
第三引数で実際の引数が渡されます。
呼び出し名(シグネチャ)の引数と同じクラスのRtype派生型のインスタンスが引数に格納されて渡されます。
上記の実装例の様に、適当なRtype派生型にキャストして下さい。
同一名のセッタでも引数の型が違えば定義できます。
_getter()メンバ関数
public override Rtype _getter(string signature, VirtualMachine vm)メンバ関数は、ゲッタの呼び出しに対して呼び出されるメンバ関数です。
例として、Rtemplateクラスの_getter()メンバ関数を実装してみます。
1: | public override Rtype _getter(string signature, VirtualMachine vm) { |
2: | switch(signature) { |
3: | case "A": { |
4: | //ゲッタの処理をここに記述 |
5: | return new Rint(); |
6: | } |
7: | case "B": { |
8: | //ゲッタの処理をここに記述 |
9: | return new Rstring(); |
10: | } |
11: | default: |
12: | return null; |
13: | } |
14: | } |
第一引数でゲッタの呼び出し名(シグネチャ)が渡されます。
例えば、Riceソースコードに以下のような記述があったとします。
template templateInstance = new template(10);
int intValue = templateInstance.A;
二行目の右辺のゲッタ呼び出し式により、templateInstance(C#のRtemplate型のインスタンス)の_getter()メンバ関数が呼び出されます。
この時、呼び出し式は、"A"という呼び出し名を作成し、_getter()メンバ関数へ第一引数として渡します。string strValue = templateInstance.B;なら"B"が呼び出し名として渡されます。
上記の実装例では、switch文で適当な処理に分岐させていますが、呼び出し名を使って正しい処理が実行できるなら、どんな実装でもかまいません。
ゲッタの処理が成功したら、適当なRtype派生型のインスタンスを返して_getter()メンバ関数を終了させます。
ゲッタが成功した場合はRtype派生型のインスタンスを返します。nullを返すことは該当するゲッタが存在しないことを示します。何らかの理由でゲッタが失敗した場合は例外をスローしてください。
第二引数は、仮想機械です。現在のゲッタ呼出を実行している仮想機械が渡されます。
Riceに固有の例外を送出する場合は、この仮想機械を経由して例外のインスタンスを取得します。
_metod()メンバ関数
public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue)メンバ関数は、メソッドの呼び出しに対して呼び出されるメンバ関数です。
例として、Rtemplateクラスの_metod()メンバ関数を実装してみます。
1: | public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
2: | switch(signature) { |
3: | case "SampleMethod(" + Rstring.TYPENAME + "," + Rstring.TYPENAME + ")": { |
4: | Rstring firstArg = (Rstring)argqueue.Dequeue(); |
5: | Rstring secondArg = (Rstring)argqueue.Dequeue(); |
6: | //メソッドの処理をここに記述 |
7: | return new Rvoid(); |
8: | } |
9: | default: |
10: | return null; |
11: | } |
12: | } |
第一引数でメソッドの呼び出し名(シグネチャ)が渡されます。
例えば、Riceソースコードに以下のような記述があったとします。
template templateInstance = new template(10);
templateInstance.SampleMethod("first str arg", "second str arg");
二行目のメソッド呼び出し文により、templateInstanceの_method()メンバ関数が呼び出されます。
この時、呼び出し文は、"SampleMethod(string,string)"という呼び出し名を作成し、_method()メンバ関数へ第一引数として渡します。
上記の実装例では、switch文で適当な処理に分岐させていますが、呼び出し名を使って正しい処理が実行できるなら、どんな実装でもかまいません。
メソッドの処理が成功したら、適当なRtype派生型のインスタンスを返して_method()メンバ関数を終了させます。
メソッドが成功した場合はRtype派生型のインスタンスを返します。nullを返すことは該当するメソッドが存在しないことを表します。何らかの理由でメソッドが失敗した場合は例外をスローしてください。
返り値が無い場合は、Rvoid型のインスタンスを返します。
第二引数は、仮想機械です。現在のゲッタ呼出を実行している仮想機械が渡されます。
Riceに固有の例外を送出する場合は、この仮想機械を経由して例外のインスタンスを取得します。
第三引数で実際の引数が渡されます。
呼び出し名(シグネチャ)の引数の並び順と同じ並び順でQueue<Rtype>型にRtype派生型のインスタンスが格納されて渡されます。
上記の実装例の様に、Queueから取り出した後、適当なRtype派生型にキャストして使用して下さい。
なお、引数が無い場合は、空のQueue<Rtype>型が渡されます。nullでないことに注意してください。
引数の型、数、並び順等が違えば、同一名のメソッドを任意の数だけ定義できます。
クラスの組み込み方法
組み込みクラスをRiceに登録する方法を説明します。
public static void AddBuiltIn(string typename, PlainProtoTypeGetter pptg)が、RiceManagerクラス(C#)の静的なメンバ関数として用意してあります。
このメンバ関数にクラス名とPlainProtoTypeGetter型のデリゲートを渡して呼び出せば、組み込みクラスの登録は終了です。
これが、組み込みクラスをRiceに登録する唯一の方法です。
上記のRtemplateクラスなら次の様になります。
RiceManager.AddBuiltIn(Rtemplate.TYPENAME, Rtemplate.InstanceGetter);