マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

TeX のループ構文

TeX は if 以外の実行制御を全てマクロ展開で行う」という前置きをおいて、再帰による繰り返し処理の話をしてきた。ところが、実は、TeX にも繰り返し処理用の「構文」らしきものがある。それは \loop...\if...\repeat というもので、丁度 C 言語(やその書式を受け継ぐ言語)の do ... while(...) に相当する。

% \countUpTo{<n>}: 1 から n までの整数を出力する
\newcount\num
\newcount\limitNum
\def\countUpTo#1{%
  \num=0 \limitNum=#1\relax
  \loop
    \advance\num1
    \the\num\space
  \ifnum\num<\limitNum \repeat
}

この \loop は次の形で用いる。

\loop <処理1> \if<条件> <処理2> \repeat
  • 「処理1」と「処理2」を順に繰り返す。「処理1」終了後に条件が不成立ならループを脱出する。C 言語で等価なものを書くと、do { <処理1> if ( ! 条件 ) break; <処理2> } while(1); となる。
  • 「処理2」を空にしたのは do { <処理1> } while (条件); と等価。
  • 「処理1」を空にしたのは while (条件) { <処理2> } と等価。

割りと普通の形の反復構文である。とすると「TeX には反復構文がない」というのは嘘なのか。無論そうではなくて、実は、この \loop は plain TeX で定義されたマクロに過ぎないのである(LaTeX でも使える)。だから plain.tex に次のようなプログラムが載っている。

\def\loop#1\repeat{\def\body{#1}\iterate}
\def\iterate{\body \let\next\iterate \else\let\next\relax\fi \next}
\let\repeat=\fi % this makes \loop...\if...\repeat skippable

このように、TeX には「一見言語で用意された構文のように見えるが、実はマクロとして実現されている」ものが結構存在する。例えば、変数を宣言する(レジスタを確保する)基本的な構文である \newcount\newdimen、等もマクロである。次のように故意に間違った使い方をしてみると、何やら怪しげなマクロに展開されていることが解る。((エラーは「制御綴があるべきだが見つからないのでダミーの制御綴を挿入した」ということ。このダミーの制御綴は表示上は \inaccessible という名前になっているが、実際には全くアクセスできない(inaccessible である)。))

(plain TeX、対話モードで)
*\newcount x               ← \newcount の後に単なる文字を置いた
! Missing control sequence inserted.
<inserted text>
                \inaccessible
<to be read again>
                   x
\alloc@ ...llocationnumber =\count 1#1\global #3#5
                                                  =\allocationnumber \wlog {...
<*> \newcount x

TeX には整数レジスタが 256 個あり、それは \count0\count255 というトークン列でアクセスできる。レジスタに単一の制御綴による別名を付けているのは、\countdef というプリミティブで、例えば \countdef\foobar=123 を実行すると、\foobar\count123 を指すようになる。((ただし、\foobar は「\count123 」に展開されるマクロではない。))つまり、\newcount\foobar は「まだ使っていないレジスタの番号(例えば 42)を得て、\countdef\foobar=42 を実行する」という処理を行うマクロとなっている。((レジスタの割り当て方法は、「単純に番号の小さいものから使う」となっている。最後に割り当てられた整数レジスタの番号は \count10 に入っていて、\newcount はこれをインクリメントしてから結果の番号のレジスタを返す。))

TeX での真偽値変数の扱いは独特である。1 つの真偽値を「新しい if 文」として定義し、「それが真と偽のどちらに評価されるか」を変更する命令を実行するという形をとる。

(plain TeX)                        (Java)
\newif\iffoobar                    boolean foobar;
\foobartrue                        foobar = true;
\iffoobar \yes \else \no \fi       if (foobar) yes(); else no();
 % \yes が実行される
\foobarfalse                       foobar = false;
\iffoobar \yes \else \no \fi       if (foobar) yes(); else no();
 % \no が実行される

この \newif もマクロであって、\newif\iffoobar は以下のコードを実行する。((実際にはこの後に \foobarfalse を実行する、つまり「初期値」を偽にする。だから Java の等価な表現は実際には「boolean foobar = false;」が正しい。))

\def\foobartrue{\let\iffoobar\iftrue}
\def\foobarfalse{\let\iffoobar\iffalse}

TeX 言語自体が提供している機能(上述の \countdef\def 等)を TeX では「プリミティブ」と呼ぶが、TeX ではマクロもプリミティブも制御綴で表され同じような見かけを持っている。((さらに言うと、これらの機能はその制御綴が最初に持っている「値」(つまり初期値)に過ぎず、プリミティブの制御綴と機能の関係は固定されたものではない。例えば、「\let\foo=\def」や「\let\def\relax」とかの代入は可能である。これは、JavaScript において、undefined や NaN が「初期値」に過ぎないのと同様である。))この性質が TeX において「合成的構文」が多用される理由になっていると思われる。

1 つ注意しておく。plain TeX\loop には重大な欠点が存在する。1 つは \par が書けないこと((上述の定義を見て判るように、\loop\long でない。))で、これは \par の代わりに、「\par の機能を \let した別のトークン」を使うことで回避できる(plain TeX では \endgraf というトークンが用意されている)。もう 1 つは単純にネストさせることができないことで、これは内側のループ全体を { } で囲うなどの対策が必要である。((「{ }(明示ブレース)で囲われていること」と「局所化されていること」の両方が必要である。\loop の定義を解析すれば理解できるだろう。))

% 九九の式を順番に(改段落しながら)出力する
\newcount\cntI \newcount\cntJ \newcount\cntP
\cntI=1 \loop
  {% この括弧が必要
    \cntJ=1 \loop
      \cntP=\cntI \multiply\cntP\cntJ
      $\the\cntI \times \the\cntJ = \the\cntP$%
      \endgraf % \par ではダメ
      \advance\cntJ1
    \ifnum\cntJ<10 \repeat
  }%
  \advance\cntI1
\ifnum\cntI<10 \repeat
\bye

もちろん、この \loop は「定義の 1 つ」に過ぎないので、これの機能が不十分だと感じたら、「もっと有用な繰り返しマクロ」を自分で作ればよいであろう。

最後に LaTeX2e の内部命令として用意されている繰り返し構文を簡単に紹介しておく。

% 整数に対する while 構文
\newcount\xx@cnti \newcount\xx@cntj \newcount\xx@cnts
\xx@cnti=1 \@whilenum\xx@cnti<10 \do{%
  \xx@cntj=1 \@whilenum\xx@cntj<10 \do{%
    \xx@cnts=\xx@cnti \advance\xx@cnts\xx@cntj
    $\the\xx@cnti + \the\xx@cntj = \the\xx@cnts$par
    \advance\xx@cntj1
  }%
  \advance\xx@cnti1
}
% ※ 寸法に対する同様の \@whiledim もある。
% 所謂 for-each 構文、コンマ区切りで列挙
\@for\xx@t:=猫,獅子,シロクマ,アゴヒゲアザラシ\do{%
  \xx@t の子、子\xx@t 。\par
}
% 所謂 for-each 構文、グループで列挙
\@tfor\xx@t:={猫}{獅子}{シロクマ}{アゴヒゲアザラシ}\do{%
  \xx@t の子、子\xx@t 。\par
}