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”であると考えます)。

文字コードの羅列をトークンに変換してゆくプロセスは概ね次の規則に従います。

  1. “\” があれば続く文字列から名前を読み取ってコントロールシークエンスに変換する。この際
    1. “\”に続くのが英文字であれば,さらに続く英文字を取れるだけとったものをそのコントロールシークエンスの名前とする
    2. “\” に続くのが英文字でなければ,“\” の後に続くその1文字をそのコントロールシークエンスの名前とする
  2. “\” でなければ,その1文字を単独の文字トークンとして読み取る

ここで「英文字」とは何かという問題がありますが,これについては次項「カテゴリーコード」を参照してください。 ただし,ここでは 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 個の文字トークンが続いたものとして読み取られることになります。

(今日はとりあえずここまで。続きはいつになるか分かりません。)

基本レジスタ

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 となります。

数値の表現

(ちょっと説明が面倒なので、保留 → どなたかへ)

レジスタの演算

演算記し方
加法\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 の場合は引数の与え方が全く異なるので、先入観を捨てておかないと理解しづらいでしょう。

TeX で単純な引数を取るマクロを定義するには、次のように記述します。

\def<トークン>#1#2#3{置き換えテキスト ― この中で仮引数として #1,#2,...,#9 が使える}

ここで,<トークン>は定義しようとするコントロールシークエンスかアクティブ文字です。続く #1#2#3 の部分はいくつの引数を取るかを示すもので、#9 までの 9個以内で必要な分だけつなげて書きます。 引数の番号は1から順に書いていきます。上の例は 3個の引数を取る場合です。 マクロが展開されるときには、ブレイス{}で囲まれたテキストか、そうでなければトークンが引数として読み込まれて、置き換えテキストの中の #1 などのところにはめ込まれます。

例えば、 (どなたかお願いします。)

グルーピング

グルーピングにより,様々な物事が「ローカル」になります.グルーピングを生み出す最も典型的な方法が

 {<グルーピングの中身>}

と {} で囲むことです.(より正確には,カテゴリーコード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>が同じトークンかどうか。
- 文字トークンの場合は,文字コードもカテゴリーコードも一致するか
- コントロールシークエンストークンの場合は,意味が一致するか(名前は違ってもよい)
\if<文字トークン1><文字トークン2><文字トークン1>と<文字トークン2>の文字コードが一致するかどうか
\ifcat<文字トークン1><文字トークン2><文字トークン1>と<文字トークン2>のカテゴリーコードが一致するかどうか
\ifodd<数値><数値>が奇数かどうか
\ifnum<数値に関する等式や不等式>等式や不等式が成り立つかどうか
\ifdim<長さに関する等式や不等式>等式や不等式が成り立つかどうか
\iftrue常に真と判断される
\iffalse常に偽と判断される
etc.

展開の制御

各トークン列の実行は,原則として先頭から行われていきます。いくつかのプリミティブはこの展開順序を変更します.

\expandafter

\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

\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

\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{...}

として定義する必要があります.

基本練習問題

いい問題ができたら書き込みましょう。

問題によっては解答がコメントとして書かれており,上の「差分」から見ることができます。

トークンの読み込み

  1. あるユーザが LaTeX 文書のプリアンブルでマクロ \macro を下記のように定義した(ただし,各行の先頭の行番号+コロンは解答の便宜のために付したもの). このユーザは余分な空白を嫌って全ての行末に``%''を付加したのだが,この定義中の各行末の % のうち,次の(1)〜(3)の各々に該当するものはどれか.
    (1) その % がなければ,\macro の使用時に行末に由来する空白が出力される可能性があるもの
    (2) その % があってもなくても \macro の挙動は変わらないもの
    (3) その % がなくても余分な空白は入らず,逆にその % があると“行末をコメントアウト”というだけにとどまらない影響があるもの
     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: }%

