構文だけを示しても実感がないでしょうから、どのように実行されるかを記述します。実装言語はJavaということにします。
●コーラブルの実装方式
コーラブル(述語、関数、コマンド)に組み込みのものは存在しません。すべてユーザー定義です。次のようなインターフェースの実装クラスとしてコーラブルを定義します。
public interface Callable {
public Object call(Object[] args) throws Exception;
}
public interface Function extends Callable {
}
public interface Predicate extends Function {
Boolean call(Object[] args) throws Exception;
}
public interface Command extends Callable {
}
コーラブルの名前は、getName()を設けるとかアノテーションで書くとかの方法もありますが、ここでは素朴に“設定ファイル”を使うことにします。設定ファイルの1行は、「種別 名前 クラス名」とでもしておきましょう。例えば:
predicate empty org.example.hoge.Empty
predicate less org.example.hoge.Less
function add org.example.hoge.Add
command doUpdate com.example.foo.DoUpdate
この方式では次の点を注意する必要があります。
- 設定ファイルには引数・戻り値の情報がないので、引数の個数・型のチェックはコーラブル実装側の責任になる。
- ==, <, + などの基本的演算子もないので、コーラブルとして実装する必要がある。
- コーラブルが所属する名前空間はフラットなので、名前の衝突に気を付ける。
いずれも好ましくない特徴ですが、まー、これでもいいとします。ユーザー定義の(プラグインの)コーラブルは、引数なしコンストラクタで生成されて、設定された種別と名前で使用されます。
問題や要求があれば、コーラブルに関する情報を(アノテーションを含む)リフレクションにより取得して、精密な引数・戻り値情報に基づいた型チェックをフレームワーク側で行うことも考慮します(ってことは、問題/要求がなければやらないってこと)。
●フレームワークの仕事
構文解析によって作られたルールのデータ構造をたどりながら、次のチェックをします。
- 条件式のトップレベルに出現するコーラブルが“存在する述語”であるかどうか。
- アクションのトップレベルに出現するコーラブルが“存在する”かどうか。
- 引数内に出現するコーラブルが“存在する関数”であるかどうか。
以前と考えが変わったのは、「アクションのトップレベルはコマンド」の制限をはずして、どんなコーラブルでもよい、としたところです。
次に、式とアクションの評価ですが:
- 変数は出現したら生成し(データ構造をたどったときに作っておいてもいいが)、初期値はnullとする。
- リテラルはそれ自身に評価される。
- コーラブルの呼び出しは、事前に全ての引数を評価して引数リスト(配列)を作り、生成したコーラブル実体のcallを呼び出す。
- 代入文は、右辺を評価した結果を変数としてセットする。代入文以外の“文の値”は捨てる。
変数とコーラブルは、名前をキーとしてマップで管理するでしょうが、変数とコーラブルで別なマップを使うことにします。つまり、同じ名前の変数とコーラブルを許します(この事を後で使うかもしれない)。
●それから
以上のような評価に便利なようにパーズ結果データ構造を考えることにします。それと、ルールの成功・失敗に応じた処理についても考える必要があります。