TeX / e-TeX

TeX Live 2024 の実行ファイルを覗くと、奇妙なことに気づきます。latexpdflatexetex も、それ自体はプログラムではなく pdftex へのシンボリックリンクなのです。クヌースが書いた TeX 本体——コマンド名 tex——だけが、いまも独立した一本のプログラムとして残っています。この違いが分かると、LaTeX まわりの混乱の大半が解けます。エンジン(プログラム)とフォーマット(先読みされたマクロ群)は別物 だからです。このページでは、その一番下にある凍結されたプログラムと、1990 年代にその上へ足された e-TeX 拡張——いまや誰もが気づかずに使っている土台——を見ていきます。

tex というコマンドが実際に動かしているもの

tex が動かしているのは、文字と箱を行と段落に詰め、改行位置を最適化し、結果をファイルに書き出す だけのプログラムです。それ以上のことはしません。混乱のもとは、TeX という語がこのプログラムと、プログラムが解釈する命令体系の両方を指してしまうところにあります。章立ても、相互参照も、参考文献も、このプログラム自身は一切知りません。

クヌースが最初の TeX を書き始めたのは 1978 年、自著『The Art of Computer Programming』第 2 巻の校正刷りの数式があまりに汚かったからでした。その初版は手探りの試作で、1982 年に全面的に書き直されます。この書き直しが TeX82 で、今日「TeX」と言えば事実上この系列を指します。クヌースがプログラムに直接組み込んだ最小の命令セットを プリミティブ(基本命令) と呼び、\def(命令の定義)、\hbox(水平の箱)、\vbox(垂直の箱)などがそれにあたります。

プリミティブだけで文書を書くのは現実的ではありません。そこで実際には、その上にマクロを積み上げた フォーマット を使います。plain.tex を読み込んだものが plain TeX、はるかに大きなマクロ群を読み込んだものが LaTeX です。フォーマットは毎回読み直すのではなく、あらかじめ展開して .fmt ファイルに保存しておき、起動時に一瞬で復元します。この仕組みの詳細——.fmt の中身、fmtutil による作り直し、LaTeX2e と LaTeX3 の関係——は「LaTeX のフォーマット」のページが受け持ちます。

texinitexetex のうち、本物のプログラムはどれか

tex だけです。 initextex へのシンボリックリンクで、etex にいたっては pdftex へのリンクです。つまり e-TeX という独立したプログラムは、現在の配布物にはもう存在しません。これは意見ではなく、手元で readlink を叩けば数秒で確かめられる事実です。TeX Live 2024 の tex --versionTeX 3.141592653 (TeX Live 2024) と答えます。

terminal
$ tex --version | head -1
TeX 3.141592653 (TeX Live 2024)

# Which of these are real binaries, and which are links?
$ cd $(dirname $(which tex))
$ readlink tex initex etex latex pdflatex amstex csplain
           # tex prints nothing: it is a real executable
tex        # initex   -> tex
pdftex     # etex     -> pdftex
pdftex     # latex    -> pdftex
pdftex     # pdflatex -> pdftex
pdftex     # amstex   -> pdftex
pdftex     # csplain  -> pdftex

なぜこうなっているのか。e-TeX の拡張が、他のエンジンに丸ごと取り込まれてしまったから です。TeX Live 同梱の doc/etex/base/README(Karl Berry, 2024)は、この状況を身も蓋もなく書いています——ほとんどの配布物が提供する etex 実行ファイルは「DVI モードで動く pdfTeX の化身」であり、そうすることで e-TeX を超える拡張も一緒に使えるようにしている、と。同じ理屈で latex は「古い TeX」ではなく DVI モードの pdfTeX です。実行すればバナーが This is pdfTeX, ... と名乗ります。エンジンとフォーマットが別軸だからこそ、こういう入れ替えが成り立ちます。

plain TeX — \documentclass\begin{document} も無い世界

plain TeX は、クヌースが『The TeXbook』で示した標準フォーマットです。フォントの設定、基本的な数式記号、\bye のような便利マクロといった最低限の道具立てを plain.tex にまとめたもので、TeX 本体に付属します。LaTeX が登場する前は、これが TeX を使う唯一の現実的な方法でした。TeX Live 2024 では tex エンジンが tex.ini からこのフォーマットを吐き出し、tex.fmt として保存しています。

