TeX入門 / マクロの作成
TeX の特徴の一つにマクロが作成できることが挙げられます。 TeX マクロは実用的な面を持つだけではなく,知的な趣味としても愉しむことができます。 LaTeX で簡単なマクロを作成したい場合は LaTeX入門/LaTeXマクロの作成 を参照してください。 こちらでは TeX に踏み込んだ解説を目的としています。
マクロの作成技術を身につけるにあたって必要な基本知識を列挙しておきましょう。 これらの事柄を調べてゆくと自ずとマクロ作成技術が身に付くことでしょう。
参考書について:マクロを作成する上で必要となる TeX のプリミティブ命令の多くは,LaTeX の入門レベルの解説書では触れられていないことが多いです。 『TeXブック』には当然解説されていますが,日本語版の入手は困難です。 とりあえず,この Wiki の TeXの本の項で挙げられているもの(TeX by Topic や TeX Reference Manual が詳しいです)を参考になさってください.
(注意)この節の内容には,初学者にとっての理解のしやすさを優先するため,不正確な記述が含まれています。従って,リファレンスマニュアルとしての使用に堪えるものとはなっていません。
TeX が読み込むファイルはテキストファイルであり,文字コードの羅列です。 TeX はそこから様々な命令を読み取って動作します。 例えば入力ファイルに “\sqrt{2}” とあれば,“\sqrt” が平方根を組み立てる命令であることを読み取って,根号の中に2を収めたものを出力します。 この際 “\sqrt” は “\”, “s”, “q”, “r”, “t” という5文字の羅列ではなく “\sqrt” という一塊のものとして認識されます。 この,TeX にとって意味のある,それ以上分解できない一塊のものがトークンです。 前述の “\sqrt{2}” であれば,TeX には “\sqrt”, “{”, “2”, “}” という4つのトークンとして把握される(文字列をトークンに変換している)ことになります。
トークンには,単独の文字がそのままトークンとなっている文字トークンと,sqrt のような名前によって区別されるトークンであるコントロールシークエンストークンがあります。 コントロールシークエンストークンのことを単にコントロールシークエンスともいいます。 このような解説文でコントロールシークエンスを記述する場合,例えば『TeXbook』などでは四角囲みの中にトークン名を収めて表しています。 ここでは括弧で括って[sqrt]のように表してもいいかもしれません(ソースファイル中の“\sqrt”という文字列に対応するコントロールシークエンストークンの名前は“sqrt”であると考えます)。
文字コードの羅列をトークンに変換してゆくプロセスは概ね次の規則に従います。
ここで「英文字」とは何かという問題がありますが,これについては次項「カテゴリーコード」を参照してください。 ただし,ここでは pTeX で使える日本語を含むコントロールシークエンストークンは考えません.
コントロールシークエンスには二種類存在します. ひとつは上記の1のiに相当する,“\”のあとに英文字が続くものでこれをコントロールワードといいます. 例えば,“\alpha”によって生成される[alpha]はコントロールワードです. もうひとつは上記の1のiiに相当する,“\”のあとに英文字以外の1文字が続くもので,これをコントロールシンボルといいます. 例えば,\!によって生成される[!]はコントロールシンボルです.
(注意)上の規則で“\”という文字を使いましたが,より正確には,カテゴリーコードが0である文字といったほうがよいでしょう。カテゴリーコードについては後の解説を参照してください。
さて,ここでコントロールワード[small]に続いて英文字の文字トークン“a”を置くことを考えて見ましょう。 本文中で小さな文字を使って“a”という活字を印字したいという場面です。
ソースファイルにはどう記述すべきでしょうか? “\smalla”でしょうか? これでは駄目ですね。 上記の規則 1のi によって続く限りの英文字が取られて[smalla]というコントロールワードになってしまい,意図とは違ってしまいます。 では英文字以外のものを“\small”の後におけばよいのですから“\small:a”などとしてみます。 今度は[small],“:”,“a”となってしまい間に余計な文字トークンが入ってしまいます。
このように,コントロールワードに続けて英文字トークンを置きたい場合にはなにか区切りになるものが必要ですが,その区切り自体が余計なトークンになってしまうなら,困る場合も出てきます。 そこで,区切り文字として空白文字を使った場合には例外的に,コントロールワードの名前の終わりを表す区切りとして認識したあとで,その空白文字を読み飛ばします。
この例外規則のおかげで,先ほどの問題は“\small a”と入力することで解決されるわけです。 この記述から生成されるトークンは,[small], “ ”, “a”ではなくて,[small], “a”となります。
ただし、この例外規則にともなう副作用として、コントロールワードのあとに本当に空白文字トークンを置きたい場合に一筋縄ではいかなくなる、ということが起こります。 慣れてきたら、コントロールワードのあとに空白文字トークンを置く方法をあれこれ考えてみると面白いでしょう。
空白文字の読み飛ばしについては,他にも考慮すべき例外ルールがあります。
前節で触れた,「英文字」とは何か,という問題に答えるのがこの節です。
TeX で処理するソースファイルはテキストファイルで,われわれの目には文字列のように見えていますが,その実体は文字コードの羅列です。 一文字に8ビットのコードを割り当てるシステムでは文字コードは0から255までの数です。 例えば“a”という文字の文字コードは97となっています。
TeXではこれらの文字を「英文字」や「空白文字」などに分類して,その分類を動的に変えられるようになっています。 そのための仕組みがカテゴリコードです。
例えば,特に変わったことをしなければ,大文字の“A”から“Z”までと小文字の“a”から“z”までの計52文字だけが「英文字」なので,ソースファイルの“\hoge@hoge”という文字列を TeX が読み込むとき,前節のトークン化の規則によって,この文字列は[hoge]というコントロールシークエンスとそれに続く5個の文字トークン“@”, “h”, “o”, “g”, “e”として読み込まれます。 ところが文字の分類を動的に変えられるという TeX の仕組みを利用して,“@”という文字を「英文字」と見なすように設定しておけば,同じ“\hoge@hoge”という文字列から,[hoge@hoge]というたった一つのコントロールシークエンスが読み取られることになるわけです。(下の小節「コントロールシークエンス名に“@”を織り交ぜたもの」も参照)
さて,TeX はこのように文字の分類をしているわけですが,TeX はコンピュータプログラムなので,「英文字」とか「空白文字」という概念を理解しているわけではなくて,単に11番目のカテゴリーだとか5番目のカテゴリーだとか把握しているのです。 例えば「“a”は英文字だ」という代わりに「文字コード97の文字は11番目のカテゴリーの文字だ」という具合です。 TeX はある文字コードの文字がどの分類に属するかを記憶しているわけです。
ここで TeX による文字の分類を一覧表にしてみましょう。
カテゴリーコード | 分類 | 通常この分類に属する文字 |
0 | エスケープ文字 | \ |
1 | グループ開始文字 | { |
2 | グループ終了文字 | } |
3 | 数式モードへの移行文字 | $ |
4 | アラインメントタブ | & |
5 | 行の終了文字 | 文字コード13の文字 |
6 | パラメータ文字 | # |
7 | 上付き文字 | ^ |
8 | 下付き文字 | _ |
9 | 無視する文字 | 文字コード0の文字 |
10 | 空白文字 | 文字コード32の文字 |
11 | 英文字 | AからZまでおよびaからzまで |
12 | その他の文字 | !"'()*+,-./0123456789:;<=>?@[]`他1文字 |
13 | アクティブ文字 | ˜ |
14 | コメント文字 | % |
15 | 無効文字 | 文字コード127の文字 |
TeX がテキストファイルから文字コードの羅列を読み込んでトークンに変換していくとき,カテゴリーコードが0か否か,および11か否かということは特別な意味を持ちます。 カテゴリーコードが0の文字はコントロールシークエンスの始まりを表し,その後にカテゴリーコード11の文字が続く限りそれがコントロールシークエンスの名前になるからです。 その他のカテゴリーの文字は単独の文字トークンとなるわけですが,このとき,その文字トークンには対応するカテゴリーコードが付与されます。
文字の分類を変えるには \catcode というプリミティブ命令を使います。 例えば“a”という文字の分類を「英文字」から「その他の文字」へ変更するには,“a”の文字コードが97で,「その他の文字」のカテゴリーコードが12なので
\catcode97=12
のようにします。 このように変更した後では,例えば“\small”という文字列を TeX がソースファイルから読み込むとき,[small]というコントロールシークエンスではなくて,[sm]というコントロールシークエンスに続いて“a”, “l”, “l”という 3 個の文字トークンが続いたものとして読み取られることになります。
(今日はとりあえずここまで。続きはいつになるか分かりません。)
少し上のところで“\hoge@hoge”という文字列の例を紹介しました。 通常のカテゴリーコード設定の下では、“@”は英文字の一員ではないため、この文字列“\hoge@hoge”は [hoge],“@”,“h”,“o”,“g”,“e”という6個のトークンとして読み取られるのでしたが、“@”のカテゴリーコードを英文字に変更することで [hoge@hoge] という単一のトークンとして読み取らせることができるという例でした。 “@”の文字コードは64ですから、“@”を英文字として扱うためには \catcode64=11 と指定すればよいことになります。 この作業はたびたび必要になるので後述するようなマクロ定義による省略形が用意されています。 \makeatletter という命令で“@”のカテゴリーコードを11(英文字)にして、\makeatother という命令で“@”のカテゴリーコードを12(その他の文字)に戻します。 TeX や LaTeX では、内部命令をユーザーの不用意な再定義から保護する仕組みとして、この“@”の振る舞いを利用しています。 後述するように、TeX や LaTeX では、ユーザーがほぼすべての命令をマクロとして自由に定義し直すことができてしまいます。 この自由度の高さゆえに、内部で使われている重要な命令の定義をユーザーが意図せず変更してしまうことが起こり得ます。 そこで、内部で機能する命令の名称は \hoge@hoge のように英文字化した“@”を含むものとして定義しておいて、ユーザーの文書を処理する際は“@”のカテゴリーコードを「その他の文字」になるように戻しておくことで、不用意に内部命令にアクセスしたり定義し直したりすることを防いでいるというわけです。 内部命令について詳しく知ったうえであえて動作を変更しようとするときは、\makeatletter によって一時的に“@”を英文字扱いとしてから内部命令の定義を変更したりした後で、\makeatother で“@”を「その他の文字」に戻すようにします。
TeX には、次の表に示す種類ごとに256個のレジスタ(変数)があります。
レジスタの種類 | アクセス | 値 | 値の設定 |
整数レジスタ | \count0 ~ \count255 | 符号付32ビット整数値( -2147483648 ~ 2147483647) | <整数レジスタ>=<数値> |
長さレジスタ | \dimen0 ~ \dimen255 | 長さ(-16383.99998pt ~ 16383.99998pt=1073741823sp) | <長さレジスタ>=<長さ> |
グルーレジスタ | \skip0 ~ \skip255 | 伸長度と収縮度を持つ長さ(10pt plus 5pt minus 1pt など) | <グルーレジスタ>=<長さ> plus <伸長度> minus <収縮度> |
ボックスレジスタ | \box0 ~ \box255 | 水平ボックスや垂直ボックスとなる | \setbox<レジスタ番号>=\hbox{...} など。\hbox の代わりに \vbox, \vtopなども可 |
トークンリストレジスタ | \toks0 ~ \toks255 | トークンの列 | <トークンリストレジスタ>={...} |
レジスタに値を設定するには \count0=32 とか \dimen1=24pt のように <レジスタ>=<数値や長さ> と指示します。 この代入に使うイコール“=”は文脈によっては省略可能で、\count0=\count1 のかわりに \count0\count1 などと書くことができます。
長さの単位は 72.27pt = 1in = 25.4mm などが使われます。 pt(ポイント)は一般的な定義よりわずかに小さくなっています。 sp(スケールドポイント)という単位は 2の16乗分の1(=0.0000152587890625) pt で、TeX における長さの基本単位です。 長さの値は TeX 内部で整数値として保持されていて、整数が要求される文脈ではそのまま整数として使われ、 長さが要求される文脈では sp 単位の長さとして解釈されます。 例えば \dimen0 が 1pt であるとき、\count0=\dimen0 のように整数レジスタに長さを代入すると、\count0 の値は 65536 となります。
これら基本レジスタの他に、TeX が動作中に値を参照するための内部レジスタと呼ばれるものもあります。例えば \baselineskip という内部グルーレジスタは、TeX が作成文書に適用する行送り(≒行中の文字の高さ+行間の空白)として参照するグルー値です。内部レジスタについても基本レジスタと同様に値を設定できるので、それによって TeX の動作を制御することができます。その意味で内部レジスタのことをパラメータと呼ぶこともあります。
(ちょっと説明が面倒なので、保留 → どなたかへ)
演算 | 記し方 |
加法 | \advance<レジスタ> by <数値や長さ> |
減法 | \advance<レジスタ> by <負の数値や長さ> |
乗法 | \multiply<レジスタ> by <数値> |
除法 | \divide<レジスタ> by <数値> |
どの演算も<レジスタ>のところに指定したレジスタに演算がなされて、その<レジスタ>の値が更新されます。 文脈によって“by”は省略可能です。
TeX の各種レジスタには 0 番から 255 番までのレジスタ番号でアクセスできますが、 複雑なマクロを組むときには、レジスタごとの役割を表すレジスタ名を付けることができれば便利です。
TeXでは \countdef<トークン>=<レジスタ番号> と指示することで、<トークン>が整数レジスタ代わりに使えるようになります。 (例:\countdef\cntA=64 とすると、\cntA が \count64 の代わりに使えるようになる) この指示には、レジスタの種類に応じて \countdef, \dimendef, \skipdef, \boxdef, \toksdef を使います。
ただし、この例のように \cntA を \count64 に割り当てたとしても、 それを知らない他人が作ったマクロ内部で \count64 の値を変更するような処理があれば、 \cntA でアクセスする値が知らないうちに変えられてしまいます。 これでは他人が作ったマクロを組み合わせて使うときに困るので、plain TeX や LaTeX では統一的にレジスタを割り当てる仕組みが (これまたマクロによって)実装されています。
この仕組みのもとでは、\newcount<トークン> と指示するとレジスタ番号の若い方から順に整数レジスタを割り当てて、<トークン>がそのレジスタ代わりに使えるようになります。割り当てられたレジスタのレジスタ番号は plain TeX や LaTeX の内部に保持されているので、何番レジスタに割り当てられたかは分かりませんが、気にせずに使えるようになります。 この割り当てには、レジスタの種類に応じて \newcount, \newdimen, \newskip, \newbox, \newtoks を使います。
plain TeX や LaTeX でマクロを組む場合、このように \newcount などで割り当てられたレジスタのみを使うようにして、直接レジスタ番号を指定した \count64 のような使い方はしないようにしています。これによって、様々な人が作成したマクロを組み合わせて使えるようになっています。
マクロの定義のしかたです。 ここの解説を読む前に,トークンについて理解しておくとよいでしょう。 上のほうの解説がまだなら先にそちらをお読みください。
一般に、プログラムソース中に命令やトークンから成る特定の並びが何度も現れるとき、その並びに短い名前を付けて管理する仕組みをマクロといいます。ソースファイル内に、その特定の並びの代わりに短い名前を書いておくと、プログラムの実行中や前処理の段階で本来の並びに置き換えて処理されるので、ソースファイルを簡略化するのに使われます。マクロ機能を有するプログラム言語は多くあり、マクロ機能の詳細は、言語ごとに様々です。
TeXのマクロ定義の基本は,引数を取らない簡単な置き換えマクロで,次のように定義します。
\def<トークン>{置き換えテキスト}
ここで,<トークン>のところには定義しようとするコントロールシークエンスかアクティブ文字を書きます。 例えば,TeX のロゴマークを出力する命令である \TeX は、マクロとして次のように定義されています。
\def\TeX{T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX}
このようにすると,\TeX というコントロールシークエンスは “T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX”というトークンの並びに展開されるマクロとして定義されて,これ以降,\TeX が展開されるたびに, “T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX”に置き換えられて処理されます。 これは,“T”に続けて0.1667emの長さの分だけ左に詰めて,0.5exという長さの分だけ下に下げたところに“E”を置き,それに続けて0.125emだけ左に詰めて“X”を置くという動作をさせるもので,“TeX”のロゴを文書中に表現するものです。 文書中に TeX のロゴが何度出てくるような場合,そのたびにこの長い命令を入力するのは大変ですから,マクロを定義することによって簡単に \TeX と入力して済ませられるようになるわけです。
このような組版上の命令ではなくても,
\def\ABP{Alexandroff-Bakelman-Pucciの不等式}
のように定義しておけば,長い名前を \ABP と簡単に入力できるようになりますし,タイプミスを防ぐのにも役立ちます。
ユーザーが定義する命令をマクロというのに対して、もともとTeXに備わっている命令はプリミティブ(=“原始的な組版命令”)といいます。
TeX 実行中に、マクロがマクロ定義に従って対応するトークンの並びに置き換えられることを“展開”といいます。典型的なマクロは、より長いトークン列に置き換えられるので、その置き換えられる様子を“展開”といい表すわけですが、それに止まらず、マクロと引数(引数については次項参照)からなる長いトークン列がより短いトークン列や空のトークン列に置き換えられる場合や、マクロ以外のプリミティブ命令の一部が別のトークン列に置き換えられる場合も含めて、トークン列の変換の様子を“展開”といいます。トークン列の変換を表す“展開”に対して、TeXが内部のレジスタ(変数)の値を変更したり、組み立て中の組版リストに文字を付け加えたり、現在の組版モード(水平モード、数式モードなど)を変更したりといった、TeXがプリミティブ命令を処理して動作を進めることを、命令を“実行”するといいます。
TeXが動作しているとき、マクロはまず展開され、展開され尽くしてできたプリミティブは“展開”または“実行”されます.TeX の動作を考えるときには、この“展開”と“実行”を区別することが肝要です。
マクロには、実行時に引数に応じて動作を変えるようなものもあります。 多くのプログラミング言語では引数を指定するには括弧とカンマを用いて macro("test",2) のような具合に書きますが、TeX の場合は引数の与え方が全く異なるので、先入観を捨てておかないと理解しづらいでしょう。
例えば単純な引数を3個取るマクロを定義するには、次のように記述します。
\def<トークン>#1#2#3{置き換えテキスト ― この中で仮引数として #1,#2,#3 が使える}
ここで,<トークン>は定義しようとするコントロールシークエンスかアクティブ文字です。 続く #1#2#3 の部分はいくつの引数を取るかを示すもので、パラメータテキストと呼ばれます。 この例では3個になっていますが、9個以内であれば好きな個数に設定できます。 必要な個数だけ#1#2#3#4#5のようにつなげて並べます。 マクロが展開されるときには、ブレイス{}で囲まれたテキストか、そうでなければトークンが引数として読み込まれて、置き換えテキストの中の #1 などのところにはめ込まれます。
例えば、数値の指数表現としてアボガドロ定数を$6.02\times 10^{23}$のように書くとき、\times や 10^ の部分は決まりきった表現なので省略したいとしましょう。 このとき、
\def\N#1#2{#1\times10^{#2}}
のようなマクロ \N を定義しておけば、「アボガドロ定数は$\N{6.02}{23}$とする」のように使えるわけです。 マクロが TeX によって展開されると \N{6.02}{23} の部分は 6.02\times10^{23} に置き換えられます。 ここで例えば定義のブレイスを一組忘れて \def\N#1#2{#1\times10^#2} のようにしたとすると、\N{6.02}{23} の展開結果は 6.02\times10^23 となってしまい、10 の肩に 2 だけが乗せられたあとで 3 が 10 と同じ大きさで出力されてしまうことになります。 また例えば最初の定義のまま使うときにブレイスでなくパーレン \N(6.02)(23) を使ってしまうと、「\N(6」までがマクロの引数と見なされて「(\times10^{6}」と展開されて、そのあとに未処理の「.02)(23)」が続くことになります。
マクロの引数の形式には上に挙げた単純な引数の他に、「区切り付き引数」と呼ばれるものもあります。
\def<定義対象となるトークン>パラメータテキスト{置き換えテキスト}
パラメータテキストのところは、パラメータを表す#1, #2 などに加えて区切り文字列と見なす任意の文字列を含ませることができます。例えばナンセンスな例ですが、
\def\emph#1is#2.{\textbf{#1}is\textit{#2}.}
のようなものを考えてみます。Emphasisというのは「強調」という意味で、受け取った二つの引数を強調して表示するマクロです。パラメータテキストは「#1is#2.」の部分で、そのうち「is」と「.」が区切り文字列となります。 このマクロを、例えば
\emph Life is Beautiful.
のように使うと、パラメータテキストの「#1is#2.」とマッチするように、マクロ \emph は「Life 」を#1, 「 Beautiful」を#2として受け取って、
\textbf{Life }is\textit{ Beautiful}.
のようにマクロ展開されます。その結果 TeX は、「Life 」を太字(BoldFont)で、「 Beautiful」をイタリック体(ITaric)で出力する紙面に書き出します。上で最初に紹介した「\def\N#1#2」のような単純な引数の場合は、パラメータテキストのところにパラメータトークン(#1, #2 など)をいくつも続けて書きました。それに対して、「区切り付き引数」を使うには、パラメータトークンの後にこの「is」や「.」のような一般の文字列を置くだけです。TeXが区切り付き引数を持つマクロを展開するときは、一文字ずつ(1トークンずつ)読み込みながら、区切り文字列を発見するまで読めるだけ読み込んで、それを引数としてマクロの置き換えテキストのところで使います。ところで、ここで例に挙げたこのマクロ \emph を次のように使うとどうなるでしょうか。
\emph This is a pen.
パラメータテキストの「#1is#2.」とマッチするように、「This 」が #1、「 a pen」が #2 として取り込まれて
\textbf{This }is\textit{ a pen}.
のように展開されるでしょうか? 答えは否です。実際には「Th」が #1、「 is a pen」が #2 として取り込まれて、展開結果は
\textbf{Th}is\textit{ is a pen}.
となります。TeX が区切り付き引数を読み取るときの動作は次のようになります。まず、引数 #1 として読み取るものを確定するために、パラメータテキストの中で #1 に続く区切り文字列である「is」と同じ文字列をソースファイルの中に見つけるまで、順に一文字ずつ読み込んでいきます。この例の場合、「This」という単語の中に含まれる「is」を区切り文字として最初に見つけてしまいます。そうしてそれまでに読み取っていた「Th」の二文字を引数#1と見なすわけです。そうして、区切り文字と見なした「This」に含まれる「is」の後から読み続けて、今度は #2 に続く区切り文字だった「.」を見つけるまでに読み取った「 is a pen」を #2 として取り込む、というわけです。
区切り付き引数を取るマクロを使うときに、うまく区切り文字が見つけられないとエラーとなります。例えば上記の \emph マクロを
\emph You are beautiful.
と書いてしまうと、TeX は区切り文字列「is」をどこまでも探し続けて、ファイルの末尾まで読み込んでも見つからなければ、引数を読み込み中にファイルが終わってしまったというエラーを出すことになります。区切り文字列をスペルミスしただけの場合や、次のようにグルーピングによって引数の走査階層を変えてしまった場合も同様です。
\emph{Life is Beautiful.}
このような「区切り付き引数」という機構は他のプログラム言語にはあまり見受けられません。マクロを使うときに、スペルミスも許されないような余計な文字列をつける必要性が感じられないかもしれません。区切り付き引数を持つマクロは、主に次のような二つの目的で使われます。ひとつは、区切り文字列によってマクロの機能や引数の与え方を執筆者に連想させやすくするためで、もうひとつは、他のマクロなどによって自動生成されるトークン列から目的の情報を切り分けるためです。
さてここまで「単純な引数」を取るマクロと「区切り付き引数」を取るマクロについて基本的な例を見てきました。両者を包摂するような一般の引数を取るマクロは次のようになります。抽象度の高い一般化したルールを説明するよりも、実例を見てもらった方が理解しやすいので具体例を考えます。
\def\macro#1#2delimiter#3#4{引数は(#1)(#2)(#3)(#4)です。}
パラメータテキスト「#1#2delimiter#3#4」には#1, #2, #3, #4 の四つが含まれるので、このマクロは4つの引数を取るマクロです。このうち、#2 だけはその後ろに「delimiter」というパラメータトークン以外の文字列を従えているので「区切り付き引数」を表すことになります。その他の #1, #3, #4 の三つは「区切りなし引数」を表します。区切り付き引数は、マクロが展開されるときに対応する区切り文字列(この例では「delimiter」という文字列)の前までを引数として取り込みます。区切りなし引数は、単純にひとつのトークンかブレイス{}で囲まれたトークン列を引数として取り込みます。 さて、この例のマクロを次のように使ったとしましょう。
\macro ABCdelimiterSTUVWXYZ
そうすると、展開結果は
引数は(A)(BC)(S)(T)です。UVWXYZ
となるわけです。必要な引数を取り込んだところまでがマクロとして展開されるので、4つ目の引数として「T」が取り込まれたところまでが展開結果に置き換えられて、その後にはそのまま「UVWXYZ」が残されています。 また、次のように使った場合
\macro {ABC}delimiter{STU}{VWXYZ}
この展開結果は
引数は(ABC)()(STU)(VWXYZ)です。
となります。区切り文字列の手前の部分がすべて #1 として取り込まれてしまったので、#2 として一文字も取り込まないうちに区切り文字列を見つけてしまうために #2 は空になっています。
\def によるマクロ定義は、任意のコントロールシークエンスにマクロとしての新しい意味を割り当てるものだと見なすこともできます。コントロールシークエンスに意味を割り当てるもう一つの方法として、\let による代入があります。 \def によるマクロ定義の場合は、そのマクロの新しい意味を具体的に記述する必要があるのに対して、\let による代入は既存のコントロールシークエンスの意味をそのまま割り当てます。
\let\macroA=\macroB %(等号「=」は省略可能で、\let\macroA\macroB と書いてもよい)
と書くことで、その時点で \macroB が持っている意味を \macroA に「代入」することができます。これ以降、\macroA は \macroB と全く同じ機能を持つマクロとして使うことができます。
グルーピングにより,レジスタの値の変更やマクロ定義などが「ローカル」になります.
グルーピングを生み出す最も典型的な方法が
{<グルーピングの中身>}
のように“{”と“}”で囲むことです.(より正確には,カテゴリーコード1の文字と,カテゴリーコード2の文字で囲むことです.)この中でのマクロ定義などは基本的にローカルなものとなります.たとえば
\def\test{A} { \def\test{X} \test % -> Xと展開される } \test % -> Aと展開される
となります.“{”により新しいグループへと入り,その中で定義された \test は“}”によりグループを脱出した際にもとの定義に戻されます.
ただし,マクロ定義の際に \global が前置してある場合,その定義は「グローバル」になります.たとえば
\def\test{A} { \global\def\test{X} \test % -> Xと展開される } \test % -> Xと展開される
となります.\global\def の短縮形として \gdef が存在します.また,パラメータ \globaldefs が正の場合,全ての定義などはグローバルになります.(通常の状態では \globaldefs は0です.)
ローカルになるのは \def によるマクロ定義のみではなく,レジスタへの代入も含まれます.
\count0=1 { \count0=2 % ここでは\count0=2 } % ここでは\count0=1に戻る
ただし,\hyphenation のように常にグローバルになるようなものも存在します.
既に述べた通り,グルーピングを生み出す最も典型的な方法は“{”と“}”で囲むことですが,これはカテゴリーコードがそれぞれ1,2の文字ならば何でも良いですし,“{”に対して“}”が対応している必要もありません.たとえば
\def\test{A} { \catcode78=2 % N のカテゴリーコードを 2 に変更 \def\test{X} \test % -> Xと展開される N% グルーピングが終わる \test % -> Aと展開される N% グルーピングの終了とともに N のカテゴリーコードも戻るため,これは普通の N
とすることもできます.またこれらの文字に \let されているトークンでも同じです.plain TeX や LaTeX のデフォルトでは
\let\bgroup={ \let\egroup=}
とされているため,
\def\test{A} \bgroup \def\test{X} \test % -> Xと展開される \egroup \test % -> Aと展開される
というようにもできます.
プリミティブ \begingroup と \endgroup もグルーピングを発生させます.\begingroup で開いたグループは \endgroup で閉じる必要があります.グルーピングは入れ子になっている必要があるため,
{ \begingroup } \endgroup
とするとエラーになります.
グルーピングのもう一つの役割が,引数の範囲を示すことです.たとえば,LaTeX において定義されている \pagestyle マクロは
\pagestyle{plain}
のように使用されますが,ここでの {} によるグルーピングは \pagestyle マクロに与える引数を表します. このような引数の範囲を示すグルーピングには,多くの場合にカテゴリーコード1,2の文字そのものしか使えません.たとえば
\pagestyle\bgroup plain\egroup
とするとエラーになります. このことを逆に使うと,次のようなことが可能です.
\def\testA{\begingroup} \def\testB{\endgroup} \testA % ここは\begingroup,\endgroupによるグルーピングの内部 \testB
\begingroupや\endgroupの代わりに {} と書くと,\defの定義本体の終わり等を意味してしまうことに注意します. LaTeX における環境内はグルーピングされていますが,これは \begin/\end 内に \begingroup/\endgroup を仕込むことで行われています.
(マスターカウンタとバランスカウンタの話?)
TeX における条件分岐は,他のプログラミング言語の条件分岐とはまったく異なるものです。 先入観を持っていると混乱するでしょう。 多くのプログラミング言語では条件分岐のための if 文というものが用意されています。 if 文は,論理演算子を組み合わせた論理式の真偽値を算出して,それに応じて処理を分岐します。 ところが TeX には論理式はおろか論理演算子というようなものすらもありません。 TeX で分岐のために扱える条件は,現在の TeX の状態を問い合わせるものや,二つのトークンが同じものかどうか比較するものなど,ごく限られたものしかありません。
まず基本的な例を見てみましょう。
\ifmmode P\else $P$\fi
この例は,“P”という文字をいつでも数式イタリック体で印字しようというものです。 そのためには,TeX が現在数式モードにあるならそのまま P を印字させ,数式モードになっていなければ $ によって数式モードに移行してから P を印字させたあとで $ によってもとのモードに復帰させればよいでしょう。 さて上記の命令は TeX に次のようなトークンの列として読み込まれます。
[ifmmode],“P”,[else],“$”,“P”,“$”,[fi]
[ifmmode],[else],[fi]の三つのコントロールシークエンストークンは TeX のプリミティブ命令です。 まず[ifmmode]によって,TeXは現在のモードが数式モードであるかどうかを調べます(if math-mode という名前から推察されるとおりです)。 もしも数式モードならば,[else]や[fi]を見つけるまではそのまま処理を続けます。 この状態で[else]を見つけたならば,それ以降は[fi]を見つけるまでひとつひとつのトークンを読み飛ばしてゆきます。 [else]があってもなくても,[fi]を見つけたところでこの条件分岐は終了して,TeX はそのあとに続く処理を続行します。 “fi”というのは“if”の逆さつづりで,開き括弧と閉じ括弧が左右逆向きになっているのを連想させるようなしゃれでしょう。 fi という英単語があるというわけではないようです。 今度は,[ifmmode]のところで TeX の現在のモードが数式モードでなかった場合を考えましょう。 このとき TeX は[ifmmode]のあとに続くトークンを[else]や[fi]を見つけるまでひとつずつ読み飛ばしていきます。 この状態で[else]を見つけたならば,読み飛ばすのは止めにして[else]のあとに続くトークンに従って処理を続けてゆきます。 [else]があってもなくても,最終的に[fi]を見つけたところでやはりこの条件分岐は終了して,TeX はそのあとに続く処理を続行します。
TeX の条件分岐処理は,上の例で[ifmmode]が担っていた条件判断の部分に何種類かのプリミティブ命令が用意されているだけで,あとのトークンの読み飛ばし方や,[else]や[fi]の扱いについては同じです。 \ifmmode の代わりに使える条件分岐命令を列挙してみましょう。
\ifmmode | 現在のモードが数式モードかどうか |
\ifhmode | 現在のモードが水平モードかどうか |
\ifx<トークン1><トークン2> | <トークン1>と<トークン2>が同じトークンかどうか。(\ifxに続くトークンがマクロのように「展開」可能なトークンであっても、展開しないでそのまま比較します) - 文字トークンの場合は,文字コードもカテゴリーコードも一致するか - コントロールシークエンストークンの場合は,意味が一致するか(名前は違ってもよい) |
\if<文字トークン1><文字トークン2> | <文字トークン1>と<文字トークン2>の文字コードが一致するかどうか。(\ifに続くトークンが文字トークンではない場合は、可能な限り「展開」し続けてから比較します。) |
\ifcat<文字トークン1><文字トークン2> | <文字トークン1>と<文字トークン2>のカテゴリーコードが一致するかどうか。(\if と同様に、\ifcat に続くトークンも展開しつくされた後で比較されます。) |
\ifodd<数値> | <数値>が奇数かどうか |
\ifnum<数値に関する等式や不等式> | 等式や不等式が成り立つかどうか |
\ifdim<長さに関する等式や不等式> | 等式や不等式が成り立つかどうか |
\iftrue | 常に真と判断される |
\iffalse | 常に偽と判断される |
etc. |
各トークン列の実行は,原則として先頭から行われていきます。いくつかのプリミティブはこの展開順序を変更します.
\expandafterは,次に続くトークンの展開/実行を抑制し,その次のトークンを先に展開する,という働きをします.例を見てみます.
\def\testA{{A}{B}} \def\testB#1{[#1]}
まずは\testB\testAとした場合を考えてみましょう。まず\testBが展開されます。このマクロは引数を一つとり,今の場合は#1 = \testAですから,その展開結果は[\testA]となります。そして\testAが展開され,[{A}{B}]となります。最終的な出力は [AB] となります。
一方,\expandafter\testB\testAとしてみます。まず\expandafterが展開され,\testBの展開が抑制されます。その結果先に\testAが展開され,\testB{A}{B}となります。その後\testBが展開されます。今回は#1={A}となり,結局[A]Bと展開されます。
\expandafter自身も\expandafterの対象となります。例えば
\def\testA{{A}{B}} \def\testB#1{[#1]} \def\testC{\testB\testA} \expandafter\expandafter\testC
を考えてみます。まず最初の\expandafterにより次の\expandafterの展開が抑制され,その次の\testCが展開されます。その結果展開列は\expandafter\testB\testAとなります。上でみたように,全体の展開結果は[A]Bとなります。
いくつか例を見てみましょう。
\expandafter\def\expandafter\test\expandafter{\test,ABCDE}
最初の\expandafterで\defが抑制されます。その後二番目の\expandafterが展開され,\testの展開が抑制されます。更に{が抑制され,\testが展開されます。その後抑制されていた\def\test{が展開され,結局
\def\test{<\testの展開結果>,ABCDE}
となります。例えば \test をカンマ区切りのリストとして使っているとすると,最後に ABCDE を加えるという操作に相当します。
なお,\expandafter が一切ない \def\test{\test,ABCDE} だとどうなるでしょうか。これにより定義された\testを展開してみましょう.\test の展開結果は \test,ABCDE となります。展開可能なトークン\testが先頭にあるので,TeX は更にこれを展開しようとし,\test,ABCDE,ABCDEとなります。以下無限に続き,最終的に TeX は TeX capacity exceeded というエラーを出して終了してしまいます。
\def\test#1{\ifx#1.\else 0\expandafter\test\fi}
ピリオドで終わる文字列を与えると,その数だけ0を出力するマクロです.\test TeX. の展開の様子を見てみましょう.まず
\test TeX. → \ifx T.\else 0\expandafter\test\fi eX. → 0\expandafter\test\fi eX.
となります.ここで \expandafter により \test の展開が抑制され,先に \fi が展開された結果,0\test eX. となります.以下再帰的に文字数の分だけ 0 を出力します.ここでもし \expandafter がなければ,
0\test\fi eX.
の段階で「\test\fi」が展開され,
0\ifx\fi.\else 0\test\fi eX
となります.これは期待した結果ではないでしょう.(更にここから \test\fi が展開されていくため,最終的には TeX capacity exceeded により終了します.)
\edefは\defと同様にマクロを定義しますが,定義の際に置き換えテキストを可能な限り展開する点が異なります.例えば
\def\testA{test} \edef\testB{\testA}
とすると,\edefはまず置き換えテキストである\testAを展開します.その展開結果はtestですから,これは
\def\testB{test}
と同等の働きをします.
展開ができないプリミティブに関してはそのままになります.実行がされることもありません.例えば,
\edef\testA{\def\test{test}}
を考えてみます.\test は定義されていないとしましょう.置き換えテキスト「\def\test{test}」の展開は次のように続きます.
従って結局「Undefined control sequence.」のエラーとなります.
\edef での展開を抑制した時には \noexpand を使います.\edef における置き換えテキスト内での \noexpand は後続のトークンの展開を抑制します.例えば
\edef\testA{\def\noexpand\test{test}}
とすると,\test の展開が抑制されます.よって今度は「Undefined control sequence.」のエラーは出ず,
\def\testA{\def\test{test}}
としたのと同等の結果を得ます.
一つのトークンではなく,いくつかのトークンの列の展開を抑制するにはトークンレジスタを使います.\edef における置き換えテキスト内に\the<トークンレジスタ>が現れた場合,トークンレジスタの中身に展開され,それ以上展開されません.例えば
\def\testA{A} \def\testB{B} \toks0{\testB\testA} \edef\test{\testA\the\toks0}
とすると,\test は A\testB\testA に展開されます. なお,e-TeX には \unexpanded というプリミティブがあります.これは上のようなトークンレジスタによる展開の抑制と同じ働きをします.上のコードは
\edef\test{\testA\unexpanded{\testB\testA}}
と同じです.こちらの方が便利でしょう.
次のコードは,\arg の一回展開を \sample に渡すマクロ \test を作ります.
\toks0\expandafter{\arg} \edef\test{\noexpand\sample{\the\toks0}}
\unexpanded は展開されていきますので,\expandafter で一端抑制することもできます.それを踏まえると、上のコードは次と等価です.
\edef\test{\noexpand\sample{\expandafter\unexpanded\expandafter{\arg}}}
\csname ... \endcsnameは,その間に挟まれた文字列(を展開したもの)というコントロールシークエンスを実行します.たとえば
\small
と
\csname small\endcsname
は同等です.\csname と\endcsnameの間は展開されるので,
\def\@tempa{small} \csname\@tempa\endcsname
としても\smallと等価になります.さらに,
\csname \ifmmode csinmath\else csnotinmath\fi\endcsname
とすれば数式モードでは\csinmathに,非数式モードでは\csnotinmathと等価になります.
より正確に言えば,\csnameは展開可能なプリミティブで,一回展開により\endcsnameまでを読み取りその間(を展開した文字列)を名前に持つコントロールシークエンスに展開されます.従って,
\expandafter\def\csname somecs\endcsname{SOMECS}
は
\def\somecs{SOMECS}
と等価になります.
コントロールシークエンス名にはカテゴリーコードが11の文字しか使えませんが,\csname ... \endcsname内はその制限を受けません.たとえば
\csname first_and_second\endcsname
は[first_and_second]という名前のコントロールシークエンスに展開されます.ただし,このようなコントロールシークエンスを定義する際には\csname/\endcsnameを使い
\expandafter\def\csname first_and_second\endcsname{...}
として定義する必要があります.
TeX は、文字, ボックス, 罫線などの要素を紙面に配置するという組版のためのソフトウェアです。ここでは、TeX の組版動作と、関連する命令について解説します。
TeX の組版動作には 垂直モード, 内部垂直モード, 水平モード, 限定水平モード という4つのモードがあります。それぞれのモードで、文字やボックスなどの要素をどう配置していくかが変わります。二つの垂直モードはどちらも要素を垂直に上から下へと並べてゆくモードで、単に垂直モードと呼ばれているほうは決まった長さごとに区切ってページ分割を行いますが、内部垂直モードのほうは主に垂直ボックス(\vbox)の中でページ分割を入れずに並べてゆくだけのモードです。また、二つの水平モードはどちらも要素を水平に左から右へと並べてゆくモードで、単に水平モードと呼ばれているほうは決まった長さごとに区切って行分割を行い、限定水平モードのほうは主に水平ボックス(\hbox)の中で行分割せずに水平方向に並べていくだけのモードです。水平モードで動作しているときは必ず外側に垂直モードがあり、行分割された各行は一つの横長のボックスとして、外側の垂直モードの要素として垂直に積み下げられていきます。(説明図があると分かりやすそう)
TeX が動作を始めるときはまず垂直モードで始まり、ソースファイルからトークン列を読み込みながら読み込んだトークンに従って処理を進めていきます。垂直モードでは、処理する要素がボックスや罫線であれば次々に垂直方向に積み下げながら要素の高さや深さを積算していき、決まった長さになるとページ分割し、そこまでの要素をDVIファイルの1ページ分として出力するための outputルーチンを起動(\output というトークンリストレジスタに保存されたトークン列を命令として実行)します。垂直モードで要素を処理している際に「水平方向の要素」を見つけると、自動的に水平モードに移行して行の作成作業に入ります。「水平方向の要素」というのは、行中に配置するべきものと考えられる、文字トークンなどのことです。水平モードでは、文字や罫線を水平に並べながら文字幅を積算していき、決まった長さになると行分割し、一行文をボックスにまとめて外側の垂直ボックスに並べていきます。行分割しても段落が続く限り次の行を作り続け、段落終了を意味する \par 命令や空行を見つけると、段落最後の行を作って元の垂直モードに復帰します。
このように、各モードの切り替えは、\hbox や \par などのプリミティブで明示的に行う場合と、TeX が動作中に読み込む要素によって暗黙のうちに切り替わる場合があります。
垂直モード中でページ分割する長さは \vsize 、水平モード中に行分割する長さは \hsize という長さレジスタで決まります。ただし、配置する行はすべて同じ高さを持っているわけではないし、配置する文字もすべて同じ文字幅を持つわけではないので、ちょうど \vsize や \hsize の長さでページ分割や行分割ができるとは限りません。また、文中で行分割できる場所は、英文であれば単語間や単語中のハイフネーション可能なところに限定されるし、和文であっても行頭や行末に配置できない禁則文字によって制限されます。しかも、行分割やページ分割によってちょうど \vsize や \hsize の長さにならないページや行は、行間隔や文字間隔を詰めたり緩めたりして揃える必要もあります。ページ全体を見ながらバランスの良い行分割位置を決める職人芸を実現するために、TeXは何通りもの分割位置を検討し、それぞれのペナルティ値を算出して評価します。このような TeX の分割アルゴリズムについて、ここで詳しく解説されるのはまだ先のことになるでしょう。それまでは『TeXブック』を参照してください。
日本語の組版では、大雑把に言えば、どの文字も同じ大きさの正方形と見なして並べていけばよいですが、アルファベットなどの英文字は、文字によって幅が異なったり、背の高いものや下にはみ出したものなど様々で、文字の上下の端をそろえて並べるわけにもいきません。各文字は、ベースラインから上の部分の「高さ」と、ベースラインから下にはみ出した部分の「深さ」、そして「幅」を持っています。そして文字を通るベースラインの左端を「参照点」と呼びます。TeX では、各文字の「高さ」「深さ」「幅」の情報はフォントファイルに含まれており、TeX はその情報に基づいて文字をベースライン上に並べていきます。 また、単一の文字に限らず、文字・罫線・空白などを詰め込んだものをボックスとして一つの箱のように扱い、文字と同様にベースラインに並べることができます。ボックスにも文字と同じように「高さ」「深さ」「幅」「参照点」があります。 ボックスは、その内部が水平モードで組まれるか垂直モードで組まれるかに応じて「水平ボックス」または「垂直ボックス」と呼ばれます。明示的に水平ボックスを作るには \hbox{文字など、水平モードの材料} とします。hboxというのは horizontal box のことですね。同様に、垂直ボックスは \vbox{垂直モードの材料\par} となります。vertical box ですね。
垂直モードではボックスや罫線を垂直に積み下げていきます。配置されるボックスは主に文章中の1行で、その行に納められている文字と同じ高さと深さを持つだけなので、そのまま各行を垂直に並べていくと行と行がくっついて行間の隙間がなくなってしまいます。そこで、垂直モードでボックスを並べるときは \baselineskip, \lineskiplimit, \lineskip, \prevdepth という4つの長さレジスタに基づく大きさの空白が行間に挟まれます。行間の空白は次のように計算されます。垂直モードに新しいボックスが配置されるとき、まずその前の段階で直前のボックスが配置されたときにその深さが \prevdepth に記憶されています。そうして、新しく配置するボックスと直前のボックスの参照点が \baselineskip だけ離れるように、\baselineskip から \prevdepth と配置するボックスの高さを減じた大きさの空白を行間に挟んで新しい行を配置します。ただし、この空白量が小さすぎて行が近くなりすぎる場合には修正されます。修正はこの空白が \lineskiplimit よりも小さくなった場合に行われ、最初に計算した行間空白量は破棄して、代わりに \lineskip に設定された大きさの空白を行間に挟むというしくみです。
いい問題ができたら書き込みましょう。
問題によっては解答がコメントとして書かれており,上の「差分」から見ることができます。
1: \def\macro{% 2: \ifnum\count0<0% 3: \setbox0=\hbox{% 4: \count0=-\count0% 5: \the\count0% 6: }% 7: \else% 8: \setbox0=\hbox{% 9: \the\count0% 10: }% 11: \fi% 12: \box0% 13: }%
\par \hbox{あ} れ,なんで?
\fontscale{<ratio>}とすると「現在のフォントサイズの<ratio>倍」にサイズ変更し,また
\addfontsize{<dimen>}とすると「現在のフォントサイズに<dimen>を加えた」サイズに変更するというマクロを作成してみよ.
\chapter{イントロダクション}\label{intro}とすることによって,
\ref{intro}では通常の参照,そして
\titleref{intro}とすると,タイトル(ここでは「イントロダクション」という文字列)が出力されるようにしたいというようなことである. このような機構は一般にはクラスファイルに依存するが,ここではjsbook.clsを前提として\chapterのみを対象として実装してみよ. なお,実際は\section以下の階層に組み込むほうがクラスファイルに依存しない可能性は高いが,\@startsectionの実装そのものが比較的複雑なので,本質的ではない問題を避けるためにjsbook.clsの\chapterを前提とした.
2010-10-17 修正済み