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