TeXの改行とスペース

TeXの処理系は他のプログラム言語とはスペースや改行の扱い方が異なるらしいが、それをよく理解しないままTeXのマクロなどを記述していた。そこで改めてTeXが改行やスペースをどのように扱うのかについて、TeX by TopicのChapter1, 2あたりを読むなどして分かったこと書くことにする。 TeX by Topicは和文組版でよく用いられるpTeX系の内部に関しては述べられていない。そこで和文に関してはLuaTeX-jaのドキュメントを参考にした。

改行とスペース

そもそもどのようなことを問題としているのかというだが、例えば次のようなものを何かTeXの処理系でコンパイルすることを考える。

This is a pen.
I like it.

スクリーンショット 2014-01-07 16.43.36.png

よく見ると、一行目の pen. と二行目の I like の間にあった改行が、スペースへ化けたと分かる。 また次のように、

This is         a         pen.

などと、大量のスペースを注入したとしても、

スクリーンショット 2014-01-04 17.43.19.png

という感じで、一部のスペースは無視される。 このように、TeXは記述したスペースや改行が全て無視されるというわけでもなければ、全て成果物に表われるというわけでもない。

TeXの入力処理

TeX by TopicではTeXの入力処理は 有限オートマトン であるとしている。ただしTeX by Topicは和文組版について全く述べられていないので、ここではLuaTeX-jaのドキュメントを参考に、pTeX系のオートマトンを述べることにした。

アルファベット

TeXは入力される文字を一文字ずつ、カテゴリーコードという文字に割り当てられた数字によって分類する。カテゴリーコードについてはTeX Wikiなどに詳細(?)がある。

