🍎

TeXで使うプログラミング言語まとめ

2021/12/24に公開

TeX & LaTeX Advent Calendar 2021の24日めの記事です。昨日は @wtsnjp による「jlreq + expl3 で学会文書クラスを作った話」でした。他人が使う文書クラス作るのまじすごい)

TeXを使っている人たちはよく「コマンド」とか「マクロ」とか「プログラム」について話しています。そういう会話を耳にして「TeXではプログラミングができる」ことを知り、興味を持ったものの、具体的にどこから始めればいいかわからない人も多いでしょう(ほんとに多いのか?)。

ここでちょっとした落とし穴があります。一口に「TeXでプログラミング」といっても、目的や手段や用途に応じて文法や機能や表現能力がまったく異なるいくつかのプログラミング言語があり、それらを使い分ける必要があるからです。つまり「プログラミング言語」という観点から言うと、TeXの本来の目的である「文書の組版」でプログラミングを活用するためには、複数の言語とそれらの主な利用場面について一通り把握している必要があります。どれか1つのプログラミング言語について文法を学べば十分という状況ではありません。

世の中で「TeX」と総称されることが多い枠組みにどんなプログラミング言語があって、どれを使うと何が制御できるのかをものすごく雑に整理すると、次の図のようになります。色がついてる箱がプログラミング言語です。

TeXの何を何でプログラミングできるか

この画像はイメージで、次のような状況を察してもらうことを期待して描いたものだから、それ以上の深い意味はありません。

  • LaTeXの挙動を変えるのに必要なのはTeXマクロexpl3
  • LuaTeXとかPythonTeXってのもあるけど、これらはLaTeXの挙動をLuaやPythonでどうにかするものじゃない
  • LaTeXの定義に使われているTeXマクロをいじっても、TeXの組版機能は変えられない
  • LuaTeXの組版機能は、Luaで触れる
  • BibTeXの挙動を変えるにはTeXマクロとは別にBeaSTとかいう謎のプログラミング言語が必要

こうした状況についての理解を助けるために、本稿ではTeXマクロexpl3、(LuaTeXにおける)LuaBeaSTについて簡単に紹介します。

TeXマクロ

「TeXでプログラミング」と聞いて多くの人が想像するのはこれでしょう。KnuthによりオリジナルのTeXに組み込まれた、トークンの置き換えによってドキュメントを書くときに役立ついろいろな機能をユーザーが拡張できるようにする仕組みです。

ここでトークンというのは、ソース(原稿)からTeXによって読み込まれたデータのことだと思ってください。TeXの内部では、著者が原稿に書いたデータを内部でトークンの列にし、それを組版処理に使います。このトークンの列をユーザーから制御可能にして、一部を別のトークンで置き換えたり置き換えなかったりできるようにしようというのが、TeXにおけるプログラミングの基本的なノリです。

ただしTeXマクロは、単純なトークンの置き換えだけでなく、トークンの列を実際に組版したり、ページとして出力したり、さらにはそもそも入力をトークンとして読み込む際の処理の定義に使ったりもします。実際、TeXでは文書を書くときのテンプレート的なものをTeXマクロで定義して使うことが一般的で、広く使われているLaTeXというやつも、このTeXマクロを駆使して実装されています。もはや「マクロ」という用語で想起される「項の置き換え」を越えているので、そういうのを「TeX言語によるプログラミング」と呼んだりすることもあります。

LaTeXでもTeXマクロが駆使されているということは、そのLaTeXの挙動を変えたいと思ったら、場合によっては自分でTeXマクロを書く必要が生じます。これを「TeX on LaTeX」などと表現する人もいます。とはいえ、実際にLaTeXの挙動を変えたいと思った場合には、あらかじめLaTeXに用意されている標準的な仕組みを使うか、すでに誰かがTeXマクロを書いて整備したパッケージを使うか、どちらかの方法によるべきでしょう。そういう方法は『LaTeXコンパニオン』のような本に書かれています(つまり『LaTeXコンパニオン』をはじめとするLaTeXの参考書では「TeXマクロの書き方」みたいなのは書かれていないものと考えてください)。

