[[TeX入門]] /
マクロの作成

----


* マクロの作成 [#nf5fd4d9]

TeX の特徴の一つにマクロが作成できることが挙げられます。
TeX マクロは実用的な面を持つだけではなく,知的な趣味としても愉しむことができます。
LaTeX で簡単なマクロを作成したい場合は [[LaTeX入門/LaTeXマクロの作成]] を参照してください。
こちらでは TeX に踏み込んだ解説を目的としています。

#contents

// 以下は手を付けていません。

* 基礎知識 [#s5fc1cf9]

マクロの作成技術を身につけるにあたって必要な基本知識を列挙しておきましょう。
これらの事柄を調べてゆくと自ずとマクロ作成技術が身に付くことでしょう。

参考書について:マクロを作成する上で必要となる TeX のプリミティブ命令の多くは,LaTeX の入門レベルの解説書では触れられていないことが多いです。
『TeXブック』には当然解説されていますが,日本語版の入手は困難です。
とりあえず,この Wiki の [[TeXの本]]の項で挙げられているもの(TeX by Topic や TeX Reference Manual が詳しいです)を参考になさってください.

- トークン (文字トークンとコントロールシークエンス(コントロールワード/コントロールシンボル))
- カテゴリーコード (\catcode, \makeatletter, \sanitize)
- 基本レジスタ (\count, \dimen, \skip/\muskip, \box, \toks, 数値と数字の違い)
- 定義と代入 (\def, \let, \newcommand, \DeclareRobustCommand)
- グルーピング ({, }, \bgroup, \egroup, \begingroup, \endgroup)
- 条件分岐 (\if, \ifx, などの各種 \if)
- 展開の制御 (\expandafter, \edef, \noexpand, \afterassignment)
// \aftergroup は有効な使用例が少ないため,保留(ただし,color パッケージで
// きわめて効果的に用いられています).
- トークンの制御 (\the, \string, \csname, 空白トークン)
- ファイル入出力(\openin/\closein, \read, \openout/\closeout, \write,
\immediate)
- デバッグ (\show, \showthe, \meaning, プリミティブとマクロ)
- 組版上の問題に由来する処理(水平モード/垂直モード, \par, \halign/\valign, \mark などの,
一般的なプログラミングの話では出てこない処理を扱うプリミティブ)
// これは,“基本知識”として挙げるべきかどうかは微妙.
// また,1 項目にしてしまうのは乱暴かもしれません.
- LaTeX における慣用的な処理(\@elt を用いたリストの処理や,
\@testopt 等の常用される内部処理用マクロの用法)
- そのほか融合問題,分類が困難な問題
// 問題カテゴリを追加してみました
- (あと,どうしましょう?)

* 解説 [#i84d9f01]
(注意)この節の内容には,初学者にとっての理解のしやすさを優先するため,不正確な記述が含まれています。従って,リファレンスマニュアルとしての使用に堪えるものとはなっていません。

** トークン [#t258c07c]
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文字を単独の文字トークンとして読み取る

ここで「英文字」とは何かという問題がありますが,これについては次項「[[カテゴリーコード>TeX入門/マクロの作成#mef7a198]]」を参照してください。
ただし,ここでは pTeX で使える日本語を含むコントロールシークエンストークンは考えません.

コントロールシークエンスには二種類存在します.
ひとつは上記の1のiに相当する,“\”のあとに英文字が続くものでこれをコントロールワードといいます.
例えば,“\alpha”によって生成される[alpha]はコントロールワードです.
もうひとつは上記の1のiiに相当する,“\”のあとに英文字以外の1文字が続くもので,これをコントロールシンボルといいます.
例えば,\!によって生成される[!]はコントロールシンボルです.

(注意)上の規則で“\”という文字を使いましたが,より正確には,カテゴリーコードが0である文字といったほうがよいでしょう。カテゴリーコードについては後の解説を参照してください。

*** トークンの区切りと空白文字の読み飛ばし [#ta414729]

さて,ここでコントロールワード[small]に続いて英文字の文字トークン“a”を置くことを考えて見ましょう。
本文中で小さな文字を使って“a”という活字を印字したいという場面です。

ソースファイルにはどう記述すべきでしょうか?
“\smalla”でしょうか?
これでは駄目ですね。
上記の規則 1のi によって続く限りの英文字が取られて[smalla]というコントロールワードになってしまい,意図とは違ってしまいます。
では英文字以外のものを“\small”の後におけばよいのですから“\small:a”などとしてみます。
今度は[small],“:”,“a”となってしまい間に余計な文字トークンが入ってしまいます。

このように,コントロールワードに続けて英文字トークンを置きたい場合にはなにか区切りになるものが必要ですが,その区切り自体が余計なトークンになってしまうなら,困る場合も出てきます。
そこで,区切り文字として空白文字を使った場合には例外的に,コントロールワードの名前の終わりを表す区切りとして認識したあとで,その空白文字を読み飛ばします。

この例外規則のおかげで,先ほどの問題は“\small a”と入力することで解決されるわけです。
この記述から生成されるトークンは,[small], “ ”, “a”ではなくて,[small], “a”となります。

空白文字の読み飛ばしについては,他にも考慮すべき例外ルールがあります。

** カテゴリーコード [#mef7a198]

前節で触れた,「英文字」とは何か,という問題に答えるのがこの節です。

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 による文字の分類を一覧表にしてみましょう。

|カテゴリーコード|分類|通常この分類に属する文字|h
|0|エスケープ文字|\|
|1|グループ開始文字|{|
|2|グループ終了文字|}|
|3|数式モードへの移行文字|$|
|4|アラインメントタブ|&|
|5|行の終了文字|文字コード13の文字|
|6|パラメータ文字|#|
|7|上付き文字|^|
|8|下付き文字|_|
|9|無視する文字|文字コード0の文字|
|10|空白文字|文字コード32の文字|
|11|英文字|AからZまでおよびaからzまで|
|12|その他の文字|!"'()*+,-./0123456789:;<=>?@[]`他1文字|
|13|アクティブ文字|&tilde;|
|14|コメント文字|%|
|15|無効文字|文字コード127の文字|

// 上の表は,plain TeX における設定に基づいたものと思われます.
// 一方,制御文字のカテゴリーコードについては,
// plain TeX での設定と LaTeX での設定の間に相違があります.
// LaTeX のデフォルトでは,文字コード 0 から 31 までおよび 127 の制御文字に
// ついては次の 4 個が例外で,それ以外はカテゴリーコード 15 となっています.
// 文字コード  9(horizontal tab,^^I): カテゴリーコード 10
// 文字コード 10(line feed,^^J):      カテゴリーコード 12
// 文字コード 12(form feed,^^L):      カテゴリーコード 13
//                                        \outer\def^^L{\par}
// 文字コード 13(carriage return,^^M):カテゴリーコード  5
// なお,これらの例外については,plain TeX でも同じ設定となっています.

TeX がテキストファイルから文字コードの羅列を読み込んでトークンに変換していくとき,カテゴリーコードが0か否か,および11か否かということは特別な意味を持ちます。
カテゴリーコードが0の文字はコントロールシークエンスの始まりを表し,その後にカテゴリーコード11の文字が続く限りそれがコントロールシークエンスの名前になるからです。
その他のカテゴリーの文字は単独の文字トークンとなるわけですが,このとき,その文字トークンには対応するカテゴリーコード付与されます。

***カテゴリーコードへのアクセス [#c853d199]

文字の分類を変えるには \catcode というプリミティブ命令を使います。
例えば“a”という文字の分類を「英文字」から「その他の文字」へ変更するには,“a”の文字コードが97で,「その他の文字」のカテゴリーコードが12なので
 \catcode97=12
のようにします。
このように変更した後では,例えば“\small”という文字列を TeX がソースファイルから読み込むとき,[small]というコントロールシークエンスではなくて,[sm]というコントロールシークエンスに続いて“a”, “l”, “l”という 3 個の文字トークンが続いたものとして読み取られることになります。

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

** 定義と代入 [#v38e683c]

マクロの定義のしかたです。
ここの解説を読む前に,トークンについて理解しておくとよいでしょう。
上のほうの解説がまだなら先にそちらをお読みください。

*** 引数を取らない簡単な置き換えマクロ [#e7dd9b15]

マクロ定義の基本は,引数を取らない簡単な置き換えマクロで,次のように定義します。
 \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 の動作を考えるときには、この“展開”と“実行”を区別することが肝要です。

*** 引数を取るマクロ [#ub81277c]

マクロには、実行時に引数に応じて動作を変えるようなものもあります。
多くのプログラミング言語では引数を指定するには括弧とカンマを用いて macro("test",2) のような具合に書きますが、TeX の場合は引数の与え方が全く異なるので、先入観を捨てておかないと理解しづらいでしょう。


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

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

** グルーピング [#cf2ee4db]

グルーピングにより,様々な物事が「ローカル」になります.グルーピングを生み出す最も典型的な方法が
  {<グルーピングの中身>}
と {} で囲むことです.(より正確には,カテゴリーコード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 のように常にグローバルになるようなものも存在します.

*** グルーピングを発生されるトークン [#n4769805]
*** グルーピングを発生させるトークン [#n4769805]
既に述べた通り,グルーピングを生み出す最も典型的な方法は {} で囲むことですが,これはカテゴリーコードがそれぞれ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

とするとエラーになります.

*** 引数の範囲を示すグルーピング [#yf516438]
グルーピングのもう一つの役割が,引数の範囲を示すことです.たとえば,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 を仕込むことで行われています.

(マスターカウンタとバランスカウンタの話?)


** 条件分岐 [#u303eedc]
TeX における条件分岐は,他のプログラミング言語の条件分岐とはまったく異なるものです。
先入観を持っていると混乱するでしょう。
多くのプログラミング言語では条件分岐のための if 文というものが用意されています。
if 文は,論理演算子を組み合わせた論理式の真偽値を算出して,それに応じて処理を分岐します。
ところが TeX には論理式はおろか論理演算子というようなものすらもありません。
TeX で分岐のために扱える条件は,現在の TeX の状態を問い合わせるものや,二つのトークンが同じものかどうか比較するものなど,ごく限られたものしかありません。
//(この段階では詳しすぎる内容のためコメントアウト)さらに,条件分岐の際に TeX がする動作というものは,条件に応じて処理を分岐するのではなく,条件に応じて実行する必要がなくなった部分を読み飛ばすに過ぎません。

まず基本的な例を見てみましょう。
//最初の例として \if を挙げないのは,トークンについての理解が不可欠なのと,他のプログラミング言語における if についての先入観を招きそうだと考えたためです。
 \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>が同じトークンかどうか。&br; - 文字トークンの場合は,文字コードもカテゴリーコードも一致するか&br; - コントロールシークエンストークンの場合は,意味が一致するか(名前は違ってもよい)|
|\if<文字トークン1><文字トークン2>|<文字トークン1>と<文字トークン2>の文字コードが一致するかどうか|
|\ifcat<文字トークン1><文字トークン2>|<文字トークン1>と<文字トークン2>のカテゴリーコードが一致するかどうか|
|\ifodd<数値>|<数値>が奇数かどうか|
|\iftrue|常に真と判断される|
|\iffalse|常に偽と判断される|
|etc.||

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

*** \expandafter [#nf0b9376]
\expandafterは,次に続くトークンの展開/実行を抑制し,その次のトークンを先に展開する,という働きをします.例を見てみます.

 \def\testA{{A}{B}}
 \def\testB#1{[#1]}

まずは\testB\testAとした場合を考えてみましょう。まず\testBが展開されます。このマクロは引数を一つとり,今の場合は#1 = \testAですから,その展開結果は[\testA]となります。そして\testAが展開され,[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,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 [#g0902f79]
\edefは\defと同様にマクロを定義しますが,定義の際に置き換えテキストを可能な限り展開する点が異なります.例えば
 \def\testA{test}
 \edef\testB{\testA}
とすると,\edefはまず置き換えテキストである\testAを展開します.その展開結果はtestですから,これは
 \def\testB{test}
と同等の働きをします.

展開ができないプリミティブに関してはそのままになります.実行がされることもありません.例えば,
 \edef\testA{\def\test{test}}
を考えてみます.\test は定義されていないとしましょう.置き換えテキスト「\def\test{test}」の展開は次のように続きます.
- \defは展開できないのでそのままです.
- \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}}
次と同じです.
 \edef\test{\noexpand\sample{\expandafter\unexpanded\expandafter{\arg}}}

* 基本練習問題 [#l9a8a164]
// いかがでしょうか。 学習効果が高い,しゃれた問題を作るのは大変そう。
// 『TeXbook』を読めばいいってのは言いっこなし(^^;)
いい問題ができたら書き込みましょう。
// あと,“実際に処理させてみればわかる”という問題以外は“解答”も
// コメントとして入れておいてはどうでしょう.

// 以下の問題では,特に断らない限り,
// - 個々の文字のカテゴリーコードは plain TeX におけるデフォルトの値である
// - 個々の文字の \lccode/\uccode の値は plain TeX におけるデフォルトの値である
//
// ものと仮定します.
// // 一応,上記の断り書きは入れておいたほうがよろしいのでは?

////// ごもっともです。
////// 入門者向けということで,デフォルトのカテゴリーコードになっていない可能性が
////// あるなどとはつゆほども思わない読者を想定していました。断り書きがなければ
////// 欠陥問題じゃないかと思うような読者にはぜひ執筆側に回ってもらいましょう(^^)

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

** トークンの読み込み [#r9336262]
+あるユーザが 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) 1,3,6,8,10 行目の %
// (2) 2,5,7,9,11,13 行目の %
// (3) 4,12 行目の %
// 実際,“数値・寸法として読み取られる文字列”の直後の(多くとも 1 個の)
// 空白文字は“数値・寸法の終端”を表すものとして吸収される.
// また,コントロール・ワードを読み取る際にも,その直後の空白文字は
// 単にそのコントロール・ワードの終端を示すものとして無視される.
//
// そのため,2,4,5,7,9,11,12 行目の %((2),(3)に該当するもののうち,
// 13 行目のもの以外)は(1)には該当しないとわかる.
// 残りのもののうち,13 行目の % は \macro の定義が済んだ後にあり,
// \macro の挙動には関係しない(つまり(2)に該当).
// // なお,13 行目の % がなければ 13 行目の行末に由来する
// // 空白が入るが,このマクロ定義はプリアンブル(すなわち,垂直モードの箇所)で
// // 行われているため,この空白は何の影響ももたない.
// さらに残りのもの(つまり,1,3,6,8,10 行目の %)は,
// それらがなければ“{”,“}”の直後という吸収されない箇所に空白が入り,
// しかも,それは \macro の置換テキスト内に入るため \macro を用いた際に
// 出力される可能性がある.
// したがって,1,3,6,8,10 行目の % は(1)に該当する.
//
// さて,先に“2,4,5,7,9,11,12 行目の % は(1)には該当しない”ことをみたが,
// それらのうち,7,11 行目の % はコントロール・ワードを読み取った直後となり,
// (2)に該当する.
// 残る 2,4,5,9,12 行目の % は“数値として読み取られる文字列”の直後にあり,
// それらの % があると“数値の終端”を探して後続のトークンを(必要があれば,
// マクロなどを展開しながら)調べ続ける.
// ただ,2,5,9 行目の場合,それらの次の行の先頭には
// \setbox(これは,展開されないプリミティブ)あるいは数字としては読めない
// トークン“}”があるため,2,5,9 行目の末尾の 0 はその直後に % が
// あったとしても数値 0 として確定する
// (したがって,2,5,9 行目の % は(2)に該当).
// 残る 4 行目と 12 行目の % については,個別に考えてみる.
//
// ・4 行目の % について:
// \count0=-\count0% の % によって \count0 の後の“数値の終端”を
// 欠いているため,代入の右辺の \count レジスタの番号は
// その直後にある \the\count0 を展開して得られる文字列を読まないと確定しない.
// そのため,\box0 の中身になるはずの \the\count0 の部分がその直前にある
// \count0=-\count0 の代入を行う前に展開されてしまう.
// 例えば,問題文にあるように \macro を定義した状態で“\count0=-1 [\macro]”を
// 処理してみると,“[-1]”という出力が得られる.
// 一方,4 行目のの % がない場合には,“\count0=-1 [\macro]”に対する出力は
// “[1]”となる.
// 実際,4 行目の末尾の(行末由来の)空白のところでその代入の右辺は確定するため,
// “\count0 の符号を反転させてから,\count0 の値を\the で文字列化”している.
// 結局,4行目の % があると“行末のコメントアウト”というだけでない影響があり,
// (3)に該当する.
//
// ・12 行目の % について:
// 定義の末尾の \box0% のところも,\box0 の直後に空白文字などの
// “数値の終端”を欠いているため,“\macro 1”のような記述を行うと
// \macro に続く数字列まで \box レジスタの番号として読み取ってしまい,
// (おそらく)意図通りではない出力が得られる.
// 一方,12 行目の % がない場合には,(4 行目の場合と同様に)出力する
// \box レジスタの番号は“0”と確定する.
// 結局,12 行目の % があると“行末のコメントアウト”というだけでない影響があり,
// (3)に該当する.
//
// [補足 1]
// 上述のとおり 13 行目の % はもともと \macro の挙動には関係しない.
// また,7,11 行目の % は,それがあろうとなかろうと \macro の
// 定義自体が変わらない.
// 残りの % については,それらの有無に応じて \macro の定義中の空白の
// 有無は変わってくるが,\macro の使用時の挙動を考えると上記のような結果となる.
//
// [補足 2]
// 今の例の \macro の定義の末尾に \def\macro{... \box0 } のごとく
// 明示的に空白文字を書き込むのを好まなければ,
// \def\macro{... \box\z@} のごとく 0 の別名を用いるのもよい
// (0 の代わりに \z@ を用いるのにはこのような積極的な意味がある場合も
// あることに注意).
//
// // 整理前の記述を保存
// +余分な空白を嫌って全ての行末に``%''が付加されているが,不要なものもある。どれか。
//  \def\macro{%
//      \ifnum\count0<0%
//          \setbox0=\hbox{%
//              \count0=-\count0%
//              \the\count0%
//          }%
//      \else%
//          \setbox0=\hbox{%
//              \the\count0%
//          }%
//      \fi%
//      \box0%
//  }%
// // ``0''の直後(五ヶ所)
// // (この例の定義中の 0 はいずれも“数値として”読み取られるので,
// // それに続く(多くとも 1 個の)空白文字は吸収される.)
// // コントロールワードの直後(``\else'', ``\fi'')
// // 最後の``}''の直後(このマクロの定義が水平モードで行われない場合)
// +前問の \macro の定義中の % のうち,単に不要というだけでなく
// “有害”であるものはどれか(前問では“あってもなくても構わない %”と
// “有害な %”の両方を尋ねているが,本問では特に“有害”なもののみを尋ねている).
// なお,本問においては“有害な %”とは
// >その % の有無によって単なる“空白トークンの有無”''以外''の相違が生じる可能性があるような %
// <のことを指す.
// // 保存部分終わり
/// “行末%”に関するはじめの問題を書いたものです。二ヶ所の有害な%については
/// 想定外でした。有害であることに気付いていなかったので問題文が不適切でしたが,
/// 図らずも練習問題に含める失敗例としては面白い要素になっていたようですね。
/// 上手く発展させていただけたようで,ありがとうございます。
/// いっそ二つの問題を統合してしまえればすっきりとしそうです。
// とりあえずまとめてみましたが,いかがでしょうか.

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

** 基本レジスタ [#o2f1fc77]

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

**グルーピング [#i714208c]

** 条件分岐 [#eb863be6]

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

**デバッグ [#me44b82b]

** 組版上の問題に由来する処理 [#o0e908b4]
+次のように書いたときに「あ」だけの行ができるのはなぜか? また,どうすれば「あれ,なんで?」と一行にできるか?
 \par
 \hbox{あ}
 れ,なんで?
// \par によって垂直モードに移行し,次の \hbox{あ} はそのまま垂直モードで
// 処理されていることによる.ここでは,\leavevmode を \hbox{あ} の
// 直前に置いて明示的に水平モードに移行すれば,“あ”のみの行ができることは
// 回避される(もっとも,“\hbox{あ}れ,…”ではなく単に“あれ,…”
// としてもこの問題の文字列に関しては解決するが,これは出題意図から
// 外れているだろう).
// 確かに意図とは違いますが,正当な理解に基づいての\hboxを外すという解は
// 正解ですね.ですので最初に「なぜか」という問いかけがあります
+あるユーザが今何ページであるかを本文中に書くために\thepageを記述した.ところが,正しくノンブルを拾えているところと,そうではないところが発生してしまった.これはなぜか(正しく拾えているところとそうではないところに何らかの傾向があるはずである).
+ ページごとに脚注をリセットさせるという流儀がある.しかも,脚注が一つしかない場合は脚注のマーク(例えば,\dagger)のみで,複数ある場合は,脚注のマークに番号をつける(例えば,\dagger1,\dagger2,・・・)としたい.どうすればよいか?

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


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

* コメント [#f7eca8fe]

- 「入門」の一項目にするのではなく,別途「マクロの作成・応用」のようなページにされた方が良いように思いますが,いかがでしょうか。 この内容は「入門」のレベルを逸脱しているように思いましたので -- トニイ 2006/06/16
- [北見 けん] まあ,もう少しコンテンツが充実したら構成を見直すということでよろしいかと思います。 まずプリミティブを把握しておけば LaTeX などのマクロは自分で読めるようになるという意味では,入門としてはプリミティブの範囲に収まるものとしてもよいような気もします。 -- 北見 けん 2006/06/16 10:43
- このページの構成自体についてもいろいろご意見があるようなので,コメント欄を追加してみました。ついでに勝手ながら過去のコメントを表に出してみました。-- 北見 けん 2006/06/23
- とりあえず「タネ」を充実させてからでどうでしょう.何問か書きましたが,一日も経たずに解答がついてますね. -- 本田 &new{2006-06-24 (土) 10:53:29};
- 私の存在自体がお気に召さない人間が存在するようですので,私が作成した部分については撤去します. -- しっぽ愛好家 &new{2007-09-06 (木) 21:20:44};
- うーん。とても残念です。-- 北見 けん 2007-10-18 (木) 22:10:00 ← 手動タイムスタンプです
- そうおっしゃられるといささか心苦しいのですが,私としては「初心者の味方」気取りの連中に勝手なマネをされても黙っているわけではないことを行動で示す必要がありましたので.私が書いたものがないことで何か不都合があれば,「初心者の味方」気取りの連中(例えば「自分ではロクに回答しないにもかかわらず回答者つぶしをやる」ような人間)に埋めさせるのがよろしいでしょう. -- しっぽ愛好家 &new{2007-11-10 (土) 17:32:52};
- カテゴリ表の7,8の記号欄が逆になってます。日本語の説明の方が正しいです。 -- 斎藤 &new{2010-10-16 (土) 16:38:21};
>2010-10-17 修正済み

- #を含むファイル名が読めるようになりました。たすかりました。\catcode35=12としてからincludegraphics。後で\catcode35=6に戻す -- 斎藤 &new{2010-10-16 (土) 21:56:25};

#comment