Diary?

2009-06-25
Thu

(19:14)

さて俺はここしばらく厳密分離指向テンプレートエンジンの作業をしているのだが、現状では Smarty 風と Genshi 風のテンプレートが一応動く(エラーチェックなどはまだ大甘。プロトタイプ的なステータスだし)という状態には漕ぎ着けている。

このテンプレートエンジンは実行モデルとしてはバイトコードコンパイラ/インタープリタになっており、テンプレート処理をするのに必要そうな最低限の命令で動作する VM を用意し、複数のテンプレート言語をその VM のターゲットであるバイトコードにコンパイルというやり方になっている。まだ仕様を凍結していないのであんまり詳細なことは書きたくないが(既に VM を一回書き直してるし)、今日はちょっと気になることを一つだけ書く。

今の実装では、次のような分岐があったとすると

{if $cond}
    <p>foo</p>
{else}
    <p>bar</p>
{/if}

以下のようなバイトコードにコンパイルされる。まだ VM を練り上げてないので仕様も実装も泥縄っぽいが、ニュアンスは伝わるだろう。

LOAD cond          # コンテキストから cond を読み出し、スタックに push
JMPIF label_else   # スタックから pop した値が偽だったら label_else にジャンプ
WRITE "<p>foo</p>" # 出力ストリームに書き出す
JMP lable_end      # lable_end にジャンプ
LABEL label_else   # ラベルをセット
WRITE "<p>bar</p>"
LABEL lable_end

StringTemplate は基本的なスタンスとしてテンプレート内での演算を認めていないので、条件式は単なる変数参照になる。これでも問題ないと言えばそうだが、じゃあ仮に定数との比較程度なら許すとしたらどうなるだろう。まず考えられるのは次のような文法だろうな。

{if $cond == "xyz"}
    <p>foo</p>
{else}
    <p>bar</p>
{/if}

こいつをコンパイルした結果は恐らく次のようになる。

LOAD cond          # コンテキストから cond を読み出し、スタックに push
PUSH "xyz"         # スタックに値を push
EQ                 # スタックから値を二つ pop し、両者が等しいかテストした結果を push
JMPIF label_else   # スタックから pop した値が偽だったら label_else にジャンプ
(略)

実はこれはバイトコードレベルでは対応しているんだが、これに対応するとなると構文解析部分に手を入れないといけなくなる。今のところ if 文も modifier/filter を含んだ変数参照もまったく同一の式言語を用いているのだが、比較などの演算子をサポートすると、 if 文の定義を変えざるを得なくなる。つまり if 文では単純な変数参照と比較演算のみに限定する。これもありといえばそうだ。

そこに手を入れずにやるとなると、 modifier/filter を用いた構文になる。

{if $cond|eq:"xyz"}
    <p>foo</p>
{else}
    <p>bar</p>
{/if}

Django のテンプレートや Smarty を知ってるなら、 modifier/filter はわかるだろう。まあぶっちゃけ関数呼び出しのようなものだ。それでここでは eq という modifier/filter を用意し、その戻り値で分岐を行うようにしている。バイトコードでは次のようになる。

LOAD cond          # コンテキストから cond を読み出し、スタックに push
PUSH "xyz"         # スタックに値を push
CALL eq 2          # スタックから二つの値を pop し、それをもって eq を呼び出す。戻り値は再度スタックへ
JMPIF label_else   # スタックから pop した値が偽だったら label_else にジャンプ
(略)

「modifier/filter の結果を表示以外に使う」のを気持ち悪いと思わなければ、こっちの方が VM の命令が少なくて済む。

あるいはその折衷案として、いくつかの modifier/filter の構文糖衣を用意するのも考えられる。つまり下記のコードから

{if $cond == "xyz"}
    <p>foo</p>
{else}
    <p>bar</p>
{/if}

下記のバイトコードを生成する。このとき eq は処理系が組み込みで提供するべき標準の modifier/filter として仕様に明記する。

LOAD cond
PUSH "xyz"
CALL eq 2          # == 演算子を eq の呼び出しに変換
JMPIF label_else
(略)

多分これが一番 VM の命令セットの要素が減り(=実装が単純になり)、なおかつ受け入れられやすい文法になるような気がする。ただしバイトコードの生成は(まあ別に難しくもなんともねえけど)三つの中で一番タルくなるし、何だかんだいってやってることは modifier/filter なので、システムの中の人的にはアレな事にかわりはない。

というわけでいろいろ思案中。

Creative Commons
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。