記法は LaTeX とかなり違います。インライン数式が $...$ なのは同じですが、水平な箱は \hbox{...}、垂直な箱は \vbox{...}、表組みは \halign で揃え、命令の定義は \def、本文の幅は \hsize、そして文書の終わりは \bye です。\documentclass\begin{document} もありません——それらはすべて LaTeX がマクロで作ったものだからです。逆に言えば、LaTeX の便利さは「エンジンの機能」ではなく「フォーマットの機能」だ、ということです。

latex
% plain TeX — process with: tex hello.tex  (produces hello.dvi)
\hsize=10cm
\font\big=cmr10 at 17pt
{\big Hello, plain \TeX!}
\medskip
This paragraph is set in the default font.
Inline math works too: $E = mc^2$.
\bye

これを tex hello.tex で処理すると DVI ファイルができます(PDF ではありません——次節で触れます)。\TeX のようなロゴ用マクロや \medskip(中くらいの縦アキ)も plain TeX が定義しているものです。LaTeX の \documentclass 文書と並べてみると、plain TeX が素のエンジンにいかに薄い皮しか被せていないかがよく分かります。

文芸的プログラミングと WEB — TeX 自身のソースの書かれ方

TeX 自体は、クヌースが提唱した 文芸的プログラミング(literate programming) の実践として、WEB という独自システムで書かれています。WEB のソースは、人間向けの解説と Pascal のコードが交互に織り込まれた一本の文書です。そこから二つのツールが別々の派生物を取り出します——tangle がコンパイル可能な Pascal を、weave が組版された解説書(書籍『TeX: The Program』そのもの)を生成します。

当初の対象言語は Pascal でしたが、現代の配布物では web2c というツールが WEB(経由の Pascal)を C 言語 に変換してビルドします。つまり、いま手元で動いている pdfTeX も XeTeX も LuaTeX も、たどっていけばこの一本の文芸的ソースに行き着くわけです。TeX Live のディレクトリ名に web2c が繰り返し現れるのは、そのためです。

π に収束するバージョン番号と、凍結されたプログラム

TeX のバージョン番号は独特です。バージョン 3 以降、更新のたびに末尾へ桁を一つ足していき、番号が 円周率 π に漸近 していく仕組みになっています。TeX Live 2024 同梱版で tex --version を実行すると TeX 3.141592653 と答えます。伴走する METAFONT のバージョンが ネイピア数 e に漸近することも合わせて覚えておくと、この命名の遊び心が見えてきます。

そしてクヌースは、自分の死後に行われる「絶対的に最後の変更」でバージョンをちょうど π にする と宣言しています。そのとき、残っているバグはすべて永久に「仕様」になる、というのが彼の言葉です(Donald E. Knuth, "The future of TeX and METAFONT", NTG 誌 MAPS, 1990; 『Digital Typography』第 30 章に再録)。この 凍結 は諦めではなく設計判断でした。土台が動かないからこそ、その上に載るフォーマットやドライバは安心して進化できます。何十年も前の .tex ファイルが今日も同じ出力を生むのは、この判断の直接の結果です。

METAFONT と DVI — TeX が直接は PDF を出さない理由

TeX には双子の伴侶 METAFONT(コマンド名 mf)があります。フォントを「形」ではなく「描き方のプログラム」として記述するシステムで、クヌースは TeX 用の Computer Modern 書体一式をこれで設計しました。役割分担は明快です——TeX が文字をどこに置くかを決め、METAFONT が文字の形そのものを生み出します。

そしてクヌースの TeX が直接吐き出すのは PDF ではなく DVI(DeVice Independent) ファイルです。DVI には「この位置にこの文字を置く」という、特定の出力機器に依存しない指示だけが入っています。そこから dvips で PostScript へ、dvipdfmx で PDF へ変換します。1982 年に PDF は存在しなかったのですから当然の設計で、逆に言えば、出力形式が変わっても TeX 本体を書き換えずに済んだ、ということでもあります。今日広く使われる pdfTeX・XeTeX・LuaTeX は、この一段を内側に取り込んで直接 PDF を生成できる点が大きな違いです。

e-TeX とは何か——凍結されたプログラムを壊さずに拡張する

e-TeX は、凍結された TeX に新しいプリミティブを足した 厳密な上位互換 の拡張です。クヌースが TeX を凍結した以上、新機能は別の手から出てくるしかありませんでした。そこで DANTE e.V.(ドイツ語圏 TeX ユーザ会)の後援のもと 1992 年に発足したのが NTS(New Typesetting System)プロジェクト で、その最初の具体的な成果が e-TeX です。実装したのは Peter Breitenlohner(ミュンヘンのマックス・プランク物理学研究所、2015 年没)で、プロジェクトの調整役は Philip Taylor が務めました。TeX Live 同梱の etex.1 マニュアルは、この取り組みの目的を「TeX の精神と哲学を受け継ぎ発展させること、ただし TeX は凍結されたままであれというクヌースの意思を尊重しつつ」と述べています。

