あるいは 〜私の TeX プログラム変換環境〜
TeX & LaTeX Advent Calendar
\textbf を語ろう、時には \futurelet を語ろう
初心者な人も TeXnician な人も、TeX 芸人な人も。
ステップ 5 : TeX コードに一気に書き換える
変換の規則については……
前回までで、元の「普通の言語」(この記事では Lua を採用している)のプログラムを「TeX に直接変換しやすい形」に変換する作業が完了した。これからいよいよ実際に TeX のプログラムに変換する作業が始まる訳であるが、その際の変換規則の詳細については……
省略する。
これについては、(このシリーズを書く動機となった)他の方の解説記事に詳しく述べられているので是非そちらを参照してほしい。
- プログラマーのためプログラミングLaTeX - プログラムモグモグ(itchyny さん)
- LaTeXで "千反田える" してみた(5) - neruko3114's Blog(ネルコ さん)
そして、もしそこにある解説に疑問を感じたならば、是非まともな TeX の解説書*1で確認してほしい。
TeX のコード記述における空白文字の扱い
その代わり(になってない)として、ここでは TeX コードを記述する際の様式上の注意について述べる。大事なのは、空白類文字(ここでは空白文字、タブ、改行文字の 3 つを指すことにする)の扱いである。他の言語の使用者が特に注意すべきことがある。
TeX はフリーフォーマットじゃない
プログラム言語が「フリーフォーマットである」というのは、空白類文字(空白文字、タブ、改行など)が字句解析の結果に影響を与えない*3(だから好きな個所に好きなように空白類文字を入れて構わない)ことをいう。現代的なプログラム言語の大部分は「ある程度はフリーフォーマット」であるが、その程度は言語により異なる。C 言語はほぼ完璧にそうであるが、Perl では空白が解析結果を変える「微妙な例」が存在する。また改行は多くの言語で「文の区切り」としての意味をもつ。Python や Haskell では行頭のインデントがブロックのネストを示すという規則があり、また whitespace 言語では全ての空白類文字が有効なトークンを成す*4。
さて、(文書生成系としての)LaTeX はどうであったか。欧文の(自然)言語は単語区切りを空きで表示する。だから「I had a dream.
」のようなソースを書いた時に空白文字が空きを生成してくれないと具合が悪い。なので LaTeX はあまりフリーフォーマットではない。といっても、空白や改行が全てそのまま出力に反映されるのではなく、文書ソースの作成の利便性のために、やや複雑な規則になっているのであった。この「空白文字の規則」*5は LaTeX の参考書に必ず載っていて皆のよく知るところだと思われるが、念のため復習しよう。(verbatim 等の特殊な環境ではないとする。*6)
- 行頭と行末にある空白文字・タブは全て無視される。
- コメントの直後の改行文字は無視される。
- 空行(連続する改行文字*7)は『改段落』を表す。
- (空行以外の)改行文字は空白文字になる。 (ただし pTeX 系で、行末が和文文字の場合は改行文字は無視される。((というのは常識であろうが、本当はもう少し複雑である。実は、「行末の文字」を判定する時に
- 1 つ以上の空白文字・タブの並びは単一の空白文字と見做される。
- 制御語(名前が英字からなる制御綴)の直後の空白文字は無視される。
- それ以外の空白もじが『意味のある空白文字』となる。
{
と }
は無視される。なので行末が「{あ}{}
」である場合、改行文字は無視される。)))
ここで注意すべきこととして、TeX(on LaTeX)では「普通の文書」の文脈と「プログラム」の文脈を一切区別しないので、上の少々複雑な「空白文字の規則」がそのまま適用される。あやふやな理解になっている人はしっかりと確認した方がよいだろう。さらにプログラミングの場合には次のことに注意すべきである。
- 3 において、『改段落』は実際には
\par
というトークンであらわされる。通常は\par
を実行すると改段落が行われる*8が、マクロ定義(\def
)の本体中に空行がある場合は、そこに\par
トークンがあると見做される。 - 無視されずに残る『意味のある空白』のことを「空白(文字)トークン」という。段落内で空白トークンを実行すると水平空き(
\spaceskip
)が出力される場合が多い。
例えば、次のようなマクロ定義を行ったとする。
\def\MacroA{ 1 2% 3 4 }
この時、\MacroA
の定義本体は次のようになる。(空白トークンを ␣
で表した。全部で 4 つある。)
␣1␣\par23␣4␣
さらなる注意として、空白トークンが必ずしも空きを生成するとは限らない。
\newcount \xx@value \def \MacroB {␣ \def\xx@msg{Answer=}␣ \xx@value =␣42␣ \xx@msg \textbf{\the\xx@value}␣ }
先の規則に従うと、␣
で示した位置の空白文字・改行文字は空白トークンとなるはずである。これを次のように使うとどういう出力になるか。
\settowidth{\dimen@}{ }\rule{\dimen@}{1pt}\par % 欧文空白の幅の罫を出力して改段落 \MacroB/\MacroB/
「42」の後に空白 1 つ分の空きがあるのはいいとして、2 つ目の「42」の前の空きは空白 2 つ分しかなく、*9さらに 1 つ目の「42」の前には空白による空きが存在しない。((上の罫のある行と比較して判断できる。なお実際には両方の行の頭に段落下げの空き(\parindent
)が存在する。))ここで空きが入らないのは次のような理由による。
- 段落外で空白トークンを実行しても何も起こらない。(改段落直後の)1 つ目の
\MarcoB
の実行では、文字「42」が出力されるまで段落が開始されないので、それまでは空白トークンを実行しても空きは出ない。2 つ目の\MacroB
は段落内で実行されているので、空白トークンの実行で空きができる。 42
の直後の空白トークンは「整数を表す数字列を終結させる空白」なので吸収(無視)されて実行されない。ちなみに、この空白トークンを吸収した時点で代入文が「完成」するので、その時点になって初めて\xx@value
への代入が行われる。=␣42
の中の空白トークンは「文(この場合代入文)の途中にある空白」なので吸収(無視)されて実行されない。
このうち最後の性質はプログラマにとって有益であるかも知れないが、残りの 2 つは厄介なことが多い。実際に TeX プログラミングをしている人なら知っているように、行末の改行文字から生じる空白トークンは大抵の場合に「出力に余計な空きを混ぜ込む邪魔なもの」である。だから多くの場合に行末に %
を付けて改行文字を無効化する作業が必要になる。この場合に、上記の 2 つの性質は次のような問題を引き起こす。
- どこかの行で改行文字の無効化を忘れて「邪魔な空白トークン」が入り込んでいたという時に、もしそのマクロのテストを段落の頭だけで行っていた(典型的には本文領域にそのマクロの実行だけを置く)とすると、「邪魔な」空きが出力されないので、混入した空白に気付かないことになる。段落の途中で使われ得るマクロを作成している時は、段落の途中での使用もテストしておく必要がある訳である。
- 2 つ目の性質は、数字列を終結させるために行末の空白トークンが必要な場合もあることを示している。つまり、無思慮に行末に全部
%
を付けるというのは上手くいかないということである。
後者について例を挙げてみる。
\advance\xx@hits -1 \the\xx@hits~Hits!%
というコードは \xx@hits
が 100 である時に実行すると「99 Hits!」と出力する。ここで、誤って「-1
」の後の改行を無効化して
\advance\xx@hits -1% \the\xx@hits~Hits!%
としてしまうと正常に動作しない。1
から始まる数字列をスキャンする時に、終結する空白トークンが欠けているために、数字列に展開される \the\xx@hits
を巻き込んでしまうからである。結果的に、「\advance\xxphits -1100
」が実行されて「 Hits!」だけが出力される。
要するに、TeX のコードを書く際には、空白文字、特に改行文字について細心の注意を払う必要がある。
*1:何だかんだ言って、TeX プログラミングをやるからには、これは結局は必要だ。
*2:変換規則をちゃんと書こうとすると 5〜6 回の分量の文章が必要で、今はそれを書く気力がないんですよね……。あとそもそも「これに従っていれば必ず成功する」という「規則」を作る必要があるわけで。まあ、強い要望があればこの辺りの説明をすることを検討しよう。
*3:ただし通常は「空白類文字がトークンの区切りとなる」ことは「影響」に含めない。
*4:逆に空白類以外の文字が全部無視される。
*5:以下、LaTeX では空白文字・タブ・改行文字の 3 つを「空白類文字」とする。
*6:すなわち LaTeX の通常のカテゴリコード設定が適用されているとする。基本的には空白類文字が何であるかはカテゴリコードの設定で決まるのであるが、一部ややこしい例外がある。
*7:つまり、「コメントしかない行」は空行ではない。
*8:段落の中でなければ何も起こらない。