* 基礎知識 [#s5fc1cf9] TeX の特徴の一つにマクロが作成できることが挙げられます。 マクロを活用することによって、ミスの少ない仕上がりや作業効率の向上などの効果が期待できます。 また、実用的な面を持つだけではなく、知的な趣味としても愉しむことができます。 マクロの作成技術を身につけるにあたって必要な基本知識を列挙しておきましょう。 これらの事柄を調べてゆくと自ずとマクロ作成技術が身に付くことでしょう。 参考書について:マクロを作成する上で必要となる 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文字を単独の文字トークンとして読み取る ここで「英文字」とは何かという問題がありますが, これについては次項「カテゴリーコード」を参照してください。 ただし,ここではpTeXで使える日本語を含む コントロールシークエンストークンは考えません. コントロールシークエンスには二種類存在します. ひとつは上記の1のiに相当する,``\''のあとに英文字が続くもので これをコントロールワードといいます. 例えば,“\alpha”によって生成される[alpha]はコントロールワードです. もうひとつは上記の1のiiに相当する, ``\''のあとに英文字以外の1文字が続くもので, これをコントロールシンボルといいます. 例えば,\!によって生成される[!]はコントロールシンボルです. *** トークンの区切りと空白文字の読み飛ばし [#ta414729] さて、ここでコントロールワード[small]に続いて英文字の文字トークン“a”を置くことを考えて見ましょう。本文中で小さな文字を使って“a”という活字を印字したいという場面です。 ソースファイルにはどう記述すべきでしょうか? “\smalla”でしょうか? これでは駄目ですね。 上記の規則 1のi によって続く限りの英文字が取られて[smalla]というコントロールワードになってしまい、意図とは違ってしまいます。では英文字以外のものを“\small”の後におけばよいのですから“\small:a”などとしてみます。今度は[small],“:”,“a”となってしまい間に余計な文字トークンが入ってしまいます。 このように、コントロールワードに続けて英文字トークンを置きたい場合にはなにか区切りになるものが必要ですが、その区切り自体が余計なトークンになってしまうなら、困る場合も出てきます。そこで、TeXでは例外的に、区切り文字として空白文字を使った場合には、コントロールワードの名前の終わりを表す区切りとして認識したあとで、その空白文字を読み飛ばします。 この例外規則のおかげで、先ほどの問題は“\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番目のカテゴリーの文字だ」という具合です。 (うーん。なんだか冗長になってしまうなぁ) (今日はとりあえずここまで。続きはいつになるか分かりません。) * 基本練習問題 [#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] +次のように書いたときに「あ」だけの行ができるのはなぜか?&br; また,どうすれば「あれ,なんで?」と一行にできるか?&br; \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を前提とした. ----- - 「入門」の一項目にするのではなく,別途「マクロの作成・応用」のようなページにされた方が良いように思いますが,いかがでしょうか。 この内容は「入門」のレベルを逸脱しているように思いましたので -- トニイ 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 ← 手動タイムスタンプです #comment