カテゴリーコード

  1. “i”のカテゴリーコードが 0 で,“ ”(空白文字)のカテゴリーコードが 11 である場合,\macro This is a test. はどのようにトークン化されるか?
  2. ファイル latex.ltx における \strip@pt の定義を解析し,このマクロの内部処理で用いられる \rem@pt の定義を単に\def\rem@pt#1.#2pt{#1\ifnum#2>\z@ .#2\fi} としたのではうまくいかない理由を述べよ.
  3. TeX Q & A 掲示板の 17370 で提起された問題を解いてみよ.ヒント: latex.ltx における \@sanitize を利用するとよい.

基本レジスタ

定義と代入

  1. \def\macro#1#2a#3.{#1;#2;#3;} と定義したとき, \macro This is a test. の展開結果はどうなるか? \def\macro#1a#2#3.{#1;#2;#3;} だとしたらどうか?
  2. latex.ltx内で定義されているマクロ\@defaultunitsの使い方とその効果を述べ,定義を解析せよ.
  3. LaTeX2eにはカウンタに親子関係を追加する\@addtoresetというマクロがある.では,この逆,「縁切り」のマクロ,つまり,<cntA>が子供<cntA1>,<cntA2>,・・・,<cntAn>をもっているときに,<cntAi>(i=1,2,...,n)を<cntA>の子供ではなくするマクロを実装せよ.
  4. アルファベットからなる文字列の先頭の文字のみを大文字にして出力するマクロを以下の条件で作成せよ.
    (1) アクセント記号はどこにもつかないことを前提とする
    (2) 先頭の文字にアクセント記号がつくことも許容する

グルーピング

条件分岐

展開の制御

  1. \def\A#1,#2{#2#1} \def\B#1;#2{#1,#2} \def\C{x;y} と定義されているとき,\A\B\C に\expandafter を適宜挿入して,展開結果が yx となるようにせよ。

デバッグ

組版上の問題に由来する処理

  1. 次のように書いたときに「あ」だけの行ができるのはなぜか? また,どうすれば「あれ,なんで?」と一行にできるか?
    \par
    \hbox{あ}
    れ,なんで?
  2. あるユーザが今何ページであるかを本文中に書くために\thepageを記述した.ところが,正しくノンブルを拾えているところと,そうではないところが発生してしまった.これはなぜか(正しく拾えているところとそうではないところに何らかの傾向があるはずである).
  3. ページごとに脚注をリセットさせるという流儀がある.しかも,脚注が一つしかない場合は脚注のマーク(例えば,\dagger)のみで,複数ある場合は,脚注のマークに番号をつける(例えば,\dagger1,\dagger2,・・・)としたい.どうすればよいか?

フォントの取り扱い

  1. LaTeX2eではフォントの絶対的なサイズは指定できるが,相対的なサイズを指定するマクロはデフォルトではない.そこで,
    \fontscale{<ratio>}
    とすると「現在のフォントサイズの<ratio>倍」にサイズ変更し,また
    \addfontsize{<dimen>}
    とすると「現在のフォントサイズに<dimen>を加えた」サイズに変更するというマクロを作成してみよ.
  2. TeXでは欧文は自動的にハイフネーションされる.しかし,ハイフネーションしてもハイフンを表示させたくない(つまり和文のように「普通に」改行しているように見せたい)こともある(例えばURLの表記). どのようにすればこのようなことができるか? なお,TeXを内部で使っているASCIIのEWBを使ったと明示されている書籍で,このような処理を行っていると思われるものは存在する(例:Petzold「プログラミングWindows第五版).

相互参照

  1. LaTeX2eでは節番号などを\label/\refによって参照することができる.だが書籍によっては番号だけではなく,例えば章のタイトルも参照したいことがある.例えば
    \chapter{イントロダクション}\label{intro}
    とすることによって,
    \ref{intro}
    では通常の参照,そして
    \titleref{intro}
    とすると,タイトル(ここでは「イントロダクション」という文字列)が出力されるようにしたいというようなことである. このような機構は一般にはクラスファイルに依存するが,ここではjsbook.clsを前提として\chapterのみを対象として実装してみよ. なお,実際は\section以下の階層に組み込むほうがクラスファイルに依存しない可能性は高いが,\@startsectionの実装そのものが比較的複雑なので,本質的ではない問題を避けるためにjsbook.clsの\chapterを前提とした.

コメント



Last-modified: 2018-10-14 (日) 18:41:34 (62d)