ELVMにTeXバックエンドを足した話
この記事はTeX & LaTeX Advent Calendar 2016の3日目の記事です. 2日目はdoraTeXさんでした.4日目はtex-ut-texさんです.
はじめに
2017年に学ぶべき言語12位はLaTeXである.すなわち,TeX言語をホンキで語ることが求められている(Advent Calendarテーマ回収).
前回の記事では,ELVMバックエンド作成法を紹介した.本記事では,それをどうTeXに適用したか,すなわちELVMのTeXバックエンドで用いているTeXプログラミング技法を紹介する.これが理解できると,TeXで言語処理系を作るのがあまり困難ではないということがわかるはずだ.
TeXバックエンドで用いたプログラミング技法
メモリおよびレジスタの表現
メモリは\@mem@42
など,\@mem@
+ 番地で表現される*1.初期値に0以外の値が与えられるか,EIR実行中に書き込みが行われた場合に定義される.事前に全番地分定義しておけばよいと思うかもしれないが,24ビット空間すべてを定義してしまうとTeXで定義できるマクロの最大数に到達してしまい,TeX処理系が停止してしまう*2.この事象の回避のため,メモリを表すマクロの定義タイミングを必要最低限にしている*3.これをどうにかするのが今後の課題だろう*4.
レジスタは\@reg@pc
など,\@reg
+ レジスタ名で表現される.
命令列の表現および実行部
前回の記事でも述べたように,EIRの命令列のうち,同じpc
の値を持つものは,同じグループに属するので処理をまとめる必要がある.TeXでは,これを\@inst@23
のように\@inst@
+pc
の値というマクロを生成することとした.このマクロは引数がなく,展開・実行するとグループ内の命令が実行されるようになっている.
EIRの実行を司る部分は次のようになっている.
\def\@loop@main{% \let\@@next\@loop@main \csname @inst@\@reg@pc\endcsname \count0=\@reg@pc\relax \advance\count0by1\relax \edef\@reg@pc{\the\count0}% \@@next}\@loop@main
\@loop@main
がループの本体で,\@@next
に自身を\let
し,それを展開することによってループを末尾再帰で実現している(なぜ回りくどい方法を取ったかは後述).ループのメインは3行目の\csname @inst@\@reg@pc\endcsname
で,この部分で\@inst@
+pc
の値を作り出し,それを展開・実行している.その展開・実行の後,pc
の値をインクリメントしている*5.
各命令の表現法
ここまでで,大まかなEIRからTeXへの変換法がわかった.次に,EIRの各命令がTeXにどのように翻訳されるか,その詳細を見ていこう.
入出力(PUTC
,GETC
)
TeXは他のプログラミング言語と異なり,標準入出力を扱うのに苦労するという話はよく知られているであろう.入力に関してはTeXの目*6を気にしないとならない.バイナリ列として入力を扱いたくてもそう簡単にはいかない.出力に関しても同様で,ASCIIコードの印字可能でない範囲を出力することが原理的に困難である.さらに,バージョン情報などの自動出力も抑制できない.
TeXバックエンドを作るにあたり,入出力に関してはTeXの外の世界に頼るしかないと割り切った.そこで,ここでは次のような変換を行うことにした.
- EIRに与えられる入力を外部の力を借りて,そのASCIIコード値の改行区切り列に変換する
- 1で得られた結果を,TeXの標準入力に与える
- TeXは,別ファイルにEIRの実行結果としての標準出力をASCIIコード値の改行区切り列で出力する
- 3で得られたファイルを外部の力を借りて通常の文字列に戻す
図示すると,次のような形になる.赤色はASCIIコード値の改行区切り列,グレーは捨てるファイルや出力を指す.また,foo.tex
はTeXバックエンドより生成されたEIRのコンパイル結果である.
ここで,「入力をASCIIコード値の改行区切り列に変える」とは,
hoge fuga
という入力を
104 111 103 101 10 102 117 103 97
に変換することを指す.
この変換を挟む関係上,TeXバックエンドの出力結果は入出力をインタラクティブに扱えないという制限を持つ.これを改善するためには,TeXで標準入出力を普通のプログラミング言語のように取り扱えるようにする技法が求められる*7.
算術演算(ADD
,SUB
)
前回の記事で言及した通り,ELVMでは整数が24ビットである.そのため,多くの言語ではマスクしてやる必要がある.しかし,TeXはビット演算をプリミティブに持っていない.今回の加算の実装では,nビット整数とnビット整数の和はたかだかn+1ビット整数であるという性質を用いて,和が24ビットの範囲を超えているようなら,そこから224を引くという実装にしている.減算についても同様である.
EXIT
プログラムを終了するとき,メインループ\@loop@main
の末尾再帰を停止してやる必要がある.そこで,今回の実装では,EXIT
を\@@next
に\relax
を\let
するように翻訳した.これにより,\@loop@main
の末尾が\relax
と等価になり,末尾再帰が終了する.
その他
特筆すべきものはないだろう.
まとめ
やはり,TeX言語はそんなに難しくない.
参考
ELVMには現在,興味深い言語のバックエンドがどんどん追加され,その実装解説が出ている.ここにリストで挙げておこう.
- UnlambdaでVMを実装する(前編)
- UnlambdaでVMを実装する(後編)
- Unlambdaでの実装解説.一言でいうと凄まじい.ラムダ計算やコンビネータ論理を学ぶ予定があるなら目を通しておくべき.
- ELVM で C コンパイラをポーティングしてみよう(Vim script 編)
- Vim scriptでの実装解説.バックエンドの追加法に関してもよく解説されているので,これからバックエンドを足そうと考えている人は必読.
- コンパイル中にコンパイルする「コンパイル時Cコンパイラ」をつくった話