TeX言語によるプログラミングの例を紹介しましょう。以下は、「LaTeX文書中で行頭にアスタリスク+空白がある行を箇条書きとして組版する」という挙動を実現する方法の一例です。入力をトークンとして読み込む処理に手を加えてアスタリスクを特別に扱うなどしています。

\newif\if@itemize
\begingroup
\catcode`\*=\active
\gdef*{%
  \ifvmode\@itemizetrue\begin{itemize}
    \everypar={\end{itemize}\@itemizefalse}
  \fi
  \itemize@to@eol}%
\endgroup

\def\itemize@to@eol{\begingroup\catcode`\^^M=12 \@@itemize@to@eol}
{\catcode`\^^M=12 \catcode`\^^I=5 %
\gdef\@@itemize@to@eol#1^^M{\review@itemize@head#1 \endgroup}}
\def\review@itemize@head{\if@itemize\item \else*\fi}

なお、このコードをコピペして使うとLaTeXがまともに使えなくなります。そういうことまで考えて実用的なプログラムを書くには、ここからまだもう2ギャップくらいある感じです。「LaTeXの挙動が気に食わない」とか、「自分流の記法をカスタマイズして楽をしたい」とかいった動機でTeX言語に手を出すのは、あまり気軽にやっていいことじゃなさそうですね。

TeXマクロ実行結果

上記はOverleafでの実行結果で、Overleafのエディタだとシンタックスエラーが出ているのがわかると思いますが、実際にはシンタックスエラーはなく、実行時のエラーも起きていません。書いちゃいけないコードだ、というのが伺い知れるでしょう。

exlp3

TeXマクロを自分で書いて実用的なプログラムを書くのは骨が折れます。そこで、この上で比較的ふつうのプログラミング言語の感覚でコードが書けるようにするラッパー言語が開発されました。これはexpl3と呼ばれています。

expl3はTeXマクロで実装されているので、原理的にできることはTeXマクロと同じす。しかし、トークンの展開制御がそこそこ直感的に書けるのと、TeXマクロには存在しなかった便利なデータ構造が用意されていることから、ふつうの人間の脳力で書けるコードの幅がとても広がります。

expl3コードの簡単な例を紹介します。以下は、「文字列の一部を常に置換して出力する」というLaTeXの環境insteadを定義する例です。TeXでは数式モードでしか使えないアンダースコアを多用するので、expl3コードを書くときは\ExplSyntaxOn\ExplSyntaxOffで囲みます。

\ExplSyntaxOn

\tl_new:N \l__body_tl
\tl_new:N \l__outer_body_tl
\cs_new:Nn \l__set_the_body:n {
  \tl_if_empty:NTF \l__body_tl
     {  }
     { \tl_set_eq:NN \l__outer_body_tl \l__body_tl }
  \tl_set:Nx \l__body_tl { #1 }
}

\usepackage{environ}
\makeatletter
\NewDocumentEnvironment{instead}{m m}{%
  \Collect@Body \l__set_the_body:n
}{
  \tl_replace_all:Nnn \l__body_tl { #1 } { #2 }
  \tl_use:N \l__body_tl \par
}
\makeatother

\ExplSyntaxOff

どこが比較的ふつうのプログラミング言語みたいなんだ、と言いたくなる気持ちはわかりますが、実はこのコードでぼく自身は「文字列の置換」処理をまったく書いていません。その処理を実行しているコードは、下から6行めの「\tl_replace_all:Nnn」という関数から始まる1行だけです。これはデフォルトで使える組み込みの関数で、マニュアル(ここinterface3.pdf)から探してきました。そういう標準的な便利関数がまったくないTeXマクロに比べると隔世の感がありますね。

じゃあ、残りのごちゃごちゃは何かというと、すべてそれを「LaTeXの文章で使えるようにする」ための仕掛けになります。

要するに、expl3はあくまでもTeX上で実行する処理を書きやすくするためのプログラミング言語なので、それをLaTeXなどの文書で使いたかったら、そういう本質でないインタフェースのコードが膨らんでしまうし、場合によっては原始的な組版機能についての知識も前提になる、ということです。そういう意味ではTeXマクロと同じ。JavaScriptの文法を知っていてもDOMがわからないとWebのフロントエンドのコードはまったく書けないよね、みたいな話に似ていると思います。

上記のコードの実行例はこんな感じになります。

expl3実行結果

LuaTeXのLua

TeXの動作をプログラムで制御したいけどTeXマクロは人が使っていいものじゃない、と感じている人にとって、LuaTeXという響きは福音に聞こえるかもしれません。確かにLuaTeXにはLuaの処理系が内蔵されていて、Luaでプログラムが書けます。そのLuaでできることは、大きくわけて次の2つです。

  1. Luaで書いたふつうのプログラムをTeXの内部で実行する
  2. Luaを使ってLuaTeXの原始的な組版機能を制御する

1つめは、文字通り、Luaで書いたプログラムの実行結果をTeXの組版処理に渡すという使い方です。TeX側が組版で使う情報をLuaに引数として渡すこともできるので、かなりいろいろなことが実現できます。TeXマクロの制限を受けずに書いたLuaのプログラムを、外部プログラムとして呼び出す必要なく実行できるので、数値計算、画像処理、文字列処理などのパワーをTeXのドキュメントで利用したい状況で助かります。

2つめは、LuaTeXの本来の仕事である組版処理に対し、ユーザーがLuaで書いたプログラムで介入するという使い方です。そのために、組版処理の途中のいくつかの場所にフックを仕掛けられるようになっています。ただし、LuaTeXの内部的なデータ構造に対する操作をLuaで書くことになるので、その内部構造についての知識が必要になります。しかも、この内部的なデータ構造はLuaTeXで独自に定義されたもので、他のTeXとは別物という、トリッキーな仕様です。本来のTeXが組版に使うトークンになるべく依拠して実装されているようには見えるんですが、そのものずばりではないので、LuaTeX以外のTeXとは互換性がありません。ややこしいですね。

このややこしさもあって、LuaTeXによる組版の制御にはTeXマクロによるそれとはまた違った頭の使い方をします。先ほどDOMの例を出しましたが、むしろこっちがDOMのたとえにぴったりかもしれない。LuaTeXにおける組版の制御は、まさにDOMとJavaScriptを駆使してウェブページのレンダリングを制御する感覚に近いものがあります。

もちろん、LuaTeXとはいえTeXですから、組版を制御したいと思ったらTeXの基本的な動作についてもある程度は知識がないと手も足も出ません。DOMとJavaScriptを使うにしてもHTMLやCSSの知識が依然として必要である、という状況と似ているといえるかもしれない。ようするに、LuaTeXではユーザーにできることが大きく広がりましたが、前提になる知識も単純に増えてしまいました。

まとめると、LuaTeXではLuaでプログラムを書いてTeXで書いた文書から使えますが、TeXが組版をするときの挙動に手を加えるのはやはり簡単ではなく、TeXマクロ(あるいはexpl3)を書くときに必要になるのと同じだけの知識に加えて、LuaTeX独自に定義された内部データ構造に対する理解が求められます。

あまり実用的ではない例ですが、LuaTeXでフックを使った組版機能の制御をLuaで書くとこんな感じというコードを紹介しておきます。

\documentclass[twocolumn]{article}
\usepackage{lipsum}
\usepackage{luacode}

\begin{luacode*}
vibrate_line = function (head, group, size)
  i = 1.5
  for list in node.traverse(head) do
    i = i + 0.2
    if list.id == node.id("hlist")
    then
      list.shift = list.shift + (600000 * (math.sin (i)))
    end
  end
  return head
end
\end{luacode*}

\directlua{%
  luatexbase.add_to_callback('vpack_filter', vibrate_line, "strict")%
}

\begin{document}
\lipsum[1-6]
\end{document}

このコードでは、TeXが垂直モードの要素を詰め込み始める直前に呼び出されるフックを利用して、行の開始位置を正弦波に沿ってずらす、という処理を実現しています。実行例はこんな感じです。

Lua実行結果

Luaのおかげで行頭をずらす量を簡単に計算できている一方、「LuaTeXの内部ではnodeという概念があって本文の行はhead以下のhlistに対応している」とかを長いマニュアルを読んで調べないと、どこを触ればいいかわかりません。そもそも「TeXの垂直モード」みたいな概念の理解も必要であるという現実をこの例から感じ取ってください。

BeaST

TeXでドキュメントを書く人は参考文献もよく使うと思います。参考文献にはBibTeXを使いますよね。BibTeXは、TeXのエコシステムで使うことが前提になっていると思いますが、TeXそのものとは直接関係ありません。まったく別の仕組みなので、BibTeXの挙動を制御するためのプログラミング言語も別にあります。これには正式名称はないっぽいのですが、よく参照されるチュートリアルのタイトルが"Tame the BeaST"であることからBeaST言語などと呼ばれることがあります。

BeaSTのプログラムの例を紹介します。次のコードの acronym.word は、複数の英単語からなる文字列から先頭のアルファベットを取り出して頭字語を作る、という関数です。PostScriptを知っていれば、どういうコードなのか読めると思います。なお、#1 とかあるのでTeXマクロっぽく見えるかもしれませんが、これは関数の引数を表しているのではなく、「数字の1」を表します。まじまぎらわしい。

STRINGS { s t str org car result }
INTEGERS { len }

FUNCTION {acronym.word}
{ duplicate$ 'org := 'str :=
  str #1 #1 substring$ 'result :=
    { str empty$ #0 = }
    { str #1 #1 substring$ 'car :=
      car " " =
        { str #2 global.max$ substring$ 'str :=
          result str #1 #1 substring$ * 'result :=
        } { skip$ } if$
      str #2 global.max$ substring$ 'str :=
    }
    while$
    result text.length$ #3 <
      { org #1 #3 substring$ 'result := }
      { result #1 #3 substring$ 'result := }
      if$
  result
}

FUNCTION {test}
{ "TeX User Group" acronym.word top$ }

ENTRY{}{}{}

READ
EXECUTE{test} %% => TUG

さて、BeaSTはTeXマクロとは関係ないですが、BeaSTで書くのは参考文献のデータを扱うプログラムなので、その結果を組版で使ったり本文から参照したりするには、やはりTeXの知識が必須です。というか、そもそもBibTeXは相互参照について、TeXマクロを駆使したLaTeXの機能にかなり依存しています。ここでもやはりTeXそのものの仕組みからは逃げられないのでした。

上記の例はこんな感じに実行します(コードをacro.bstというファイルに保存しているとします)。

$ cat test.tex
\documentclass{book}
\begin{document}
\bibliographystyle{acro}
\bibliography{anything.bib}
\end{document}
$ latex test > /dev/null
$ bibtex test
This is BibTeX, Version 0.99d (TeX Live 2018)
The top-level auxiliary file: test.aux
The style file: acro.bst
I found no \citation commands---while reading file test.aux
Warning--I didn't find any fields--line 26 of file acro.bst
Database file #1: anything.bib
TUG
(There was 1 error message)
$

下から3行めに"TeX User Group"の頭字語である"TUG"が表示されているのがわかりますね。ちなみにエラーになっているのは、参考文献のデータベースを何も用意してないからです。

実はもっとある

KnuthはTeXを作ったときに数式用の書体を作るためのプログラム言語も開発した、という話を聞いたことがあるかもしれません。この言語はMetaFontと呼ばれています。さらに、MetaFontと同じ文法でTeXにより絵を書くためのMetaPostという仕組みもあります。とくにMetaPostはTeXとは別の仕組みであり、TeXマクロ、expl3、LuaTeXなどとも無関係ですが、TeXエンジンでネイティブに処理できるという特徴があるので、TeXのドキュメントで図形を描画したりするのに使えます。

最後に、ここまで紹介してきたのは、TeXの処理に手を加えたり、TeXと一緒に使う仕組みの処理に手を加えるための言語でしたが、これらで実現できることは、どうしても限られます。TeXそのものの原始的な組版の挙動を変えるには、TeXそのものに手を入れるしかありません。そのためには、TeXの実装言語まで下りる必要があるんでしょう。これはWEBと呼ばれています。ぼくはWEBを書いたことがないので、WEBでTeXの挙動を変える話については北川さんの記事で雰囲気を見てください。

Discussion