マクロツイーター

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

完全展開可能なナベアツ ― e-TeX の場合

(一応前回の続き)

完全展開可能ナベアツを実装しようとする場合、e-TeX 拡張のある時とない時では難しさが全然違ってくる。その理由は、e- でない TeX で完全展開可能なマクロを作る際には算術演算の機能が全く使えなくなるからである。

一般に、マクロを完全展開可能にしようとすると「代入操作」が禁止される。ここで「代入操作」には、レジスタへの代入文(\count2=42)の他に、\def\let\chardef も含まれ、さらに \advance\multiply\divide の算術演算命令も同様に使えなくなる(すなわち、これらのプリミティブは展開可能ではない)。ここで、e-TeX 拡張にある \numexpr 等の算術式を導入するプリミティブは、それ自体が内部値となるので \the を付けて展開することが可能である――例えば、「\the\numexpr1+5*8+1」を一回展開すると「42」となる。だから、e-TeX では展開限定文脈でも算術演算が容易に行える。対して、非 e-TeX では、「1 を足す」ことすら難行になってしまう。

当然の帰結として、より挑戦的で興味深いのは e- でない TeX での実装ということになる。だから、「つまらない」e-TeX の方はとっとと片付けてしまおう。((ちなみに、LuaTeX で Lua 呼出(\directlua)を使うと、自然と完全展開可能なマクロが実現できる。*「何だって!誰でも簡単にマクロが組めるなんて全くもって許し難い!けしからん!大体、TeX ってものは…(以下略)」))以下に実装例を示す。

\catcode`@=11 %--------------
%% \NabeAzz{<整数>}: ナベアツ本体
\def\NabeAzzX#1{%
  \xx@nax@a1;#1;%
}                    
  % 区切り引数にする理由は(*2)で \expandafter を節約するため
\def\xx@nax@a#1;#2;{%
  \xx@nax@print{#1}\space
  \ifnum#1<#2 \xx@escape@if % if-ネストを脱出する (*1)
    \expandafter\xx@nax@a\the\numexpr#1+1;#2;% (*2)
  \fi
}
%% \xx@nax@print{<整数>}: 整数のアホ性を判定して適切に出力する
\def\xx@nax@print#1{%
  \ifnum% OR のイディオム (*3)
    \nax@has@three#13@%
    \ifnum\numexpr#1/3*3=#1 1\else0\fi >0
    % 3 の倍数または 3 のつく数字だからアホなフォントにする
    {\AhoFont #1}%
  \else
    #1%
  \fi
}
%% \nax@has@three<文字列>3@: 3 を含む/含まない場合に 1/0 を出力
\def\nax@has@three#13#2@{%
  \ifx @#2@0\else 1\fi % 空判定のイディオム (*4)
}
\def\xx@escape@if#1\fi{\fi#1}% (*1) 参照
\catcode`@=12 %--------------

%% アホなフォントの定義
\font\AhoFont=cmfi10 scaled \magstep2 % Computer Modern Funny Italic
%%
\edef\result{\NabeAzzX{40}}
\show\result % 中身を見る
\result % 実際に出力を行う
\bye

「1 を足す」のに苦労することはないが、それでも完全展開版の実装はやはり普通のナベアツと比べると、様々な技法を要する場面が多く難度が高くなる。以下に少し解説を加えておく。

  • 完全展開のマクロのコーディングは、「代入ができない」という性質上、関数型言語のそれと非常に近くなる。(\NabeAzzX を副作用のない「トークン列を返す関数」と考えればよい。)ただし、飽くまで関数でなくマクロに過ぎないので、\numexpr の演算結果を数字に「展開」する必要が生じる。
  • \xx@escape@if は if-ネストを脱出するトリックで、再帰を末尾再帰型にするために用いている。(実際に展開した後の形を考えれば原理が解るだろう。)
  • (*3) の形は if 文で OR を表すイディオム。もう少し解りやすい例を挙げると
    \ifnum \ifA1\else0\fi \ifB1\else0\fi >0 YES\fi
    はスイッチ A と B の少なくとも一方が真の時に YES を出力する。
  • (*4) に現れる「\ifx @#2@」は引数 #2 が空列であるかの判定。ただし #2 が「@」を含まないことを仮定している。