* 基礎知識 [#s5fc1cf9]

TeX の特徴の一つにマクロが作成できることが挙げられます。
マクロを活用することによって、ミスの少ない仕上がりや作業効率の向上などの効果が期待できます。
また、実用的な面を持つだけではなく、知的な趣味としても愉しむことができます。

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

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

- トークン (文字トークンとコントロールシークエンス(コントロールワード/コントロールシンボル))
- カテゴリーコード (\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 等の常用される内部処理用マクロの用法)
- そのほか融合問題,分類が困難な問題
// 問題カテゴリを追加してみました
- (あと、どうしましょう?)

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

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

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

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

** カテゴリーコード [#gfea22ef]
+ “i”のカテゴリーコードが 0 で、“ ”(空白文字)のカテゴリーコードが 11 である場合、\macro This is a test. はどのようにトークン化されるか?
// \relax の代わりに [relax] と書いて,個々のコントロール・ワードを
// 明示することにすると,この問題の \macro This is a test. は
// [macro Th][s ][s a test].
// (最後のピリオドのみ,文字トークン)のように扱われる(ただし,問題文中で
// 断ってある i と空白文字以外の文字については,カテゴリーコードの変更は
// なされていないものと仮定).
+ ファイル latex.ltx における \strip@pt の定義を解析し,
このマクロの内部処理で用いられる \rem@pt の定義を単に
\def\rem@pt#1.#2pt{#1\ifnum#2>\z@ .#2\fi} としたのでは
うまくいかない理由を述べよ.
// \rem@pt では“\the + \dimen レジスタ”が与える文字列の末尾の“p_{12}t_{12}”
// を取り除くことになるので,通常の文字列“p_{11}t_{11}”を書式指定文字列に
// したのではパターンマッチが意図通りには行われない.

** 基本レジスタ [#o2f1fc77]
+ 整数型の数値の四則演算を行う方法を挙げよ.
実数型の数値の場合はどうか.
// 実数型の数値の乗除算以外については,\advance,\multiply,\divide という
// プリミティブを用いてできる(整数については \count レジスタを用い,
// 実数型の数値に対しては \dimen レジスタを用いる).
// 実数型の数値の乗算は,\dimen@=2.72\p@ \dimen@=3.14\dimen@
// (この時点で,\dimen@ の値は 2.72 * 3.14 pt となる)のように
// “係数”を使えばいい.
// 実数型の数値の除算は,整数の除算に帰着させる.
// \dimen0 = x [pt],\dimen2 = y (≠ 0) [pt] のとき,
// 単に \divide\dimen0 by \dimen2 とすると \dimen0 の値は x/y [sp]
// (しかも,小数点以下を切り捨てた値)となってしまうが,
// 何らかの形で 65536 x/y [sp] = x/y [pt] が得られればよい.
// 例えば,\multiply\dimen0 by 256 \divide\dimen2 by 256 としてから
// \divide\dimien0 by \dimen2 とすると \dimen0 の値は
// (256x)/(y/256) = 65536 x/y [sp] = x/y [pt] となる.
// (ただし,あくまで整数演算なので,分母は正確に y/256 になるとは限らず,
// その分の狂いが生じる.それを気にするような用途には,後述するような
// 正確な計算を用いることになる.)
// もちろん,『LaTeX 自由自在』で例示されているように,
// \count0 = \dimen0 のようにいったん \count レジスタに代入したのち,
// 整数の除算を用いて計算してもいい(もちろん,小数点以下の部分を
// 計算するには,“余りを 10 倍したのち除数で割る”操作を繰り返せばいい).
+ 以下の操作を行ったときの \skip0,\skip2 の最終的な値はどうなるか.
 \skip0=10pt plus 5pt minus 3pt
 \skip2=2\skip0
 \multiply\skip0 by 2
// 最終的には,\skip0 = 20pt plus 10pt minus 6pt,\skip2 = 20pt となる.
// “係数”をつけた場合には伸縮度が失われることに注意.
// 問: \skip レジスタの値を伸縮度も含めて実数倍するマクロを作成せよ.
+ \macroA,\macroB は引数をとらないマクロであるものとするとき,
 \edef\macro{\macroA\macroB}
と
 \toks@\expandafter{\macroA}
 \@temptokena\expandafter{\macroB}
 \edef\macro{\the\toks@\the\@temptokena}
はどのように違うか(ただし,\toks@,\@temptokena は LaTeX がすでに
用意している \toks レジスタ)?
また,後者の処理を \toks レジスタを用いずに実現するにはどうすればよいか.
// \toks レジスタを用いないほうでは,\macroA,\macroB に含まれるマクロなどは
// 展開され,条件処理も行われてしまうが,\toks レジスタを用いたほうでは
// \macroA,\macroB の置換テキストが単純に連結される.
// 例えば,\macroA,\macroB が
// \def\macroA{[\the\year]}
// \def\macroB{\ifnum\year>10000 ???\else OK\fi}
// のように定義されているとき,(この記述を行っている時点 2006 年では)
// \edef\macro{\macroA\macroB} は \def\macro{[2006]OK} と定義したのと
// 同じことになる一方,\toks レジスタを用いたほうは
// \def\macro{[\the\year]\ifnum\year>10000 ???\else OK\fi}
// と定義したのと同じことになる.
// [注] \the + \toks レジスタが与えるトークン列は \edef での置換テキスト
// 作成時には基本的にはそれ以上展開されないが,数値の読み取りなどの都合で
// 展開せざるを得ない場合には展開されてしまう.
// そのため,今の例でも \toks@ と \@temptokena の代わりに \toks0 と \toks2 を
// 用いた上で \edef\macro{\the\toks0\the\toks2} とはしていない.
// もっとも,\edef\macro{\the\toks0 \the\toks2} とすれば問題ない.
//
// また,\toks レジスタを用いたのと同じ,置換テキストの単純な連結は
// \expandafter\expandafter\expandafter\def
// \expandafter\expandafter\expandafter\macro
// \expandafter\expandafter\expandafter{\expandafter\macroA\macroB}
// という記述でも可能.
+ 次の記述を (La)TeX で処理すると,どのような出力が得られるか?
 \setbox0\hbox to10mm{X\hfil Y}
 \hbox to40mm{\copy0 \hfil \copy0}
 \hbox to40mm{\unhcopy0 \hfil \unhcopy0}
 \hbox to40mm{\box0 \hfil \box0}
 \setbox0\hbox to10mm{X\hfil Y}
 \hbox to40mm{\unhbox0 \hfil \unhbox0}
// 問: 不特定多数の整数や寸法を必要とする処理は,それらを文字列で
// 表したものを適当なマクロに保存すれば実現できる.
// では,不特定多数のボックスを必要とする処理を実現するには
// どうすればよいか.

** 定義と代入 [#gcea75b2]
+ \def\macro#1#2a#3.{#1;#2;#3;} と定義したとき、 \macro This is a test. の展開結果はどうなるか? \def\macro#1a#2#3.{#1;#2;#3;} だとしたらどうか?
+ あるユーザが,次のような記述を行った(\@firstoftwo,\@secondoftwo
については,必要があればファイル latex.ltx を参照のこと).
 \documentclass{article}
 \begin{document}
 \makeatletter
 \def\issubstring#1#2{%
    \def\@tempa##1#1##2\@nil{\def\@tempa{##2}}%
    \@tempa#2#1\@nil
    \ifx\@tempa\@empty \expandafter\@secondoftwo
    \else              \expandafter\@firstoftwo
    \fi}
 \makeatother
 \def\test#1#2{\issubstring{#1}{#2}%
    {``#1'' is contained in ``#2''.}%
    {``#1'' is not contained in ``#2''.}}
 \test{cat}{dog}
 \end{document}
// つまり,
// \issubstring は第 1 引数が第 2 引数の部分文字列であれば第 3 引数を実行し,
// そうでなければ第 4 引数を実行するようなマクロというつもりである.
実際,上記の \test{cat}{dog} の出力は意図通りのものであった.
問: \issubstring はどのようなマクロで,
\test{cat}{dog} の出力はどうなっていたのか?~
しかし,\test{aa}{a} を処理させるととんでもない結果が得られ,
このユーザは頭を抱えた.
// 実際,“aa”が“a”の部分文字列と判定されてしまったのだが,
問: いったいどのような結果が得られたのか?
また,\issubstring の定義のどこに問題があり,どう修正すべきであったか?
// \@tempa#2#1\@nil の行が問題.
// 実際,\issubstring の第 1 引数が ABA の形で,第 2 引数は AB の形である
// 場合(e.g. \issubstring{cat-cat}{cat-} などの場合)には,
// 第 2 引数と第 1 引数の先頭部分がつながって“意図しない位置に”\@tempa の
// 書式指定文字列が生じてしまう.
// ここでは,\@tempa#2\@empty#1\@nil のように \@empty を入れればよい.
// なお,\@empty も \issubstring の引数として用いられかねないような状況では,
// \@empty の代わりに %_{12} あるいは ^^@_{12} あたりを用いればよいだろう.
// [余談] LaTeX の内部処理用マクロ \in@ にもまったく同じ問題があるので,
// ユーザ自身が \in@ を用いる場合には注意が必要.
+latex.ltx内で定義されているマクロ\@defaultunitsの使い方とその効果を述べ,定義を解析せよ.
// 文法上は \@defaultunits <たいていの代入操作><ごみ(存在しなくてもいい)>\@nnil
// という形式で用いることができるが,使用例は
//   \def\setparindent#1{\@defaultunits\parindent#1pt\relax\@nnil}
// のような具合に
//   \@defaultunits<寸法を代入できるもの><寸法または実数><単位>\relax\@nnil
// となっていることが大半(というより,そのような用法を念頭に置いていると
// 思われる).ここで,\relax は“代入に伴って,単位の後に続くマクロが
// 不用意に展開される”ことを防ぐために必要であることに注意.
// なお,今例示した \setparindent は引数として寸法または実数値
//(を 10 進表記した文字列)をとり,引数が寸法であるときには
// \parindent をその引数に設定し,数値であるときには単位 pt を補ってできる
// 寸法を \parident に代入するマクロ.\@defaultunits の効果(として
// 念頭に置いていると思われるもの)はこの例のように“必要に応じて
// デフォルトの単位を補うような”寸法の代入ができる,ということである.
// 問: \@defaultunits の文法上の書式の中で <たいていの代入操作> と
// 書いているように,\@defaultunits の直後では使えないような代入操作も
// 存在する.そのような操作の例を挙げよ(cf. “The TeXbook”の第 24 章に
// おける \afterassignment の説明).
+ 2 個のマクロ \macroA,\macroB の定義を入れ換える際に,
 \def\temp{\macroA}
 \def\macroA{\macroB}
 \def\macroB{\temp}
という記述と
 \let\temp\macroA
 \let\macroA\macroB
 \let\macroB\temp
という記述のどちらが意図通りに処理されるか?
また,意図通りに処理されないほうはどのような結果になるか.
// // \def と \let の相違を確認させる問題で,もっと気の利いた問題が
// // ありましたら,ぜひこの問題と差し替えてください(出題者より).
// 意図通りに処理されるのは後者.
// 前者では \macroA,\macroB のどちらを用いても無限ループに陥る(か,
// \macroA,\macroB を用いるまでに \temp が再定義されていたら
// “無関係なもの”が実行されてしまう).
// 実際,\temp が再定義されなかった場合について展開過程を
// 考えてみると,\macroA → \macroB → \temp → \macroA(以下,
// 繰り返しになる)のようになっている.

**グルーピング [#i714208c]
+ 何らかの処理をグループ内でローカルに行う場合,
ときとして処理結果をグループの外に運び出したいことがある.
例えば,
 \begingroup
 <何らかの計算>
 \count@=...%%% 計算結果
 \endgroup
のようなことをして,しかも,“\endgroup の後においてもグループ内で設定した
\count@ の値を使いたい”という場合,どのようにすればよいか.
なお,\count@ は LaTeX 自身が提供するマクロなどのいたるところで用いられるので,
\count@ に対してグローバルな代入を行ってはいけないことに注意.
// 例えば,
// (1) \expandafter\endgroup\expandafter\count@\expandafter=\the\count@\relax
// (2) \edef\@tempa{\count@=\the\count@\relax} \expandafter\endgroup\@tempa
// (2') \xdef\g@tempa{\count@=\the\count@\relax} \endgroup \g@tempa
// (3) \global\somecnt\count@ \endgroup \count@=\somecnt
//     (ただし,\somecnt はあらかじめユーザ側で用意しておいた,
//     グローバル代入を行っても構わないような \count レジスタ)
// のような方法がある.


** 条件分岐 [#eb863be6]
+あるユーザが
 \def\mymacro#1#2{% #1: 出力形式コード, #2: 文字列
    \leavevmode
    \ifcase#1\relax \textit{#2}%
    \or             \textbf{#2}%
    \or             \underline{#2}%
    \else           {#2}%
    \fi}
のようなマクロを作成して(これは,正常に動作する),
もう少しコードを削減しようと
 \def\mymacro#1#2{%
    \leavevmode
    \ifcase#1\relax \textit
    \or             \textbf
    \or             \underline
    \fi
    {#2}}
のように書き換えたら,とたんにエラーが生じるようになった.
その理由および対処法を述べよ.
// TeX の条件処理は,条件に合致する部分に対応する記述のみに
// 置換されるのでは *なく*,
//   必要な部分に到達するまで不要な部分を読み飛ばし,
//   そののち必要な部分の処理を実行して,さらに \fi までの不要な部分を
//   読み飛ばす
// という処理であることによる.
// 例えば,上記の \mymacro の最初の定義では,引数 #1 が 1 であるときは
// “1 番目の \or までを読み飛ばしたのち \textbf{#2} を処理し,そののち
// 2 番目の \or から \fi までを読み飛ばす”ため意図通りに処理される.
// 一方,2 番目の定義ではやはり引数 #1 が 1 であるときには,
// 1 番目の \or までを読み飛ばしたのち“\textbf を処理”しようとし,
// その際に \textbf の引数として \textbf の直後にある \or が用いられ,
// それはユーザの意図とは異なる処理である(実際,\textbf の内部で用いられる
// \ifmmode と \fi の間に \or が入り込み,エラーを引き起こす).
// 対処法は,この例のマクロに関しては
// \def\mymacro#1#2{%
//    \leavevmode
//    \ifcase#1\relax \expandafter\textit
//    \or             \expandafter\textbf
//    \or             \expandafter\underline
//    \fi
//    {#2}}
// のように \expandafter を用いて \textbf などの展開を行う前に
// 余分な \or ... \fi の読み飛ばしを行わせる,という方法がある.
// もっと一般的には,最初の定義のように条件分岐の個々の分岐先のみで
// 完結するように記述するか,
// \def\mymacro#1#2{%
//    \leavevmode
//    \let\mymacro@temp\empty
//    \ifcase#1\relax \let\mymacro@temp\textit
//    \or             \let\mymacro@temp\textbf
//    \or             \let\mymacro@temp\underline
//    \fi
//    \mymacro@temp{#2}}
// のような具合に“分岐先の処理を表すマクロ”を条件に応じて定義すればよい.
+ あるユーザが“ただの文字とコマンドを区別する”意図で
 \def\mymacro#1{% #1: 1 文字あるいは 1 コマンド
    \if\noexpand#1\relax <コマンドである場合の処理>
    \else                <文字である場合の処理>
    \fi}
という定義を行った.
問: \noexpand が必要である理由を述べよ.
また,文字とコマンドの判別を誤る場合について述べよ.
// \noexpand を用いずに \if#1\relax ... のようにしてしまうと,
// #1 の部分を展開してから比較が行われ意図通りの比較にならないので,
// \noexpand が必要.
// 実際,\def\macroA{aa},\def\macroB{XY} のように定義された \macroA,
// \macroB を考えると,\if\macroA\relax は \if aa\relax となり,
// \if による比較では 2 個の a が比較される(\macroA と \relax の
// 比較ではない).同様に,\if\macroB\relax では \if XY\relax となり,
// 比較されるのは X と Y である.
// また,\let\sb=_ で定義された \sb のような文字トークンのコピーに関しては,
// \if あるいは \ifcat での比較の際にはコピーされた文字の文字コードあるいは
// カテゴリーコードを持つものとして扱われる(実際,\if,\ifcat の比較では
// \sb と _ は区別されない).そのため,この問題の \mymacro では,
// 文字トークンのコピーに対しては文字であるか否かの判別を誤る.
+ 次に引用するコードは,ファイル amsgen.sty における \@ifempty の定義である
(\@firstoftwo,\@secondoftwo については,必要があればファイル latex.ltx
を参照のこと.なお,\@xp は \expandafter のコピーである).
 \long\def\@ifempty#1{\@xifempty#1@@..\@nil}
 \long\def\@xifempty#1#2@#3#4#5\@nil{%
   \ifx#3#4\@xp\@firstoftwo\else\@xp\@secondoftwo\fi}
このマクロの用法と,このマクロが誤動作する場合について述べよ.
// \@ifempty{<開き括弧と閉じ括弧の対応がとれたトークン列>}
//   {<そのトークン列が空文字列である場合の処理>}
//   {<そのトークン列が空文字列ではない場合の処理>}
// のように用いる.
// 実際,\@ifempty の引数 #1 に @_{11} が含まれない場合について考えると,
// #1 が空文字列でない場合には \@xifempty は
// \@xifempty <何かある>@@..\@nil という形で用いられ,
// <何かある> の部分が \@xifempty の引数 #1, #2 となり,
// #3 = @, #4 = . となって,\ifx#3#4 の比較結果は偽となる.
// 一方,\@ifempty の引数 #1 が空文字列である場合には,
// \@xifempty @@..\@nil を展開することになり,\@xifempty の各引数は
// #1 = @, #2 は空文字列, #3 = ., #4 = . で,\ifx#3#4 の比較結果は真となる.
// また,誤動作する場合については,\@ifempty の引数に @_{11} が含まれる
// 場合を調べればいい.実際,\@ifempty の引数 #1 が
// (1) <@_{11} を含まず,かつ,空でない文字列>@_{11}XX...
// (2) @_{11}@_{11}XX...
// (3) <@_{11} を含まない文字列><@_{11} の 2 個以上の繰り返し>
// (XX は同じ文字の繰り返し,... は任意の“開き括弧と閉じ括弧の対応が
// とれたトークン列”)といった場合に \@ifempty の引数 #1 が空文字列の
// 場合の処理が実行されてしまう.
// [余談] もっとも,@_{11} が紛れ込む場合,というのは TeX 文書中で
// “ユーザが原稿中に書き込んだ文字列”が空であるか否かを判定する際には
// 問題にならない.空文字列であるか否かの判定としてよく見かける
// \if/#1/ ...\else ...\fi のような処理に比べはるかに器用なので取り上げてみた.


**展開の制御 [#h72dcb23]
+ \def\A#1,#2{#2#1} \def\B#1;#2{#1,#2} \def\C{x;y} と定義されているとき、\A\B\C に\expandafter を適宜挿入して、展開結果が yx となるようにせよ。 
// \expandafter\expandafter\expandafter\A\expandafter\B\C
+ \def\macrocopy#1#2{\expandafter\let\csname #1\expandafter\endcsname\csname #2\endcsname}
のように定義された \macrocopy はどのようなマクロか?
この定義の中の 2 番目の \expandafter を忘れると,どのような不都合があるか?
// \macrocopy{macroA}{macroB} は \let\macroA\macroB という処理を行う,
// という具合に,\macrocopy はその第 1 引数から作成される control sequence を
// 第 2 引数から作成される control sequence のコピーにする.
// また,\macrocopy の定義中の 2 番目の \expandafter を忘れると,
// \macrocopy{<copy>}{<original>} は
// \expandafter\let\csname <copy>\endcsname\csname <original>\endcsname
// となるが,これは
// \let\<copy>\csname <original>\endcsname
// ということで,\<copy> を \csname のコピーにしたのち,<original> と
// \endcsname が取り残されてしまう(その結果,たいていの場合には余分な
// \endcsname があることによるエラーが生じる).
+ あるユーザが,“事物の名称とその事物の分類とを組にして登録するマクロ”と
“(登録済みの)事物の名称を与えると,
その事物と同じ分類先に属する(やはり,登録済みの)事物を与えるマクロ”を作成する,
という問題に出くわした.
例えば,“猫”・“犬”・“人間”の 3 項目のみが登録されていて
“猫”と“犬”の分類は“愛玩動物”で
“人間”の分類は“無用なもの”となっていたとすると,
互いに同じ分類に属する“猫”・“犬”の一方を与えたときには
“猫”・“犬”の両方が得られるが,
別の分類がなされている“人間”は出てこないようなマクロを作りたい,
というのである(もちろん,そのためには“登録用”のマクロも要る).
このユーザは“登録用”のマクロを次のように定義するところまでは自力でできた.
 \def\makeentry#1#2{% #1: 事物の名称,#2: 分類名
    \expandafter\gdef\csname @category@#1\endcsname{#2}%
    \expandafter\ifx\csname @entrylist@#2\endcsname\relax
       \expandafter\gdef\csname @entrylist@#2\endcsname{#1}%
    \else
       \expandafter\gdef
          \csname @entrylist@#2\expandafter\expandafter\expandafter\endcsname
          \expandafter\expandafter\expandafter{%
          \csname @entrylist@#2\endcsname
          ,#1}%
    \fi}
// もちろん,例えば次のように定義すると少しは読みやすいが,
// “故意に”上記のような定義を採用している.
// \def\makeentry#1#2{% #1: 事物の名称,#2: 分類名
//    \expandafter\gdef\csname @category@#1\endcsname{#2}%
//    \expandafter\ifx\csname @entrylist@#2\endcsname\relax
//       \expandafter\gdef\csname @entrylist@#2\endcsname{#1}%
//    \else
//       \expandafter\let\expandafter\@tempa\csname @entrylist@#2\endcsname
//       \expandafter\gdef\csname @entrylist@#2\expandafter\endcsname
//          \expandafter{\@tempa,#1}%
//    \fi}
// また,実際問題としては“重複登録”を回避する必要もあるが,
// “展開順序の変更”に関係しない点には必ずしもこだわらずに出題している.
問: \makeentry{<item>}{<category>}(<item>,<category> は文字列)のように
\makeentry を用いたときに定義される 2 個のマクロは
“項目名 <item> が与えられたときにその <item> が属する分類
(<category>)を与えるマクロ”と“分類名 <category> が与えられたときに,
その <category> に属する登録済み項目のコンマ区切りリストを与えるマクロ”
であることを確認し,それに基づいて“(登録済みの)事物の名称を与えると,
その事物と同じ分類先に属する(登録済みの)事物を与えるマクロ”を定義せよ.
// \makeentry{<item>}{<category>} は次の 2 個のマクロを定義する.
// ・\@category@<item>
//   これは <category> に展開されるので,
//   “<item> が属する分類を与えるマクロ”である.
// ・\@entrylist@<category>
//   これは,このマクロが未定義であるときには <item> に展開されるように定義され,
//   定義済みのときには“このマクロの置換テキストの後に ,<item> を追加したもの”
//   に展開されるように定義されるので,結局 \@entrylist@<category> は
//   “<category> に属する項目のコンマ区切りリスト”を与えるマクロである.
// また,与えた事物と同じ分類に属する項目を与えるマクロは,例えば,
// 次のように定義できる.
// \def\showsiblings#1{% #1: 事物の名称
//    \expandafter\ifx\csname @category@#1\endcsname\relax\else
//       \csname @entrylist@\csname @category@#1\endcsname\endcsname
//    \fi}
// // [余談] \csname @<array>@<index>\endcsname を <array>[<index>] と
// // 書き換えてみると,上記のコードの内側の
// // \csname @entrylist@\csname @category@#1\endcsname\endcsname
// // のところは entrylist[category[#1]] と書き換えられる
// // (こう書いてみると,どこかで見たような気がするのではないだろうか).


**デバッグ [#me44b82b]
+ あるユーザが,一時的に文字列の幅を測定してその結果を用いるつもりで
 \def\mymacro#1{%
   \newdimen\tempwidth
   \settowidth\tempwidth{#1}%
   ...}
のような定義を行ったところ,別のユーザから“本当にそう定義していいかい?
そのマクロを 256 回以上使ってみるとどうなる?”とツッコミが入った.
上記の定義の問題点および対処法を述べよ.
// 問題点: 一時使用用のレジスタを \mymacro を用いるたびに新規割り当て
// しているため,いずれレジスタを使い果たしてしまう(eTeX ベースの
// システムではこの問題はあまり顕在化しないとは思うが,問題であることには
// 変わりがない).つまり,“メモリリーク”が存在する.
// 解決法: 一時使用用のレジスタはマクロの置換テキストの外部で割り当てればいい.
// e.g.
// \newdimen\tempwidth
// \def\mymacro#1{\settowidth\tempwidth{#1}...}
// なお,“ローカルに使う変数”が要る場合には,レジスタをローカルに
// 割り当てようとするのではなく(実際,\newdimen などのレジスタ
// 割り当て用マクロではグローバルにしか割り当てない),
// “レジスタの値のスコープ”をグルーピングで制御すればよい.

** 組版上の問題に由来する処理 [#o0e908b4]
+次のように書いたときに「あ」だけの行ができるのはなぜか?&br;
また,どうすれば「あれ,なんで?」と一行にできるか?&br;
 \par
 \hbox{あ}
 れ,なんで?
// \par によって垂直モードに移行し,次の \hbox{あ} はそのまま垂直モードで
// 処理されていることによる.ここでは,\leavevmode を \hbox{あ} の
// 直前に置いて明示的に水平モードに移行すれば,“あ”のみの行ができることは
// 回避される(もっとも,“\hbox{あ}れ,…”ではなく単に“あれ,…”
// としてもこの問題の文字列に関しては解決するが,これは出題意図から
// 外れているだろう).
// 確かに意図とは違いますが,正当な理解に基づいての\hboxを外すという解は
// 正解ですね.ですので最初に「なぜか」という問いかけがあります
+あるユーザが今何ページであるかを本文中に書くために\thepageを記述した.ところが,正しくノンブルを拾えているところと,そうではないところが発生してしまった.これはなぜか(正しく拾えているところとそうではないところに何らかの傾向があるはずである).
// TeX の組版処理が“段落指向”であることによる.
// 例えば,ある段落が複数のページにまたがっている場合,
// その段落の中のページ分割位置以降の部分に \thepage が書かれていると,
// その \thepage に対応する出力は段落の開始位置を含むページ(状況によっては
// それ以前のページとなることもありうる)のページ番号となってしまう.
// 実際,“段落の組版処理を済ませて個々の行を作成してから”
// ページ分割位置が割り出されるため,段落の組版処理(その際に,段落全体が
// 文字列レベルでは確定する)の際にはページ分割に伴う \thepage の更新など
// 行わないで処理を済ませてしまう.

-----
- 「入門」の一項目にするのではなく,別途「マクロの作成・応用」のようなページにされた方が良いように思いますが,いかがでしょうか。 この内容は「入門」のレベルを逸脱しているように思いましたので -- トニイ 2006/06/16
- [北見 けん] まあ、もう少しコンテンツが充実したら構成を見直すということでよろしいかと思います。 まずプリミティブを把握しておけば LaTeX などのマクロは自分で読めるようになるという意味では、入門としてはプリミティブの範囲に収まるものとしてもよいような気もします。 -- 北見 けん 2006/06/16 10:43
- このページの構成自体についてもいろいろご意見があるようなので、コメント欄を追加してみました。ついでに勝手ながら過去のコメントを表に出してみました。-- 北見 けん 2006/06/23
- とりあえず「タネ」を充実させてからでどうでしょう.何問か書きましたが,一日も経たずに解答がついてますね. -- 本田 &new{2006-06-24 (土) 10:53:29};

#comment