2011/08/10

TeXで「最後のコマンド」だけ挙動を変える(ワンパス版)

@munepixyzさんのツイートで、あるコマンドの挙動を最後の出現のときだけ別なものにしたい、というTeXフォーラムへの質問があったことを知りました。

最後のコマンド
http://oku.edu.mie-u.ac.jp/tex/mod/forum/discuss.php?d=691&parent=3733

すでに、LaTeXで.auxファイルを使うというお手本のような回答があがっていますが、くだんのコマンドが本文のトップレベル階層にしか出現しない場合には、texコマンドを一回起動するだけで求める動作を実現できます。TeXフォーラムに登録していないため、ここでネタにします。

いつもは文字列「hoge」を出力し、最後の出現だけは文字列「fuga」を出力するコマンド\hogeの定義はこれだけです。(最後の\fiと#2の位置が逆だったバグを直しました(2012/2/27)。utさんコメントでの指摘ありがとうございます。)
\long\def\hoge#1\hoge#2{%
  \ifx#2\end fuga#1#2%
    \else hoge#1\expandafter\hoge
  \fi#2}

こんなふうに使います。pdftexを起動してインタプリタに以下を貼り付ければ、最後の\hogeだけがfugaに置き換わったPDFができあがります。
\hoge \hoge $1+2=3$ \hoge

\hoge \hoge

oshimai
% 本文はここまで

\hoge\end % この行を本文の最後に追記する
\eject

\hogeの定義では、引数を指定する部分が#1\hoge#2というあまり見慣れない形をしています。これは大雑把に言うと「次に\hogeが出てくるまでを#1に、その\hogeの直後のトークンを#2に束縛して、定義の本体を実行せよ」という意味です。このように、TeXのマクロでは引数のパターンマッチができる!

実際には1つのパターンでしか定義できないので、これはパターンマッチでもなんでもないのですが、この「パターンでマクロの引数を取り出す」手法はTeXプログラミングの主要な武器のひとつです。少なくとも私はそうおもっているので、自分でもけっこう使う機会が多いです。これを知ってればlatex.ltxだってもりもり読めるよ。

5 件のコメント:

  1. ut と申します。お示しくださったものを試してみたところ、

    ! Extra \else.

    と言われてしまいました。それで少し考えてみたのですが、#2 を \fi の外に出さなければいけないような気がして、

    \long\def\hoge#1\hoge#2{%
    \ifx#2\stophoge
    hoge #1 fuga \let\stophoge\relax%
    \else
    hoge #1\expandafter\hoge
    \fi#2%
    }

    としてみました(\end を \stophoge に変えました)。更に、\hoge#2 を \fi の外に出せないかなと思いましたら TeX by Topic に \hop というのが載ってましたので、次のようにしてもいけそうな感じです。

    \def\hop#1\fi{\fi#1}
    \long\def\hoge#1\hoge#2{%
    \ifx#2\stophoge
    hoge #1 fuga%
    \else
    hoge #1\hop\hoge#2%
    \fi}

    うまくいっているように見えるのですが、もっと簡単で確実な方法があるのかも知れません。
    あと、oshimai の後ろに % を置かないと、スペースが余計に入るみたいです。

    返信削除
  2. おはようございます。続けてのコメントで申し訳ありません。

    \hoge#2 を \fi の外に出すには、

    \expandafter\hoge#2\fi



    \expandafter\hoge\expandafter#2\fi

    とするだけでよかったのですね…。

    返信削除
  3. utさん、こんにちは。
    確かにバグってましたね。。#2が\fiで閉じる前に置かれてたので、3つめ\hogeが展開されてしまい、おかしなことになってました。#2と\fiの位置を入れ替えれば動くはずです。

    返信削除
  4. ところでutさんの定義は、本文中の最後の\hogeの直後に\stophogeを追記して使うという意図でしょうか? それだと

    \def\hoge#1{\ifx#1\stophoge...}

    で十分かも。

    最後に\hogeが出てくる場所は知らないけど、とにかく本文のお尻に\hoge\endと追記すれば、本文中で最後に出てくる\hogeだけは「fuga」になる、というのが本記事の\hogeの定義のポイントだったりします。(とはいっても、確実に最後の\hogeを見つけられるようにしようと思うとplain TeXに限定してさえけっこう面倒が予想されるので、ネタにすぎないのですが。。)

    いただいたコメントに「oshimaiの後の%が必要」とあったので、oshimaiの後の\hogeも変換対象の本文という誤解を与えてしまった気がしてます。すみません。oshimaiが本文の最後のつもりで、その後の\hogeは、出力はされないトークンです。

    返信削除