TL; DR
- text/templateのREPLを自作
- テンプレートの評価にはこれまでの入力履歴との結合が必要
- 表示されない文字
\034
で履歴と結合、評価後にこの文字以降だけを取り出し出力
はじめに
Go のtext/template パッケージで使える構文は非常に強力で、単体でプログラミング言語として使用できるほどです。
そこで、Go Templateの 沼に引きずり込む 魅力を伝えるために、インタープリターを作成しました。
本来テンプレートエンジンであるこの言語でREPLを作るにはトリックが必要だったため、本記事ではこの方法について紹介します。
REPLを作る上での課題
REPLは入力した行を評価し、対話的にその結果を出力します。
>>> "Hello, " + "world!"
'Hello, world!'
>>> a = "again!"
>>> "Hello, " + a
'Hello, again!'
動作としては以下の流れです。
- 評価するのは直近の1行だけ
- 変数はこれまでの履歴をすべて引き継ぐ
翻ってGo TemplateのREPLを考えてみましょう。Templateは当然REPLのためには作られていないため「定義された変数を保持した状態で次の入力を評価する」機能はありません。
これまでに定義された変数を参照するには再度すべての入力を評価する必要があります。
tmpl:1> {{$a := "again"}}
# $aを知るには1行目をもう一度評価する必要がある
# ※templateでは、`{{ }}` に囲まれていない地の文はそのまま表示される
tmpl:2> Hello, {{$a}}
Hello, again
一方、これまでの行を単に再度評価した場合、前の行までの出力が重複してしまいます。
tmpl:1> Hello, world!
Hello, world!
tmpl:2> {{$a := "again"}}
Hello, world!
tmpl:3> Hello, {{$a}}
Hello, world!
Hello, again
これは鬱陶しいですね...
解決策
- 評価するのはこれまでの全入力
- 出力するのは直近の1行を評価した結果だけ
両者をみたすために、直近の入力とこれまでの入力履歴を分離するための区切り文字 \034
を入れることにしました。
入力
{これまでの入力のソースコード} \034 {直近の行のソースコード}
↓ 評価
{これまでの入力を評価した結果} \034 {直近の行を評価した結果}
↓ \034 の後だけを抽出
{直近の行を評価した結果}
これまでの入力: Hello, world!\n{{$a := "again"}}\n
直近の1行: Hello, {{$a}}\n
Hello, world!\n{{$a := "again"}}\n\034Hello, {{$a}}\n
↓ 評価
Hello, world!\n\n\034Hello, again\n
↓ \034 の後だけを抽出
Hello, again\n
区切り文字 \034
は表示されず、ユーザーが使うこともほぼ無いので入力を壊してしまう心配はほとんどありません。
(\034
はGAWKの添え字の区切り文字を参考に選びました )
また、Go Templateは {{ }}
の外側をそのまま出力するため、\034
そのものは評価の前後で変化しません 1。マーカーとしても優秀です。
おわりに
以上、Go TemplateのREPLで出力を制御するためのトリックでした。\034
は手軽に扱えるので、自作言語、CLIで区切り文字が欲しい場合に使ってみてはいかがでしょうか?
-
仮に変化するとすれば
{{ }}
の内側にあることになりますが、その場合行内で{{
と}}
が対応していないためシンタックスエラーになります。 ↩