マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

今さら人に聞けない zref パッケージのキホン(2)

前回の続き)
ラベルを置く

次に「文書の最後にラベル“lastdo:”を置く」処理を実装する。このために、「文書の最後に実行されるフック」である \AtEndDocuemnt を利用する。

\AtEndDocuemnt{\zref@labelbylist{lastdo:}{LastDo}}

ラベルを置く命令が \zref@labelbylist であるが、この第 2 引数にリスト名 LastDo を指定している。これにより、この命令で .aux ファイルに出力されるプロパティ情報がリスト LastDo に所属するものに限定されるのである。もし仮に「リスト」という概念がないと、例えば別の(zref を利用する)パッケージが「数式番号」や「ページ番号」などのプロパティを作っていたとすると、((実は、zref パッケージ自身が「LaTeX の相互参照」の zref 枠組でのエミュレートを実装していて、「数式(などの \label 付加対象の)番号」を表す default や「ページ番号」を表す page というプロパティを実際に定義している。これらのプロパティは main というリストに所属する。main のプロパティを対象にしてラベルを配置したい場合は、\zref@labelbylist{ラベル}{main}} とすればいいのだが、main リストについては、\zref@label{ラベル} という短縮形が利用できる。))ここの処理で“lastdo: の数式番号”まで記録しようとするが、これは全くの無駄である。恐らくはこれがリストという概念の存在理由なのだろう。

あと残っているのは、lastdo 命令の本体となるマクロ \lastdo@do である。

%% \lastdo@do{<命令名>}{<非最後>}{<最後>}
% LastDo命令の本体.
\def\lastdo@do#1#2#3{%
  ……
}

例えば、lastdo 命令 \hoge を実行すると、「\lastdo@do{hoge}{Hoge}{LastHoge}」に展開されるのであった。この \lastdo@do の中で行うべきことは次の 3 つである。

  1. プロパティ lastdo@hoge の値を 1 増やす。
  2. lastdo@hoge の前回コンパイル時の“lastdo:”での値を得る。
  3. 1 と 2 の値を比較して、「非最後」と「最後」の一方を実行する。
プロパティの値の読み書き

(今回実行の)プロパティの値を読むには \zref@getcurrent を用いる。「1 増やす」演算を行いたいので一旦整数レジスタ \@tempcnta に代入することにする。

%(\lastdo@do の定義本体の中;#1は"hoge"などのlastdo命令名)
  \@tempcnta=\zref@getcurrent{lastdo@#1}%
  \advance\@tempcnta 1

\zref@getcurrent は完全展開可能であるので、(今の例のように)プロパティの値が整数を表すトークン列である場合は、TeX コード中の“整数を書くべき場所”に直接書くことができることに注意しよう。

プロパティの値を書くには \zref@setcurrent 命令を使う。ただしここで注意が必要である。

  % これはダメ!
  \zref@setcurrent{lastdo@#1}{\@tempcnta}%

この命令は引数に与えられたトークン列を「そのまま」値として使う。ところが、lastdo@hoge に格納するものは「\@tempcnta」(あるいは「\the\@tempcnta」)自体ではなくて“\@tempcnta が現在保持する整数値”(例えば「3」)でないといけないはずである。

引数を完全展開させるため、「\edef 踏み台パターン」を使うことにする。

  % 予め \the\@tempcnta を完全展開しておく
  \edef\next{\noexpand\zref@setcurrent{lastdo@#1}{\the\@tempcnta}}%
  \next

※ちなみに、完全展開でなくて一回展開でないといけない場合は次のようにする。

  \def\next{\zref@setcurrent{lastdo@#1}}%
  \expandafter\next\expandafter{\the\@tempcnta}%
“前回の値”を読み出す

補助(.aux)ファイルから読み出した「前回コンパイル時の値」を参照するには、\zref@extract 命令を利用する((読出については、\zref@extract の他に様相の異なる命令が存在する。))。最初の「zref の概念説明」で述べたように、「前回コンパイル時の値」はラベル毎に記録されるものである。(LaTeX の相互参照でいえば、\ref{eq:abc} のようにラベル名が引数になっている。)従って、\zref@extract はラベル名とプロパティ名を引数にとる。つまり、今必要としている「前回に \hoge が呼び出された回数」は次の命令で得られる。

% ラベル"lastdo:"に紐づくプロパティ"lastdo@hoge"の値
\zref@extract{lastdo:}{lastdo@hoge}

\zref@extract も完全展開可能であるので、この値と(インクリメントした後の)現在の lastdo@hoge の値(これは \@tempcnta に入っている)を比較する(つまり「最後のコマンド」であるかを判定する)if 文は次のように書ける。

  \ifnum\zref@extract{lastdo:}{lastdo@#1}=\@tempcnta
    % 最後である
  \else
    % 最後でない
  \fi

以上を合わせると、\lastdo@do の実装は以下のようになる。

%% \lastdo@do{<命令名>}{<非最後>}{<最後>}
% LastDo命令の本体.
\def\lastdo@do#1#2#3{%
  % '現在値'を読み込む
  \@tempcnta=\zref@getcurrent{lastdo@#1}%
  % '現在値'をインクリメントして書き込む
  \advance\@tempcnta 1
  \edef\next{\noexpand\zref@setcurrent{lastdo@#1}{\the\@tempcnta}%
  }\next
  % '現在値'が'前回の値'とを比較する
  \ifnum\zref@extract{lastdo:}{lastdo@#1}=\@tempcnta #3%
  \else#2\fi}

これで全ての実装が完了した。全体のプログラム(lastdo パッケージ)は以下の場所で公開している。