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

\csnameの挙動

この記事は TeX & LaTeX Advent Calendar 2015 の20日目の記事です。
昨日はumireonさんでした.明日はtermoshttさんです.

はじめに

今年もAdvent Calendarの季節がやってきた.今年の重点テーマは

「今さら人に聞けない、TeXのキホン」

だそうだ.そこで,今回はTeXの基本的なプリミティブである\csnameについて,その挙動を紹介しよう*1と思う.

基本的なこと

\csnameは,端的に言うと文字列から制御綴りを生み出すためのプリミティブであり,\csnameと\endcsnameの間にある文字トークン列を名前とする制御綴りに展開される.例えば,

\csname Large\endcsname

は\Largeと書いたものと同様に扱われる.これだけでは利便性がないが,中の文字列を動的に変更できるようにすると,使用する制御綴りを動的に選択できるようになる.次のようなマクロを考えるとわかりやすいだろう.

\newcommand{\hoge}[1]{\expandafter\show\csname#1\endcsname}

この\hogeというマクロは,マクロもしくはプリミティブの名前について,その意味をコンソールに出力する.\hoge{Large}とすると,(クラスファイルにもよるが)次のような出力が得られるはずである.

> \Large=\long macro:                
->\@setfontsize \Large \@xivpt {18}. 
<recently read> \Large               
                                     
l.4 \hoge{Large}                     

\csnameは便利なプリミティブであり,LaTeXでは\beginおよび\endの実装に使われていることで有名[要出典]である.簡単にいうと,\begin{hoge}は\hogeに,\end{hoge}は\endhogeに展開される.この実装に\csnameが使われている.

与えることが可能なトークン

The TeXbookでは,\csnameを次のように説明している.

When TeX expands \csname it reads to the matching \endcsname, expanding tokens as it goes; only character tokens should remain after this expansion has taken place.

すなわち,文字トークン以外が与えられるとエラーになる.同書では,\csname\TeX\endcsnameがエラーになる例として紹介されている*2

未定義な名称を使った時の挙動

\csnameで与えた名称が事前に定義されていない場合はどうなるのだろうか? The TeXbookの\csnameに関する記述には続きがある.

Then the "expansion" of the entire \csname...\endcsname text will be a single control sequence token, defined to be like \relax if its meaning is currently undefined.

そう,定義されていない場合は\relaxのように定義されるのである.もし与える名前が間違っていても無意味なものとして取り扱ってくれる.

しかし,この性質はTeXにおいて特異なものでもある.TeXは,マクロの世界とプリミティブの世界を厳格に区別していて,マクロ展開時には定義や代入を含むすべての副作用を発生させることができない,純粋な言語だ*3.にもかかわらず,\csnameだけはマクロ展開時に定義を行う.実は,この定義の動作自体も,他の定義と違っていたりする部分がある.ここからは,\csnameがマクロを\relaxに定義する際の挙動を見ていこう.

\relax自身が書き換えられている場合

TeXは色々なものをユーザが書き換え可能である.プリミティブもその対象で,\relaxを別のマクロに置き換えることができる.では,\relaxが書き変わっている状況で\csnameで未定義の制御綴りを呼び出したらどうなるか? 答えはオリジナルの\relaxが使用される.*4この動作は次のコードで試すことができる.

\show\relax
\def\relax{yukidaruma}
\let\hoge\relax\show\hoge
\expandafter\show\csname fuga\endcsname
\bye

これの出力は次のようになる.

> \relax=\relax.                                
l.1 \show\relax                                 
                                                
?                                               
> \hoge=macro:                                  
->yukidaruma.                                   
l.3 \let\hoge\relax\show\hoge                   
                                                
?                                               
> \fuga=\relax.                                 
<recently read> \fuga                           
                                                
l.4 \expandafter\show\csname fuga\endcsname     
                                                
?                                               

\relaxを代入した\hogeが書き変わった後の定義になっているのに対し,\fugaはオリジナルの\relaxになっている.つまり,あるコードで\relaxを書き換えられてしまっても,それを復元する手段が常にあるということである*5

\globaldefsとの組み合わせ

TeXには\globaldefsというパラメータがあり,このパラメータが正の値の場合,定義や代入は常に\globalが付与されたものとして扱われる.*6すなわち,グルーピングの枠を超えて,マクロが大域的に定義されるようになる.実は,\csnameによる代入はこの\globaldefsに関係なく常にローカルに行われるようである.次のコードを見てみよう.

\globaldefs=42
\show\hoge \show\fuga
\begingroup
\let\hoge\relax
\csname fuga\endcsname
\show\hoge \show\fuga
\endgroup
\show\hoge \show\fuga
\bye

結果は次のようになる.

> \hoge=undefined.              
l.2 \show\hoge                  
               \show\fuga       
?                               
> \fuga=undefined.              
l.2 \show\hoge \show\fuga       
                                
?                               
> \hoge=\relax.                 
l.6 \show\hoge                  
               \show\fuga       
?                               
> \fuga=\relax.                 
l.6 \show\hoge \show\fuga       
                                
?                               
> \hoge=\relax.                 
l.8 \show\hoge                  
               \show\fuga       
?                               
> \fuga=undefined.              
l.8 \show\hoge \show\fuga       
                                
?                               

\hogeは大域的に定義されているのがわかるが,\fugaはそうなっていない.

\afterassignmentとの関係

TeXは様々な場所にフックを仕掛けておくことができる.\afterassignmentは定義や代入の直後に対するフックである.しかし,\csname由来の代入では\afterassignmentは動かない.次のコードを見てみよう.

\def\hook{\show\expandafter}
\afterassignment\hook \let\hoge\relax
\afterassignment\hook \csname fuga\endcsname
\show\hoge
\show\fuga
\bye

実行すると,次の結果が得られる.

> \expandafter=\expandafter.                 
\hook ->\show \expandafter                   
                                             
l.2 \afterassignment\hook \let\hoge\relax    
                                             
?                                            
> \hoge=\relax.                              
l.4 \show\hoge                               
                                             
?                                            
> \fuga=\relax.                              
l.5 \show\fuga                               
                                             
?                                            

\fugaに\relaxが代入されているにも関わらず,\afterassignmentが起動しない.

おわりに

いささか,TeXの闇を見せたような気がするが,\csnameは便利なプリミティブである.ぜひ活用していってほしい.

*1:毎年のように重点テーマと関係ない記事書いていたことに目をつぶりつつ

*2:定義の中に\kernが入っている

*3:なのに,二つの世界の文法がほぼ同一.

*4:某言語のmethod_missing()のようなものには使えない.

*5:どこかの言語でundefinedを入手する手段に似ている

*6:逆に,負なら\globalが無視される.通常は0で,この時は\globalが付与されているかを考慮する