読者です 読者をやめる 読者になる 読者になる

ELVMにTeXバックエンドを足した話

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にどのように翻訳されるか,その詳細を見ていこう.

入出力(PUTCGETC

TeXは他のプログラミング言語と異なり,標準入出力を扱うのに苦労するという話はよく知られているであろう.入力に関してはTeXの目*6を気にしないとならない.バイナリ列として入力を扱いたくてもそう簡単にはいかない.出力に関しても同様で,ASCIIコードの印字可能でない範囲を出力することが原理的に困難である.さらに,バージョン情報などの自動出力も抑制できない.

TeXバックエンドを作るにあたり,入出力に関してはTeXの外の世界に頼るしかないと割り切った.そこで,ここでは次のような変換を行うことにした.

  1. EIRに与えられる入力を外部の力を借りて,そのASCIIコード値の改行区切り列に変換する
  2. 1で得られた結果を,TeXの標準入力に与える
  3. TeXは,別ファイルにEIRの実行結果としての標準出力をASCIIコード値の改行区切り列で出力する
  4. 3で得られたファイルを外部の力を借りて通常の文字列に戻す

図示すると,次のような形になる.赤色はASCIIコード値の改行区切り列,グレーは捨てるファイルや出力を指す.また,foo.texTeXバックエンドより生成されたEIRのコンパイル結果である.

f:id:hak7a3:20161202195906p:plain

ここで,「入力をASCIIコード値の改行区切り列に変える」とは,

hoge
fuga

という入力を

104
111
103
101
10
102
117
103
97

に変換することを指す.

この変換を挟む関係上,TeXバックエンドの出力結果は入出力をインタラクティブに扱えないという制限を持つ.これを改善するためには,TeXで標準入出力を普通のプログラミング言語のように取り扱えるようにする技法が求められる*7

算術演算(ADDSUB

前回の記事で言及した通り,ELVMでは整数が24ビットである.そのため,多くの言語ではマスクしてやる必要がある.しかし,TeXはビット演算をプリミティブに持っていない.今回の加算の実装では,nビット整数とnビット整数の和はたかだかn+1ビット整数であるという性質を用いて,和が24ビットの範囲を超えているようなら,そこから224を引くという実装にしている.減算についても同様である.

EXIT

プログラムを終了するとき,メインループ\@loop@mainの末尾再帰を停止してやる必要がある.そこで,今回の実装では,EXIT\@@next\relax\letするように翻訳した.これにより,\@loop@mainの末尾が\relaxと等価になり,末尾再帰が終了する.

その他

特筆すべきものはないだろう.

まとめ

やはり,TeX言語はそんなに難しくない.

参考

ELVMには現在,興味深い言語のバックエンドがどんどん追加され,その実装解説が出ている.ここにリストで挙げておこう.

*1:当然,\csnameで生成するのでひとつのコントロールシーケンスである

*2:正確には,限界はもっと早い.ELVMのバックエンドのテストにelcのコンパイルがあるのだが,そのテストが通らない.

*3:なお,マクロを未定義の状態には戻せないので,メモリを大量に使用するプログラムを実行しようとすると死ぬ.

*4:むしろ,このままでは申し訳ないので,近いうちにやります.

*5:実は,この部分もやや冗長になっている.レジスタを単純にマクロに割り当てたことが主な原因で,レジスタTeXのカウントレジスタを割り当てるといいのかもしれない.

*6:入力列をトークンに変換する部分

*7:教えてください