☃️

一度きりのTeX実行で番号参照する試み

2024/12/12に公開

一度きりのTeX実行で番号参照する試み

TeX & LaTeX アドベントカレンダー 2024 に参加させていただきました。
この記事は12日目のとっておき記事です。
昨日はutさん、明日はzr_tex8rさんです。

何度も実行したくない

節や図、表、ページなどに自動で振られた番号を参照して文中に表示するには、通常、LaTeX(ラテフ)の\label \ref \pagerefの機能を使います。
番号情報を記した中間ファイルを1回目の実行で作り、2回目以降の実行時にそれを参照する仕組みです。

しかし、なぜ何度も実行しないといけないのでしょうか。

ページ番号については、版を組んでみないと分からないので、どうしても2回(以上)の組版処理が要ります。
しかし、節や図、表の番号を参照するのは、1回の実行でできないでしょうか。
中間ファイルも作らずできないでしょうか。
LaTeXではなくTeX(テフ)でできないでしょうか。

もし、1回の実行で番号参照できれば、同じ組版処理を何度も待ったり、実行回数が足りているか気にしたりしなくて済みます。
特に、ページ番号の参照がない小さな文書なら、1回の実行で済ませたいところです。

1回の実行で文書全体を2回読む

1回実行による番参照は、文書全体を2回読むようなTeX大命令を作ることで実現できそうです。
つまり、下のような大命令(マクロ)を作って、文書の内容全体を\番振始\番振終で囲みます。

