こんにちは、高木です。

前回予告したとおり、今回はいよいよスクリプトの評価部分を作っていきます。Tclの今回をなす部分ですので、ここができれば最低限のことはできるようになります。

Tclのスクリプトを評価する関数は、公式ドキュメントによると合計9種類あります。たくさんあるのですが、大別すると、スクリプトをTcl_Obj型で受け取るもの(Tcl_EvalObjEx関数など)、char文字列で受け取るもの(Tcl_Eval関数など)、ファイルから読み込むもの(Tcl_EvalFile関数など)の3種類になります。

このうち、スクリプトをchar文字列で受け取るものは古いバージョンとの互換性のために用意されています。バイトコンパイルを行わないので、一度しか評価されないスクリプトであればchar文字列で受け取る関数の方がTcl_Obj型を受け取る関数より若干速いようです。

ただ、一度しか評価されないのであればその差が問題になることはほぼありませんし、明示的にバイトコンパイルを抑止することもできますので、今回はchar文字列を受け取る関数は対象外とし、Tcl_Obj型を受け取る関数を内部で呼ぶ出すことにします。

Tcl_Obj型(正確にはTcl_Obj*)を受け取る関数にもいくつかのバリエーションがあるのですが、今回はそのうち一番オーソドックスなTcl_EvalObjEx関数だけを扱うことにします。最終的な目標はTkを使うことですので、ここであまり頑張る必要はないと考えます。

ファイルから読み込む関数にはTcl_EvalFile関数などがあります。日本語などを扱う場合はエンコーディングの問題を無視できませんので、実際にはFileSystemのページに掲載されているTcl_FSEvalFileEx関数を使うことになるのでしょう。これは少し後回しにしようと思いますので今回は扱いません。

ということで方針が見えてきました。基本的にはTcl_EvalObjEx関数をラップしたメンバー関数を用意します。objクラスのconst参照を引数として受け取るようにしておけば、char8_t文字列を渡しても暗黙的にobjに型変換してくれますので便利です。

それではコードを見ていきましょう。

上記のように、interpreterクラスのpublicかつ非静的なメンバー関数としてevaluateを用意してあげればOKです。非常にあっさりしています。

flagsには、0または、TCL_EVAL_GLOBALとTCL_EVAL_DIRECTを必要に応じてORした値を指定することができます。TCL_EVAL_GLOBALを指定すれば、(Tclの)名前空間を考慮せずに変数はすべてグローバルなものとして扱われます。TCL_EVAL_DIRECTを指定すればバイトコンパイルされずに(char文字列としてスクリプトを渡したときのように)直接評価されます。念のため指定できるようにはしていますが、通常は0で大丈夫だと思います。

返却値は、スクリプトの評価に成功すればTCL_OK、失敗すればTCL_ERRORを返します。ここからがちょっと特殊なのですが、プリシージャーやループ等特定の文脈で使用した場合には、TCL_RETURN、TCL_BREAK、TCL_CONTINUEを返すことがあります。Tkを使うことが主目的なので、これらを考慮する必要はほぼないと思います。

スクリプトの評価については、これで一通りの説明は終わりました。次回はルートとなるインタープリターの扱いについて考えてみたいと思います。