sankantsuのブログ

技術メモ・競プロなど

LaTeXの\@ifnextcharマクロ

概要

LaTeXカーネルで定義されている\@ifnextcharというマクロについて説明する. このマクロを使うとトークンの先読みを行うことができ,オプション引数の実装などに応用できる. カーネルやパッケージのソースにはよく登場するので,これらを読んでみたい場合には役立つ知識になるだろう.

以下,\makeatletterの状態で考える (@が英文字と同等扱い).

動作

\@ifnextcharは,3つの引数をとる.

\@ifnextchar<token>{<true case text>}{<false case text>}...

と書くと,3つ目の引数の次にくるトークン(上で言うと...の部分の先頭)を先読みし,このトークンが<token>に一致していれば<true case text>が展開され,一致しなければ<false case text>が展開される.

使用例

最も重要な使用例は,オプション引数の実装である. LaTeXの多くのコマンドは,[...]で囲んだ部分をオプション引数として渡せるようにしてある.

\defによるマクロで[...]の形の引数を受け取ろうと思うと,次のように書くことになる.

\def\foo[#1]{(option:#1)}

この定義により,\foo[...]のような形で引数を渡すマクロを作れる. 問題はこの定義だと[]の括弧は省略不可能になり,これに沿わない形式の引数を与えるとエラーとなることである.

\@ifnextcharを用いると,この問題点を次のような方法で解消できる.

\def\foo{\@ifnextchar[\@foo\@@foo}
\def\@foo[#1]{(option:#1)}
\def\@@foo{(no option)}

このように定義することで,\fooは次にくるトークンを先読みして[と等しいかどうか調べることができる. 等しい場合にはオプション引数を取るバージョンである\@fooを展開し, 等しくない場合にはオプション引数を取らない\@@fooを展開する.

したがって,オプション引数がある場合とない場合でそれぞれ次のような結果が得られる.

\foo[bar] % -> (option:bar)
\foo      % -> (no option)

実装

LaTeXカーネルのソース(latex.ltx)を読んで実装を追ってみる.

\long\def\@ifnextchar#1#2#3{%
  \let\reserved@d=#1%
  \def\reserved@a{#2}%
  \def\reserved@b{#3}%
  \futurelet\@let@token\@ifnch}

まず,引数をそれぞれ\reserved@d,\reserved@a,\reserved@bというマクロに保存しておく.

その後にくる\futureletというプリミティブが鍵で,これは

\futurelet\cs<token1><token2>...

と書くと\let\cs=<token2>を実行したあとで<token1>を展開するものである. この動作により,\csに先読みトークンを保存した上で<token1>の展開が行えるので,<token1>の定義の中で\csの中身を見ての分岐を行うことができるようになる.

つまり,今回の場合には\@let@tokenという制御綴に先読みトークンを保存しておいてから\@ifnchの展開を行う. この\@ifnchは次のように定義されたマクロである.

\def\@ifnch{%
  \ifx\@let@token\@sptoken
    \let\reserved@c\@xifnch
  \else
    \ifx\@let@token\reserved@d
      \let\reserved@c\reserved@a
    \else
      \let\reserved@c\reserved@b
    \fi
  \fi
  \reserved@c}
\def\:{\let\@sptoken= } \:  % this makes \@sptoken a space token
\def\:{\@xifnch} \expandafter\def\: {\futurelet\@let@token\@ifnch}

\ifxは,\ifx<token1><token2>の形で<token1><token2>の一致判定を行うプリミティブである. 最初の分岐は先読みトーク\@let@tokenが空白文字かどうかを判定して,もし空白文字なら読み飛ばすということをしている.

次の分岐が重要で,先読みトークンが事前に保存しておいた\@ifnextcharの第1引数\reserved@dに等しいかどうかを判定する. 等しければ保存された第2引数\reserved@aを実行するようにセットし, 等しくなければ第3引数\reserved@bを実行するようにセットする.

まとめ

  • \@ifnextcharの動作と応用例を説明した.
  • \futureletを用いた\@ifnextcharの実装を説明した.