「厳密な上位互換」というのは、既存の入力を一字も変えずに走らせて出力が同一になる、という意味です。それを保証するために e-TeX には二つのモードがあります。互換モード(compatibility mode) では素の TeX と完全に同じ振る舞いをし、拡張モード(extended mode) に入ってはじめて追加のプリミティブが使えるようになります。どちらで動くかを決めるのは実行時ではなく フォーマットを作るとき です。フォーマットの元ファイル名に * を前置してダンプすると拡張モードになる——この一文字の約束が、次節で見るとおり現代の LaTeX 全体を支えています。

なぜ気づかないうちに e-TeX を使っているのか

LaTeX のフォーマットが拡張モードでダンプされているからです。 これは推測ではなく設定ファイルに書いてあります。TeX Live 2024 の fmtutil.cnf を開くと latex pdftex language.dat -translate-file=cp227.tcx *latex.ini という行があり、末尾の *latex.ini* が拡張モードの指定です。同じファイルの先頭のコメントが、この *-etex オプションと本質的に等価だと明言しています。対照的に plain TeX の行は tex tex - tex.ini で、エンジンが tex* もありません。素の TeX だけが e-TeX 拡張を持たない、という状態です。

LaTeX 側からも同じことが確認できます。カーネル本体 latex.ltx の冒頭近くには \ifx\eTeXversion\undefined \errmessage{LaTeX requires e-TeX} という守衛が置かれていて、拡張が無ければフォーマットの生成そのものが止まります。この方針が正式になった時期もはっきりしています——LaTeX News 16(2003 年 12 月)で「今後 2 年ほどのうちに e-TeX 前提へ移る見込み」と予告され、LaTeX News 26 が「2017 年 1 月リリース以降、フォーマットの生成に e-TeX が必須」と宣言しました。latex を実行するとログの 3 行目に entering extended mode と出るのは、この宣言の目に見える痕跡です。

terminal
# Ask each engine whether the e-TeX primitives exist.
$ cat > probe.tex <<'EOF'
\message{[eTeX=\ifx\eTeXversion\undefined none\else\number\eTeXversion\eTeXrevision\fi]}
\count300=7  % register 300 does not exist in Knuth's TeX
\end
EOF

$ tex probe.tex          # Knuth's TeX: no extensions
[eTeX=none]
! Bad register code (300).

$ pdftex probe.tex       # pdfTeX: e-TeX 2.6 built in, 32768 registers
[eTeX=2.6]

$ latex probe.tex | head -3
This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024) (preloaded format=latex)
 restricted \write18 enabled.
entering extended mode

最後のバナーは、このページの主題をそのまま一行にしたものです。3.141592653-2.6-1.40.26 という版番号は三層構造になっていて、クヌースの TeX が 3.141592653、e-TeX が 2.6、pdfTeX が 1.40.26。そして (preloaded format=latex) がフォーマットの名前です。つまり一行のなかに、エンジンの系譜とフォーマットの選択が同時に書いてあります。ログの先頭行を読む習慣をつけると、「同じコマンドなのに結果が違う」という共同作業のトラブルが一瞬で切り分けられます。

e-TeX が足したプリミティブ

e-TeX の追加は、マクロを書く人にこそ効きます。最大のものは 整数・寸法・グルーの算術 です。素の TeX では計算のたびに作業用レジスタをやりくりする必要がありましたが、e-TeX は \numexpr\dimexpr\glueexpr を用意しました。(a+b)*c/d のような式をその場で、しかも 展開可能な形 で評価できます。展開可能であることが重要で、\edef\write の中でもそのまま値になります。

latex
% On any e-TeX engine in extended mode (pdftex, xetex, luatex, euptex).
\count0=\numexpr (3+4)*2/7 \relax   % yields 2

% Branch safely on whether a name is defined.
\ifdefined\foo \message{foo exists}\else \message{no foo}\fi

% Test a control sequence without creating it.
\ifcsname chapter\endcsname \message{chapter is defined}\fi

% Negate a conditional directly, instead of swapping the branches.
\unless\ifnum\count0>10 \message{count0 is not greater than 10}\fi

