読者です 読者をやめる 読者になる 読者になる

TeX芸人の知らない#の挙動

8/28 \edefにおいて,マクロ展開とパラメータトークン生成のどちらが先に走るのかを追加

はじめに

このツイートから始まった一連の話.

この事象を解析していくと,TeXがマクロ中の#をどのように扱っているかの一端を知ることができる. この記事では,私が得たTeXにおける#の扱いを紹介する.

基本

TeXのデフォルトでは,文字#に「パラメータ文字」という役割を与えている. この文字はマクロ定義おける引数を表すために用いられる. 次の例を見てみよう*1

\def\somecs#1{#1 is crazy}  % マクロ定義
\toks0\expandafter{\somecs{\TeX}}
\showthe\toks0              % -> \TeX  is crazy.

よく知られているように,\somecsが次のようなマクロとして定義されている.

  • 1引数をとる
  • 引数を<<第1引数>>としたとき,<<第1引数>> is crazy.というトークン列を出力する

このとき,引数であることを示すための文字の役割がパラメータ文字である. パラメータ文字はカテゴリーコードが6の文字である.

パラメータ文字#はマクロ定義内で次の役割を持つ*2

  1. #1から#9を第1引数から第9引数として取り扱う
  2. 置換えテキストで##を単一の#に置換える
  3. パラメータテキストの末尾が#である場合,{がパラメータテキストと置換えテキストの両方の末尾に挿入されたものとして扱う(TeXブック p.276)

ここで,置換えテキストとは{}で囲われた部分で, パラメータテキストとはコントロールシーケンスと{の間のことである.

1についてはよく知られているだろう.

2は#をエスケープするための規則である.主にマクロを定義するマクロで使用される. なお,パラメータテキスト内では2の規則がないため, #にパターンマッチすることはできない*3

3はLaTeXを使っている場面においては触れる機会が少ないルールであろう.次の例を見てみよう.

\def\testA#1#2{!#2!#1!}
\def\testB#1#{\testA{<#1>}}
\edef\result{\testB foo{bar}}
\show\result % !bar!<foo>!

この例の\testBが3のルールを使用したマクロである.\testBの定義を見ると,

> \testB=macro:
#1{->\testA {<#1>}{.
<*> \show\testB

となる.->の前にあるパラメータテキストだけでなく, 置換えテキストの末尾にあることがわかるだろう.よって,\testB foo{bar}は 次のように展開される.

  1. \testB foo{bar}
  2. \testA {<foo>}{bar}\testB foo{\testA {<foo>}{に置き換えられた)
  3. !bar!<foo>!\testAの展開)

ツイートへの回答

最初に挙げたツイートに回答するには,これに合わせて次のルールを知る必要がある.

  1. 基本で挙げたすべてのルールはマクロ定義時に解決される
  2. カテゴリーコードが6の文字が出力されるとき,その文字は2回出力される
  3. マクロ定義外では,このルールは適用されない

3は簡単だとしても,1と2については例が必要であろう.次のコードを考える.

\def\testA#1{#1##}
\show\testA

これを実行すると次を得る.

> \testA=macro:
#1->#1##.

一連の流れを考える.まず,\testAが定義される.このとき,#の マクロ定義中のルールが適用され,#1が第1引数を表すようになる.このとき, 特別なトークン(パラメータトークン)が生成される.さらに###に 置換される.よって,\testAのパラメータテキストは「パラメータトークン#1」, 置換えテキストは「パラメータトークン#1#」になる.これが先のルール1である.

そして,このマクロを\showする.このとき,パラメータトークンが#1のような 形式に,#が先のルール3により2回出力される.よって,#1->#1##が出力される. このようなマクロ定義の逆の処理が行われることにより,\showしたときに直感的に わかりやすい表示が得られる.

さて,ツイートの回答に移ろう.最後に知るべきことは\unexpandedの中はマクロ定義中 でないということである.よって,\testの置換えテキストは#1から構成されることになる. これが出力されるので,先のルール2が適用され,#1->##1という出力を得る.

応用

さて,感のいい読者諸兄は,次のような疑問を持ったであろう.

  • カテゴリーコード6の文字が複数あったらどうなるのか
  • パラメータトークンをつくるときの1から9の文字になにか条件はないのか
  • \edefにおいて,マクロ展開とパラメータトークン生成のどちらが先に走るのか

これらの疑問を解決していこう.

カテゴリーコード6の文字が複数あったらどうなるのか

この疑問については,TeXブックのEXERCISE 20.7でほぼ解決される.幸いなことに,回答が ついている問題である.基本的には次のルールを覚えておけばいいだろう.

  • \letしたトークンも有効
  • ##から#への置き換え時に残るのは2つめのトークン
  • \letしたトークンはコンソール出力時の2回出力の対象外
  • 置換えテキスト内のパラメータトークンを出力する際のパラメータ文字は,パラメータテキストの最後のパラメータで使用された文字

次の例を見てみよう.

\let\S=#       % \Sは#と同等
\catcode`\+=6  % +もパラメータ文字に
\def\foo#1\S2+3#{#++##\S\S2}
\show\foo

次の出力が得られる.

> \foo=macro:
#1#2+3{->++##\S +2{.

パラメータテキストでは,\S#と同じ扱いになっていることがわかるだろう. 置換えテキストでは#++に,+##に,#\S\Sに置き換えられる. そして,+++として,###として出力される.さらに,置換えテキストの パラメータトークン#2+2となって出力される.よって,先の出力が得られるのである.

パラメータトークンをつくるときの1から9の文字になにか条件はないのか

これはTeXブックに記載が見当たらなかったので,調べた限りの結果を示しておこう.

  • \letしたトークンは無効
  • カテゴリーコードが12でないときも無効

この例でわかるだろう.

\let\One=1
\catcode`\2=11
\def\foo#\One{#\One}
\def\bar#1#2{#2}

出力は次の通り.

! Parameters must be numbered consecutively.
<to be read again>
                   \One
l.3 \def\foo#\One
                 {#\One}
?
! Illegal parameter number in definition of \foo.
<to be read again>
                   \One
l.3 \def\foo#\One{#\One
                       }
?
! Parameters must be numbered consecutively.
<to be read again>
                   2
l.4 \def\bar#1#2
                {#2}
?
! Illegal parameter number in definition of \bar.
<to be read again>
                   2
l.4 \def\bar#1#2{#2
                   }
?

なぜこのような動きをするのかについては未調査である.

\edefにおいて,マクロ展開とパラメータトークン生成のどちらが先に走るのか

これは私もかなり混乱した*4.結論としては, マクロ展開が先に走る.次の例を見てみよう.

\def\firstoftwo#1#2{#1}
\edef\testA#1{\firstoftwo#91}
\show\testA

出力は次の通り.

> \testA=macro:
#1->#1.

この件に関しては,Twitterでかなりのご迷惑をおかけしました.お詫び申し上げます.

まとめ

TeXは難しい.

*1:出力に不要な空白が入るように見えるのはコントロールシーケンスの出力規則のせい

*2:実はアラインメントでも#を使うのだが,本記事の対象外である

*3:##を定義に含めようとするとエラーになる.私は回避策を知らない

*4:hak7a3のTwitterを参照するとよくわかる