IronPythonで式評価 その2

DIコンテナにおける式評価のために必要なのは、以下の二つのインタフェースを実装することだ。まずは評価式のためのインタフェース。これはSeasae2で使用しているものと、似たインタフェースだ。

public interface IExpression
{
    object Evaluate(IDIContainer container, IDictionary context);
    string ExpresionString { get; set; }
}

拙作のDIコンテナは.NET 2.0専用としているので、引数(this、標準出力、 エラー、etc)を渡すためのコンテキストパラメタ用の辞書は、後述するIronPythonの実装もあり、Generics-Dictionaryに変更している。もう一つは、今回新たに用意した、評価エンジンのためのインタフェース。既にJScriptWindowsPowerShellと実装しているが、今回更にIronPythonが追加されるため、いい加減インタフェースとして分離した。

    public interface IEvaluator
    {
        object Evaluate(string exp, IDictionary ctx, object root);
        object Evaluate(string exp, object root);
    }

実装はIExpressionインタフェースの実装クラスであるIronPythonExpressionクラスと、そのインナークラスのIronPythonEvaluatorクラスだが、核心のIronPythonEvaluatorクラスだけ紹介しておこう。(ビルドするためにはIronPythonのインストールと、IronPython.dllアセンブリへの参照が必要なのは書くまでもない)

using IronPython.Hosting;
public class IronPythonEvaluator : IEvaluator
{
    private PythonEngine engine;

    public IronPythonEvaluator()
    {
        EngineOptions engineOptions = new EngineOptions();
        engineOptions.ShowClrExceptions = true;
        this.engine = new PythonEngine(engineOptions);
    }
    public object Evaluate(string exp, IDictionary ctx, object root)
    {
        IDictionary globals = new Dictionary();
        globals["root"] = root;
        globals["exp"] = exp;
        EngineModule module = this.engine.CreateModule("IronPythonEvaluator", globals, true);
        this.engine.Execute("import System");
        this.engine.Compile("result = eval(exp)").Execute(module, ctx);
        return this.engine.Evaluate("result", module, ctx);
    }
    public object Evaluate(string exp, object root)
    {
        return this.Evaluate(exp, new Dictionary(), root);
    }
}

渡されたパラメタのうち、ルートオブジェクト(通常はDIコンテナへの参照)と、評価式そのものをPythonのモジュールスコープとして使用するためには、以下のように辞書にセットし、

        IDictionary globals = new Dictionary();
        globals["root"] = root;
        globals["exp"] = exp;

その辞書を引数にして、以下のようにモジュールを生成する。これで評価式全体で引数"root"と"exp"を参照できる。

        EngineModule module = engine.CreateModule("IronPythonEvaluator", globals, true);

あとは、スクリプトをコンパイルして実行、戻り値を評価して返す。

        this.engine.Execute("import System");
        this.engine.Compile("result = eval(exp)").Execute(module, ctx);
        return this.engine.Evaluate("result", module, ctx);

これで完成だ。JScriptのときはで括ることにしたが、IronPythonで式評価を行うためにはで評価式を括ることにした。

2+3+4

しかし、式評価エンジンの実装が増えるたびに要素が増えるのも、DTDを修正することを考えるとそろそろ厳しくなってきた。ただし、従来の考え通りデフォルトでは式評価エンジンを通したくないので、ここは検討課題ということで。

使い勝手としてだが、そもそも私がPythonを殆ど使ったことが無いこともあり、今ひとつの状態だ。そもそも、IronPythonの評価式上(eval)で"DateTime.Now"をどう書くかすら判らない状態だ。(そもそも評価式中でも有効な、.NET Frameworkのラィブラリィのインポートが書けるのだろうか? 書き方があるのであれば、どなたか、是非教えて頂けないだろうか)
正直、ここがJScript.NETや、WindowsPowerShellとは勝手が違うところだろう。

今回はIronPythonDIコンテナの式評価エンジンとして利用する、という限定された目的で使用したが、静的な言語と、それを補う動的な言語との関係というのは非常に面白い。このようにコンパイルが必要な静的な部分と、実行時の遅延解釈が必要な動的な部分が修練しながら、最終的には静的な言語と動的な言語は境目が無くなっていくのだろうか。

追記
スクリプトを実行する際、サンプルではコンパイルして実行、その結果を評価していた。

this.engine.Execute("import System");
this.engine.Compile("result = eval(exp)").Execute(module, ctx);
return this.engine.Evaluate("result", module, ctx);

しかし、これは以下の一行で書いても結果は同じのはず。

return this.engine.Evaluate("eval(exp)", module, ctx);

この場合コンパイルという手段が選択できるのは、やはりILを前もって出力しておき、2度目以降の実行の効率を上げる目的があるからなのだろうか。