ただ、TeXの状態遷移を表わすのであれば、とりあえず次のように分類すればことが足りると思う。なのでアルファベット(記号)を次のようにする。

  • \:バックスラッシュ
  •  :半角スペース
  • \n:改行記号
  • %:コメント文字
  • C:半角英字(a-z, A-Z)
  • G:グループの開始と終了({, }
  • O\,  , \n, %以外の半角文字
  • J:日本語文字(あ、イ、宇……)

5つの状態

pTeXは次の5つの状態を持つ。(TeX by Topicでは状態 CS は書かれていないが、こちらの説明がやりやすいと思うので追加した。)

状態 N:新規行
TeXが始まった時、または何らかの状態から、\nによって新しい行が始まった時の状態
状態 K:和文の行中
何らかの状態から、日本語文字Jが出現した時の状態(pTeX系にのみ存在)
状態 M:英文の行中
何らかの状態から文字C, Oが出現した時の状態
状態 S:スキップスペース
何らかの状態からスペース の出現などによって、以降のスペースを読み飛ばす時の状態
状態 CS:コントロールシークエンス
何らかの状態から\の出現によって、コントロールシークエンス(マクロなど)を構成する時の状態

TeXソースコードにおけるスペースや改行は、このオートマトンの状態によって処理が変わる。

状態遷移と改行などの扱い

各状態と、その状態によって改行やスペースがどのように扱われるのか述べる。

状態 N :新規行

TeXはこの状態から始まり、新しい行へ入った際にこの状態へ遷移する。この状態では、

  • 全てのスペース が無視される
  • 改行\nを行うと改段落\parが挿入される

状態 K :和文の行中

何か和文文字Jが出現した際にこの状態へ遷移する。この状態では、

  • スペース が出現すると、スペースを表示して状態 S へ遷移する
  • 改行\nがあると、何も出力せずに状態 N へ遷移する

つまり、スペースは表示され、改行は状態を遷移させるだけで表示に影響は与えない。例えば次のような文章を与える。

スペース の        テスト
改行したが、前の行に書いた文字との間に
スペースはない。

スクリーンショット 2014-01-05 17.12.23.png

最初の行はよく見ると「の」の前後にスペースが挿入されているが、たくさんスペースを連続して書いても、状態 S へ遷移してスペースを読み飛ばすので、「の」と「テスト」の間に大量の隙間が出来ることはない。 また、状態 K においてはグループ文字Gが表われても状態 M へ遷移しない。なので、

グループを開始させて改行し{
直後にグループを閉じて}
さらに改行した。

スクリーンショット 2014-01-05 20.12.09.png

としても、改行の部分にスペースが挿入されない。

状態 M :英文の行中

英字Cの他に、例えば{, }といったグループに関する文字や、数字などによって状態 M へ遷移する。 "This is a pen."の例で挙げたように、英文の場合は和文と異なり改行によってスペースが挿入される。

  • スペース が出現すると、スペースを表示して状態 S へ遷移する
  • 改行\nがあると、スペースを表示して状態 N へ遷移する

従って、例えば次のような記述では、

This is a pen,{
I like it}
very much.

スクリーンショット 2014-01-05 20.18.14.png

などと、改行の度にスペースが挿入される。

ただこの時にコメント文字%が出現して次のようになった場合を考える。

This is a pen.%
I like it.

スクリーンショット 2014-01-06 1.09.04.png

このように%が出現した後改行した場合、その後に改行\nがあったとしてもコメントアウトされているものとされる。従って改行によるスペースの挿入も行われず、状態 M のまま次の行を処理する。従ってスペースの挿入などが行われない。

状態 S :スキップスペース

次のような時に状態 S へ遷移する。

  • スペース の後
  • 状態 CS の後(一部例外あり)

この状態の時、スペースと改行は次のようになる。

  • スペース を無視する(何も表示せず、状態もそのまま)
  • 改行\nがあると、何もせず状態 N へ遷移する

つまりこの状態においては、連続するスペースが全て無視される。

状態 CS :コントロールシークエンス

文字\の後にこの状態へと遷移する。TeX by Topicにはコントロールシークエンス( control sequence )として、次の2つを全てまとめたものであると述べている。

コントロールシンボル(control symbol
\の後に、英字以外の一字,%などが続くもの
コントロールワード(control word
\の後に、英字Cあるいは日本語文字Jが一字以上続くもの

つまり、\smallといったものはコントロールワードであり、\%\,がコントロールシンボルとなる。状態 CS はこの二つで動作が異なる。

コントロールシンボル

\の後にC以外のものが続くもの場合は、その一字を読んだ後状態 M へ遷移する。例えば次のようなものを考える。

コントロールシンボル\%
の直後に改行を入れる。

スクリーンショット 2014-01-05 20.49.34.png

このように%記号の直後に半角スペースが挿入されていることから、状態 M へ遷移したことが分かる。(分かりやすくするためスペースの部分に色を付けた。)

ただし例外があり、\ \+半角スペース、コントロールスペースと呼ばれる)の場合は状態 S へ遷移する。

コントロールワード

コントロールワードの場合は、\に続く英字Cあるいは日本語文字Jを全て読んだ後に状態 S へ遷移する。従って、

  • コントロールワードの後にあるスペースは無視される
  • コントロールワードの直後にある改行は何も表示せず、状態を N へ遷移させる

となる。例えば次のようにコントロールワードの直後にいくつかのスペースを挿入する。

\def\hoge{ほげ}

コントロールワード\hoge         % 大量のスペース
の直後に大量のスペースを入れる。

スクリーンショット 2014-01-05 21.00.12.png

このように\hogeの後に入れたスペースは表示されない。

引数を取るコントロールワード

コントロールワードの中には、次のように引数を取るものがある。

\def\hoge#1{(#1)が吸い込まれた}

\hoge       の直後にはスペースが入っている。

スクリーンショット 2014-01-05 23.36.02.png

図の通り、\hogeの直後にある大量のスペースは状態 CS\hogeを処理した直後に状態 S へと遷移し、そこでスペースは消滅した。その後に引数の処理を行ったためスペースの直後にある「の」が引数となった。 また、境界なしの場合は引数と引数の間にある全てのスペースや改行が無視される。

ただし、コントロールワードが引数を取る際は、コントロールワードがどのように引数を取るかによって、スペースなどの処理が分かれる。

境界なし引数(Undelimited parameters)
\def\hoge#1#2{...}のように、引数と引数の間に境界を明示しない
境界あり引数(Delimited parameters)
\def\hoge#1, #2{...}のように、引数と引数の間に境界となる文字列を明示する

境界なし引数

まずは境界なし引数の場合は、コントロールワードの直後にあるスペースと改行が状態 S によって無視され、その後引数の確保になる。

\def\hoge#1#2{(#1,#2)が吸い込まれた}

\hoge
  A
  B
という感じ。

スクリーンショット 2014-01-07 16.15.50.png

このように、 "A"の後にある改行や状態 M による改行で挿入されるスペースは無視されているが、"B"の後にある改行(状態 M における改行で挿入されるスペース)は残っている。つまり、引数と引数の間に境界がない場合、引数の間にあるスペースや改行は無視される。

境界あり引数

ここで、境界となる文字列を明示すると次のようになる。

\def\hoge#1,#2and#3{(#1,#2,#3)が吸い込まれた}

\hoge A , B and C
という感じ。

スクリーンショット 2014-01-07 16.19.12.png

このように境界文字列で挟まれた部分についてはスペースなどが無視されない。(ただ、スペースによって状態が S へ遷移するので連続するスペースは一つ分になるなどする)

まとめ

結論としては、

  • 状態 M の時に改行するとスペースが挿入される可能性がある

ということになる。なので、

  • 和文(状態 K )の改行は無視される
  • コントロールワードの直後にあるスペースや改行は無視される
  • コントロールワードの引数は、境界を明示しているのかどうかで振る舞いが違うが、場合によっては改行によるスペースなどが入る可能性がある

コメント