マクロツイーター

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

10分でわかる \afterassignment

前回に引き続いて、TeX言語の“変態的なプリミティブ”について解説してみる。今回は\afterassignmentについて解説する。

例によって、\afterassignmentを使ったコードを“読む”ことを目標とする。(ただしTeX on LaTeX1のコードでの\afterassignmentの使用頻度はかなり少ない。)

前提知識

  • TeX言語の「トークン(token)」とは何かを理解している。
  • レジスタやマクロ(macro)についてのキホンを知っている。

とにかく \afterassignment の規則を知ろう

\afterassignmentはその名の通りで「“代入の後”に実行されるトークンを前もって指定する」という機能をもつ。具体的な定義は以下の通りである。

  • \afterassignment‹トークン›: 次に実行される代入文が実行された直後に‹トークン›を実行する。

まず書式についての注意であるが、\afterassignmentの“引数”は「その直後にある(単一の)トーク」である。例えば、

\afterassignment{ABC}

というコードの場合、この\afterassignmentの“引数”はABCではなく2{となる3

前述の定義には「代入文」という言葉がある。TeX言語学習者が「代入文」と聞いて思い浮かべるのは次のような「レジスタ・パラメタに対する代入」の文であろう。

\dimen@=42pt % 寸法レジスタ'\dimen@'に42ptを代入
\catcode`\!=13 % '!'のカテゴリコードを13に変更
\setbox0=\box\voidv@x % 0番のボックスレジスタにボックスレジスタ'\voidb@x'の内容を代入
\toks@{\TeX!} % トークン列レジスタ'\toks@'にトークン列'\TeX!'を代入

しかし「TeXにおける代入文」はこれに限らない。\let文のような「トークンの意味を変更する文」も代入文に含まれる。

\let\@tempa=\relax % '\@tempa'の意味を`\relax'プリミティブに変更する
\chardef\my@bs=`\\ % '\my@bs'の意味をchardefトークンに変更する
\font\my@rm=cmr12 at 13pt % '\my@rm'の意味をfontdefトークンに変更する

さらにいうと\defなどを用いた「マクロ定義文」も「トークンの意味を変更する文」であるため代入文の一種である。

\def\my@name{ZR} % '\my@name'の意味を(とある)マクロに変更する
\long\xdef\@gtempa{\@tempa}

とにかく \afterassignment するコードを読もう

\afterassignmentの規則が解ったので、例によって簡単なコードを“読む”練習をしてみよう。以下のコードにおいて、マクロ\myAssignCountが何をするものなのかを言葉で説明してほしい。

LaTeXフォーマットでetoolboxパッケージを読み込んだ状態を仮定する。

%% \myAssignCount{<整数レジスタ>}{<整数>}
\newif\ifmy@ok
\newcount\my@count
\let\my@mark\indent % 展開不能なマーカー
\def\myAssignCount#1#2{%
  \afterassignment\my@assign@count@a
  \my@count=#2\my@mark
  \ifmy@ok #1=\my@count
  \else \@latex@error{Bad number format}{\@eha}% エラー表示
  \fi}
\def\my@assign@count@a#1\my@mark{%
  % '\ifblank{<トークン列>}{<真>}{<偽>}'はetoolboxのマクロで,
  % "<トークン列>が空白文字トークンしか含まないか"を判定する.
  \ifblank{#1}{\my@oktrue}{\my@okfalse}}

先頭のコメントから\myAssignCountが整数レジスタと整数を引数にとることがわかるので、取りあえず次のコードの実行を追ってみよう。

\myAssignCount{\@tempcnta}{42}

この場合、マクロの最初の2行は次のようになる。

\afterassignment\my@assign@count@a
\my@count=42\my@mark

\afterassignmentを実行した直後に整数レジスタ\my@count)への代入文がある。\afterassignmentの規則に従うと、\my@countに42が代入された直後にトーク\my@assign@count@aが実行されることになる。つまり以下のコードが実行される。

\my@assign@count@a\my@mark

マクロ\my@assign@count@aの引数#1は区切りトーク\my@mark4までのトークン列だから、今の場合は#1は空になる。

\ifblank{}{\my@oktrue}{\my@okfalse}

\ifblankの第1引数は空である(何も含まない)ので、スイッチ5my@okは真になり、\myAssignCountの残りのコードの実行に戻る。

\ifmy@ok \@tempcnta=\my@count
\else \@latex@error{Bad number format}{\@eha}% エラー表示
\fi

my@okが真なので\@tempcnta\my@countの値が代入される。結局「\@tempcntaに42が代入される」ことになった。\myAssignCountの引数の値を変えても実行の流れは同じで「第1引数のレジスタに第2引数の整数を代入する」ことになりそうだ。

ではmy@okが偽になるのはどんな場合だろうか。このときにBad number formatというエラーを出力することが指示されていることから考えると、「何か入力の整数の書式に異常がある場合」と推測できる。そこで次のような異常な入力(整数値の後ろにゴミがある)での動作を追ってみよう。

\myAssignCount{\@tempcnta}{42A}% 余計な'A'がある

このとき\afterassignmentの後のコードは

\my@count=42A\my@mark

となり、この中で代入文である部分は「\my@count=42」までなので、結局次のコードが実行される。

\my@assign@count@a A\my@mark
↓(展開)
\ifblank{A}{\my@oktrue}{\my@okfalse}

今度は区切りの\my@markの前に空白でないトークAが存在するので、my@okは偽になり、ゆえに(予想通り)エラーが出る。(このときには\@tempcntaへの代入は行われないことに注意。)

以上の考察をまとめると、\myAssignCountの動作は以下の通りである。

  • 引数にレジスタと整数値を受け取り、そのレジスタに整数値を代入する。ただしその際に「整数値が正しい書式をもっているか」を検査し、異常であった場合はエラーを出力して6代入を行わない。
\myAssignCount{\@tempcnta}{42}
%→ '\@tempcnta'に42が代入される
\myAssignCount{\@tempcnta}{42A}
%→ 'Bad number format'エラーが発生, '\@tempcnta'は変わらない

TeX on LaTeXのコードにおいて\afterassignmentが使われる典型的な場面は、ここで扱った\myAssignCountのように「入力の書式を検査する」処理である。それ以外で\afterassignmentが使われていたら、それは上級者向けのトリッキーなコードであることが多い。

補足

  • 規則上は\afterassignmentの実行の直後に代入文を置く必要はなく間に処理を挟むことが可能であるが、現実のコードにおいては間の処理はないことがほとんどである7ので、初級者はその使い方だけを知っていればよい。
  • 代入文が「右辺が即値のボックスであるようなボックスレジスタへの代入文」(つまり\setbox0=\hbox{...}のような文)である場合は少々特別な規則8が適用される。(初級者は特別規則があることに留意していればよい。)

まとめ

2回目にして早くも「絶対に10分では読めない記事」になってしまった(ざんねん🙃)


  1. LaTeXのフォーマットの上で「ユーザレベルのLaTeX」の範囲を超えたTeX言語の機能を使うこと。公式には「プログラマレベルのLaTeX」などと呼ばれる。
  2. そもそもABCは「トークン列」ではあるが「(単一の)トークン」ではない。
  3. もっとも、私はこういうトリッキーなコードが実用されているのを見たことがない。
  4. 〈中級者向き補足〉\my@markは単なる区切りとして用いるので、\indentでなくても展開不能トークンであればよい。展開可能トークン(LaTeXカーネル\@nil等)は整数表記を終結しない(代入文の実行の際に展開されてしまう)ので使えない。そう考えると\my@markの意味は\relaxでもよいはずなのであるが、引数に\numexpr6*7のような「終結していない\numexpr式」が与えられた場合には\relaxは吸収されてしまうので、その対策として\indentを使っている。
  5. スイッチ(switch)」は「\newifで定義されるif-トークン」を表す用語。一般のプログラミング言語における真偽値変数の役割を果たす。
  6. なお第2引数の値がABCのように「最初から整数ではない」場合は、まずTeXの「Missing number」のエラーが出た後に所定のエラー処理が行われる。実は今の\myAssignCountの実装には不備があって、第2引数が空白の場合は「Missing number」エラーが出た後に0が代入されてしまう。これを防ぐためには、前もって「第2引数が空白でないか」の検査を入れる必要がある。
  7. 間に処理を挟む場合を勘案すると、「\afterassignmentを同時に2回以上使えるのか」(→答え:できない)や「グルーピングの影響は受けるのか?」(→答え:受けない)などの複雑な考慮事項が増えてしまう。
  8. 〈中級者向き補足〉「右辺が即値のボックスであるボックスレジスタへの代入」である場合は、\afterassignmentの引数のトークンが実行されるタイミングが通常の「代入文が実行された直後」ではなく、「右辺のボックスのトークン列の実行の直前」になる。例えば\afterassignment\X\setbox0=\hbox{ABC}の場合は\XAの直前に実行される。