* 基礎知識 [#s5fc1cf9]

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

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

参考書について:マクロを作成する上で必要となる TeX のプリミティブ命令の多くは、LaTeX の入門レベルの解説書では触れられていないことが多いです。『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] となる.
// もちろん,『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}}
// のような具合に“分岐先の処理を表すマクロ”を条件に応じて定義すればよい.


**展開の制御 [#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

**デバッグ [#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