第二の柱は 条件判定とトークン操作 です。\ifdefined は制御綴が定義済みかを調べ、\ifcsname...\endcsname は名前から組み立てた制御綴の存在を調べます。どちらも 副作用がない ことが肝心で、素の TeX で使われていた \ifx を用いる古い手口は、未定義の制御綴を黙って \relax に変えてしまうことがありました。\unless は任意の条件を反転させ、分岐を入れ替える定型作業を消します。\detokenize はトークン列を文字列(カテゴリコード 12 の文字)に変えます。

プリミティブ何をするか
\numexpr整数式をその場で評価する(展開可能)。\numexpr (3+4)*2/7 \relax
\dimexpr / \glueexpr同じことを寸法とグルーに対して行う。\dimexpr \textwidth/3 \relax
\ifdefined制御綴が定義済みかを、副作用なしに調べる
\ifcsname名前から組み立てた制御綴の存在を、それを作らずに調べる
\unless直後の条件の真偽を反転させる。\unless\ifnum ...
\protected\edef\write の中で勝手に展開されないマクロを定義する
\detokenize / \unexpandedトークン列を文字列に変える/トークン列を展開せずそのまま残す
\scantokens / \readline文字列を入力として読み直す/入力行をそのまま(逐語的に)読む
\middle\left … \right の途中に、伸縮する区切り記号を置く
\currentgrouplevelいま何重のグループの中にいるかを返す(\interactionmode は対話モードの読み書き)

展開の制御も強化されました。\unexpanded は中身を展開せずそのまま置き、\protected は「展開文脈で勝手に展開しないマクロ」を定義します。後者は LaTeX の \protect を低水準で正しく実装するための鍵で、\edef\write にもろい命令を書いても壊れなくなります。数式では \middle が効きます——\left( … \middle| … \right) と書けば、真ん中の縦棒も左右の括弧と同じだけ背が伸びます。素の TeX には、この「途中の区切り記号」という概念そのものがありませんでした。

地味ながら決定的なのが レジスタの大増設 です。素の TeX には \count\dimen\skip\toks などが各 256 個しかありませんでした。上の実験で tex! Bad register code (300). と拒んだのがそれです。e-TeX はこれを 32768 個 に増やし、しかも疎な配列として確保するので使わない分のメモリを食いません。大きなクラスとパッケージを何十も読み込む今日の LaTeX 文書が、レジスタ不足で止まらないのはこの拡張のおかげです。

このほか e-TeX は 双方向組版(右から左に書く文字体系)のためのしくみ も持ち込みました。Breitenlohner の TeX--XeT に由来するこの部分は、のちの XeTeX や LuaTeX、そして日本語処理の仕事へとつながっていきます。そして現代的なパッケージの多くは、これらのプリミティブ抜きには成立しません。LaTeX のプログラミング層である expl3 がその代表で、\ifcsname\protected が使えなければ、あの巨大なマクロ体系はそもそも書けなかったはずです。

この層を知っていると何が読めるようになるか

エラーメッセージの底が見えるようになります。 LaTeX が出す警告の多くは、実はこの下の層の言葉で書かれています。Overfull \hbox (12.3pt too wide) は「行という水平の箱から中身が 12.3pt はみ出した」という意味で、\hbox が箱だと知っていれば、原因が長い URL か改行できない語かに絞り込めます。! Missing number, treated as zero. は「数値または寸法として読めるものを期待したのに無かった」——たいてい長さ指定の綴り間違いです。

ただし、この層を 読む ことと、この層で 書く ことは別の話です。見出し・箇条書き・相互参照・定理環境は LaTeX の構造化命令に任せ、低水準の命令は「なぜ壊れたかを読む」ときと「クラスを狭くつつく」ときに取っておきます。新しい命令を定義するときですら、通常の文書では生の \def より \newcommand\NewDocumentCommand のほうが安全です。引数の個数を検査し、既存の命令との衝突を教えてくれるからです。

  • 原稿を書いているとき — LaTeX の構造化命令で書き、低水準のプリミティブは極力使わない。
  • クラスやパッケージを直しているとき\ifdefined\numexpr といった e-TeX の道具は、古い TeX の小細工より安全で読みやすい。
  • ログを読んでいるとき — 警告を箱・グルー・レジスタという TeX の語彙に翻訳すると、原因が見えてくる。
  • 再現性が要るとき — エンジンは凍結されているので、過去の文書は将来も同じ結果を生む。ただし実際に動くのは フォーマットとパッケージのバージョン のほうなので、固定すべきはそちら。