TeX by Topic ゼミ (3) uppercase と lowercase

2017-11-15   #TeX 

前回は TeX のカテゴリーコードに関わる話を網羅的にまとめました.今回は少し分量を抑えて,2つのプリミティブ \uppercase\lowercase に焦点を絞ってみたいと思います.

基本的な使い方

\uppercase, \lowercase は文字通りそれぞれトークン列の「大文字化」と「小文字化」を行うプリミティブです(「大文字化・小文字化」の厳密な意味については次節で解説します).

\uppercase{Foo} %=>FOO
\lowercase{Bar} %=>bar

これだけならまるで「普通のマクロ」のように使うことができそうに思えますが,実際にはこれらのプリミティブはもう少し特殊な挙動を示します.すなわち,これらの命令は「展開されて,引数を大文字化・小文字化したトークン列を生成する」のではなく「実行すると,引数の中で大文字化・小文字化すべき文字トークンの文字コードを別の文字コードに変換する.一方,文字トークン以外のトークンやすべての文字トークンのカテゴリーコードは変更しない」ということを行います.

このことは,言葉で説明しても少々わかりにくいと思うので,下にいくつか例を挙げてみます.

例1:\expndafter との連携

マクロ \foo

\def\foo{foo}

のように定義されている状況で \uppercase\foo を用いて “FOO” という出力を得たい場合を考えます.普通に

\uppercase{\foo} %=>foo

とすると,\uppercase の引数内の \foo は制御綴(=文字トークンではない)なので,\uppercase は特に何の効果ももたらしません.一方で,\uppercase は展開可能なマクロではないので,通常のマクロに対する展開制御のように \uppercase の前に \expandafter を置いても無意味です.

今のケースで目的を遂げるためには \uppercase の直後に \expandafter を1つ置くとうまくいきます.

\uppercase\expandafter{\foo} %=>FOO

例2:\csname との連携

\uppercase は(大文字化すべき)文字トークンを大文字化する一方で,制御綴はそのままにするので

\uppercase{\csname foo\endcsname}

のように書くと \FOO と書くのと同様の効果が得られます.

一方 \csname ... \endcsname の間には展開できないトークンを置くことはできないので,当然「実行される」プリミティブである \uppercase も置くことはできません.したがって次のコードはエラーになります.

\csname\uppercase{foo}\endcsname %<-error

\uccode\lccode

これまで何度か「大文字化・小文字化」という表現を使ってきましたが,ある文字コードをどんな文字コードに大文字化あるいは小文字化するかは TeX 処理系の built-in な設定で決められているわけではありません.大文字化・小文字化の操作で,どの文字コードを何の文字コードに変換するのかは,それぞれ \uccode, \lccode というプリミティブで指定することが可能です.

\uccode<code>=<uccode>
\lccode<code>=<lccode>

この設定によって,文字コード <code> に対応する文字は \uppercase 利用時は <uccode> の文字に,\lowercase の場合は <lccode> の文字に変換されます.ただし <uccode><lccode> が0に設定されている場合,変換は行われません.

\lowercase トリック

さて,ここからやっと本稿の本題に入ります.これまで見てきたように \uppercase, \lowercase は「カテゴリーコードはそのままにして,文字コードだけを別のものに変換する」という動作をします.このことを利用すると,通常の方法では作ることが難しい「特殊な文字トークン(文字コードとカテゴリーコードの組)」を作り出すことができます.

応用例1:\newif の定義

\lowercase トリックは,有名なところでは \newif マクロを定義する際に利用できます.ご存知のように,\newif 命令は

\newif\ifhoge

という記述によって

\def\hogetrue{\let\ifhoge=\iftrue}
\def\hogefalse{\let\ifhoge=\iffalse}
\hogefalse

と等価な働きをする命令です.このマクロを定義するためにまず \[email protected] という命令を定義します.

{\lccode`1=`i \lccode`2=`f \lowercase{\gdef\[email protected]{}}}

1 および 2 のカテゴリーコードは12なので,上の \gdef\[email protected]{} において 12 の部分はカテゴリーコード12のトークン列です(したがって,この部分は \gdef のパラメタテキストとなります).これが \lowercase の効果によって,カテゴリーコードは12のまま文字コードだけがそれぞれ if のそれになります.すなわち,\[email protected] は直後に続くカテゴリーコード12の if という文字トークン列を消し去るマクロです.

あとは,\newif 命令本体と内部マクロ \@if の定義を見ればその使用法がわかります.

\def\newif#1{\[email protected]\escapechar \escapechar\[email protected]
  \expandafter\expandafter\expandafter
    \def\@if#1{true}{\let#1=\iftrue}%
  \expandafter\expandafter\expandafter
    \def\@if#1{false}{\let#1=\iffalse}%
  \@if#1{false}\escapechar\[email protected]}
\def\@if#1#2{\csname\expandafter\[email protected]\string#1#2\endcsname}

重要な部分を抜粋して解説すると,まず \newif の定義の1行目で \escapechar が-1に設定されていることがわかります.その上で \@if の定義を見ると \string#1 の展開結果を先ほどの \[email protected] 命令に与えています.ここで #1\newif\ifhoge の形で与えられる \ifhoge なので,\string の効果によってカテゴリーコード12のトークン列 ifhoge\[email protected] の後に続くことになります.つまり,このトークン列の冒頭 if\[email protected] 命令によって消去され,最終的に \@if 命令は \hogetrue\hogefalse という制御綴に展開されるわけです.

応用例2:任意の文字コードを出力する

\lowercase トリックは狙った特殊な文字トークンを作り出すという用途のみならず,任意の文字コードを外部に出力したい場合などにも役立ちます.

以下は,TeX by Topic に掲載されている整数レジスタ \mycount に格納されている値の文字コードを端末に出力するシンプルな例です.

\lccode`a=\mycount \chardef\terminal=16
\lowercase{\write\terminal{a}}

拙作の Whitesnowman パッケージでは,このテクニックをより汎用的な形で使うため,次のようなサブルーチンを定義しています.

\begingroup
  \catcode0=12\relax
  \global\def\chrdef#1#2{%
    \begingroup\lccode0=#2\relax
    \lowercase{\endgroup\def#1{^^@}}}%
\endgroup

プリミティブ \chardef で文字コードが代入された制御綴(「chardef トークン」といいます)は実行プロセッサによって処理されるものなので展開することができません.一方,この \chrdef マクロは \chardef に近いシンタックスで用いることができますが,定義される制御綴の実体がマクロとなるので「展開可能な chardef トークンもどき」を作ることが可能です.