\long\def\番振始#1\番振終{%
  [#1を使って番号を振る]
  [#1で番号参照しながら通常通りの組版をする]
}

XeLaTeX上での小さな例

%xelatex
\documentclass[a4paper,xelatex,ja=standard,jafont=ipaex]{bxjsarticle}
\usepackage{graphicx}%
\pagestyle{empty}

\newcount\図\図番=0
\newcount\表\表番=0

\def\Ref#1{}
\def\Label#1#2{%
  \global\advance#1 by 1%
  \expandafter\xdef\csname#2\endcsname{\the#1}}
\long\def\番振始#1\番振終{%
  {\def\begin{}\def\end{}
    \setbox0\vbox{#1}} % vbox内で印字を抑制しながら実行
  \def\Ref##1{\csname ##1\endcsname}
  \def\Label##1##2{\csname##2\endcsname}%
  #1}

\begin{document}
\番振始
アを図\Ref{図ア}に、イを図\Ref{図イ}に、ウを図\Ref{図ウ}に示す。\par%
甲を表\Ref{表甲}に、乙を表\Ref{表乙}に、丙を表\Ref{表丙}に示す。\par%
\begin{figure}[h]\centering
\quad\Label\図{図ア}:ア%
\quad\Label\図{図イ}:イ%
\quad\Label\図{図ウ}:ウ%
\end{figure}
\input{外la}
アを図\Ref{図ア}に、イを図\Ref{図イ}、ウを図\Ref{図ウ}に示す。\par%
甲を表\Ref{表甲}に、乙を表\Ref{表乙}、丙を表\Ref{表丙}に示す。\par%
\番振終
\end{document}
外la.tex
外部ファイルここから。

\begin{table}[h]\centering
\quad\Label\表{表甲}:甲%
\quad\Label\表{表乙}:乙%
\quad\Label\表{表丙}:丙%
\end{table}

外部ファイルここまで。\par

XeTeXでの大きな例

%xetex
\input miniltx %https://ctan.org/pkg/miniltx からダウンロード
\input graphicx.sty %
\input hologo.sty %
\footline={}
\baselineskip=20pt
\font\ミン="IPAexMincho" at 12pt
\font\ゴチ="IPAexGothic" at 12pt
\font\ゴチ小="IPAexGothic" at 8pt
\font\ゴチ大="IPAexGothic" at 14pt
\font\ゴチ巨大="IPAexGothic" at 50pt

\newcount\節\節番=0
\newcount\図\図番=0
\newcount\表\表番=0
\def\Ref#1{}
\def\Label#1#2{%
  \global\advance#1 by 1%
  \expandafter\xdef\csname#2\endcsname{\the#1}}
\long\def\番振始#1\番振終{%
  \setbox0\vbox{#1} % vbox内で印字を抑制しながら実行 
  \def\Ref##1{\csname ##1\endcsname}
  \def\Label##1##2{\csname##2\endcsname}%
  #1}
\def\Section#1#2#3{%
  \par\vskip10pt\noindent
  \ifx#2\Label {\ゴチ大 \Label\節{#3}. #1}
  \else {\ゴチ大 \Label\節{#1}. #1}
  \fi
  \par\vskip 0.5em
}

\newbox\boxa%最も縦長に表示する画像をもとに縦箱の縦幅を決定
\setbox\boxa\hbox{\includegraphics[width=.3\hsize]{ctan_lion_350x350.png}}
\def\縦箱#1{\vbox to \dimexpr\the\ht\boxa+24pt\relax{#1}}%+2行分24pt

\ミ\番振始

\noindent {\ゴチ 後方の番号を先立って参照する例}\par
この文章は{\ゴチ 節\Ref{図節}}{\ゴチ 節\Ref{表節}}の2節から成る。\par
曲道注意の標識を{\ゴチ 図\Ref{曲道注意図}}に、雪だるまを{\ゴチ 図\Ref{雪だるま図}}に、\TeX\hskip0pt獅子を{\ゴチ 図\Ref{CTANライオン図}}に、それぞれ示す。\par
組み合わせの表を{\ゴチ 表\Ref{組合表}}に、罫線の多い表を{\ゴチ 表\Ref{多罫表}}に、それぞれ示す。\par

\Section{様々な図}\Label{図節}
\def\横箱#1{\hbox to.3\hsize{\hfil#1\hfil}}
\hfil\hbox{\offinterlineskip\ゴ%
\縦{\vfil
  \横{\includegraphics[width=.28\hsize]{Borne_Michelin_Virages.JPG}}
  %https://commons.wikimedia.org/wiki/File:Borne_Michelin_Virages.JPG
  \横{\ゴチ小\strut Roulex 45 作}
  \横{\ゴチ小\strut (CC) 表示-継承 3.0}\vfil
  \横{\Label\図{曲道注意図} 曲道注意の標識}}
\縦{\vfil
  \横{\ゴチ巨大 ☃}\vfil
  \横{\Label\図{雪だるま図} 雪だるま}}
\縦{\vfil
  \横{\includegraphics[width=.3\hsize]{ctan_lion_350x350.png}}
  %https://ctan.org/lion
  \横{\ゴチ小\strut Duane Bibby 作}\vfil
  \横{\Label\図{CTANライオン図} CTANライオンの絵}}
}

\Section{様々な表}\Label{表節}

\noindent {\ゴチ 外部ファイルを含む例(input で読み込み)}\par
\input{}\vskip 3mm%

\番振終

\bye
外.tex
外部ファイルここから。\vskip10pt

\def\雪TeX{\lower-1.1ex\hbox{}\kern-.8em\lower.5ex\hbox{}\kern-.3em\TeX}

\ゴ\hfil\hbox to.45\hsize{\hfil\strut\Label\表{組合表} 組み合わせの表\hfil}%
\hfil\hbox to.45\hsize{\hfil\strut\Label\表{多罫表} 罫線が多い表\hfil}\par

\hfil\hbox to.45\hsize{\offinterlineskip
  \hfil\vbox{\halign{
	\vrule width0pt height13pt depth4pt\hfil~~#~~\hfil\vrule%
	  &&\hfil~~#~~\hfil\cr
	&\TeX&だるま\cr\noalign{\hrule}
	Xe&\hologo{XeTeX}&キセノン達磨\cr
	\omit\vrule width0pt height18ptdepth6pt\hfil\hfil\vrule&\雪TeX&\ゴチ大 ☃\cr
	}\vfil}\hfil}
%
\hfil\hbox to.45\hsize{\offinterlineskip
  \hfil\vbox{\halign{%
	\vrule height 13pt depth4pt\hfil~~#~~\hfil&#\vrule\hskip1pt\vrule%
	  &&\hfil~~#~~\hfil\vrule\cr
	\noalign{\hrule}
	&&\TeX&だるま\cr\noalign{\hrule}
	\omit\vrule height1pt\hfil&&\omit\hfil\vrule&\omit\hfil\vrule\cr\noalign{\hrule}
	Xe&&\hologo{XeTeX}&キセノン達磨\cr\noalign{\hrule}
	\omit\vrule height18ptdepth6pt\hfil\hfil&&\雪TeX&\ゴチ大 ☃\cr\noalign{\hrule}
	}\vfil}\hfil}\par

\ミ\noindent {\ゴチ 既に示された番号を参照する例}\par

この文章は{\ゴチ 節\Ref{図節}}{\ゴチ 節\Ref{表節}}の2節から成る。\par
曲道注意の標識を{\ゴチ 図\Ref{曲道注意図}}に、雪だるまを{\ゴチ 図\Ref{雪だるま図}}に、\TeX\hskip0pt獅子を{\ゴチ 図\Ref{CTANライオン図}}に、それぞれ示した。\par
組み合わせの表を{\ゴチ 表\Ref{組合表}}に、罫線の多い表を{\ゴチ 表\Ref{多罫表}}に、それぞれ示した。\par

なお、\雪TeX とは、雪だるまが含まれた\TeX 組文書の総称かもしれない。

\hfill 外部ファイルここまで。

注意

ここで紹介している方法は、まだ試みの段階です。
どんな場合にどんな問題が起きるか、まだよく分かっていません。

\番振始\番振終の中に入れると動かなくなってしまうものがたくさんありそうです。
例えば、前回の記事で紹介した、字類番号(catcode)を変えた表組みは、残念ながら\番振始\番振終の中では動かなくなってしまいました。
より実用的には、それらを解決して正しく動くようにしなければいけません。

すぐに使ってみたい場合は、小さな文書などで、問題が起きないことを確かめながら慎重に使うのが良いでしょう。

望み

やはり、1回で済むのは使い心地がいいです。
いつの日にかは、ページ番号の参照が無い文書は、1回の実行で済むのが当たり前になれば嬉しいです。

誰かがより良い方法を思いついて、よい形に仕上げ、使えるようにしてくれるとありがたいです。
偉大なるTeX達人の方々に期待したいと思います。

(もちろん、この記事で示した方法は誰でも自由に使って構いません。)

まとめ

番号参照の理想的な形への考えと、その試みを紹介しました。

より詳しく

1回実行での後方参照が難しい理由

1回での参照が難しいのは、前にさかのぼる計算が要るためです。
前から順に処理する通常の方法では、後ろの情報は参照できません。
つまり、参照の文が先に来る場合、その部分を処理している時にはまだ、その後に来るまだ割り振っていない番号は分かりません。
後方参照するには、先に一度全体を一通り読んで全ての番号を割り振った後、初めにさかのぼって再び全体を読み通常の組版処理をする、という二重の手続きが必要です。

超越計画法(メタプログラミング)

このような、さかのぼりを含む計算記譜は「超越計画法」(メタプログラミング)で実現できます。
超越計画法とは、算譜を作る算譜のことです。

先に全体を一通り読んで再処理用の算譜を作り出してから再び全体を実行することで、あたかも前にさかのぼるような計算ができ、手続きの前後関係を超越できるというわけです。

Lispで前処理するという発想

番号参照をLaTeXよりも早く上手く処理する方法として、(1回実行による解決ではなく)前処理を考えたこともありました。
前処理には、Lisp(リスプ)などの大命令(マクロ)機能による超越計画ができる言語が合うでしょう。
Lispを上位言語としてLisp内に文章を書いて、そこからTeXファイルやHTMLを作るのも面白そうです。

ただし、やはり文書を書くには、普通の算譜言語よりもTeXの方が向いています。
TeXの、引数の番兵を自由に指定できる大命令定義\defも使い勝手が良く、文字列処理の仕組みとしても面白いです。
文書記述言語として、TeXには独特の魅力があります。

なお、LispをTeXの前処理器として使うという発想には、先行例(岩崎 英哉 - Lisp を用いた TEX のプリプロセッサ)もあるようです。
やはり、柔軟で強力なLispを使おうというのは、前例もある自然な考え方です。

TeXはなんでもできるはず

何度も実行しないとできないことがあるというのは、組版処理系あるいは算譜言語として不完全であるようにも思えてしまいます。
しかし、TeX言語は不完全ではありません。
TeX言語は計算完備(チューリング完全)だそうなので、基本的には何でもできるはずです。
前処理器を使わなくても1回の実行でできるはずです。

そして、まだ試みですが実際にできました。
簡単にあきらめてはいけなかったのです。

大命令で超越計画

TeX言語は大命令(マクロ)による言語です。
LispとTeXとではかなり勝手が違いますが、どちらも大命令による超越計画ができるのです。

この記事ではマクロを大命令と訳しました。
マクロとは、もともとは「大きい」という意味で、大命令は、その名の通り大きな力を持っています。
大命令は一見して関数(函数)に似ていますが、関数の働きは、値を返したり状態を変えたりといったことに限られます。
これに対して大命令は、算譜自体を作り出すという超越計画の働きを持ち、関数を上回る大きな力があるのです。

\番振始#1\番振終の引数#1には文書の内容を丸ごと入れました。
文書全体を引数に取ってそれを2回使うという超越計画により、2回分の実行を1回に減らしているわけです。

字句置換と超越計画の関係

一般的な算譜言語では、算譜自体を丸ごと引数に取って処理することは難しいです。
これに対してLispやTeXでは、算譜そのものを記号形式(S式)や字句(トークン)のような基本単位によって統一的に構成しているため、算譜自体を引数に取って処理することも容易いです。

このように考えていくと、TeXのような大命令方式の言語における字句置換の仕組み自体が、ある種の動的な算譜の生成であり、超越計画の一種であるという気付きも得られます。

訳語と造語

超越計画法(メタプログラミング)、大命令(マクロ)は、この記事を書くにあたり新しく作った訳語です。
雪TeXとキセノン達磨は、まだ誰も知らない謎の概念です。

おわりに

お読みくださりありがとうございました!
ハートやバッジを頂けますととても嬉しく思います。
ご感想、ご指摘などもお気軽にお寄せください。

Discussion