カテゴリコードと \makeatletter

$ という文字そのものに、数式を開く力はありません。その役目は ! にも渡せて——渡したとたん !x_1! がエラーひとつ出さずに数式として組まれます——役割を決めているのは文字ではなく カテゴリコード(catcode)、TeX が読み込んだ一文字ごとに与える 0〜15 の番号のほうです。LaTeX の内部命令が @ だらけなのも、\makeatletter が何をしているのかも、% が何も言わずに行の残りを飲み込むのも、この番号ひとつで説明が付きます。ここでは 16 個の割り当て表、\catcode による書き換え、\makeatletter の実際の中身(たった一行です)、そして \string\meaning\detokenize でトークンの正体を覗く方法までを扱います。

16 個のカテゴリコードと、既定でどの文字がどれか

カテゴリコードは 0 から 15 までの 16 種類 で、TeX が原稿を トークン の列に切り分ける段階——組版よりずっと手前の字句解析——で使われます。読み込んだ文字が命令の始まりなのか、グループを開くのか、ただのインクなのかは、この番号だけで決まります。日常的に意識するのは、命令を始める 0、グループの 12、数式の 3、表のセル区切りの 4、段落を作る行末の 5、マクロの引数を表す 6、上付き・下付きの 78、地の文を構成する 101112、そしてコメントの 14 です。

catcode役割既定の文字(LaTeX の文書内)
0エスケープ(命令の開始)バックスラッシュ \
1グループ開始左波括弧 {
2グループ終了右波括弧 }
3数式モードの切り替えドル記号 $
4桁揃え(表のセル区切り)アンパサンド &
5行末(空行が改段落になる)改行(リターン、文字コード 13)
6パラメータ(マクロの引数)ハッシュ #
7上付き文字キャレット ^
8下付き文字アンダースコア _
9無視される文字(読み飛ばす)LaTeX では既定でどの文字にも割り当てられていません
10スペース空白とタブ(文字コード 9)
11英字(命令名を構成できる)az, AZ
12その他(ただのインク)数字・句読点・@ など残り全部
13アクティブ文字(それ自体が命令)チルダ ~ と制御文字 1〜31
14コメント(行の残りを捨てる)パーセント %
15無効な文字(エラーになる)ヌル文字(0)とデリート文字(127)

この表の 9 行目には注意してください。多くの解説が「catcode 9 はヌル文字」と書きますが、それは 素の TeX(IniTeX)の話 です。TeX Live 2024 の LaTeX で 0〜255 の全文字を走査すると、catcode 9 を持つ文字は一つもありません。ヌル文字は latex.ltx で 15(無効)に移されており、9 は空席のまま——16 種のうち唯一、既定の住人がいないカテゴリです。使う側が自分で割り当てて初めて働きます。実際 listings パッケージは、自分自身のソースを読み込むあいだタブ・改ページ・復帰を catcode 9 に落として読み飛ばしています。逆に制御文字 1〜31 が アクティブ(13) なのは、UTF-8 入力を処理するための現代 LaTeX の仕掛けです。

\catcode で文字の役割を書き換える

文字とカテゴリの対応は固定ではなく、プリミティブ \catcode で書き換えられます。書き方は「\catcode のあとに左引用符(バッククォート)と対象の文字、=、新しい番号」。読み出しは \the\catcode に同じ形を続けるだけです。次のコードは ! に数式モードの役目(catcode 3)を与えたもので、エラーはゼロ!x_1!!\frac{a}{b}! も正しく数式として組まれます。$ が数式を開くのは「ドル記号だから」ではなく「catcode 3 だから」——それを一行で証明できるのが、この仕組みの面白いところです。

document.tex
\documentclass{article}
\begin{document}
\catcode`\!=3          % hand the math-shift job to "!"
!x_1! and !\frac{a}{b}!  % typeset as mathematics, zero errors
\catcode`\!=12         % give it back to "other"
Back to normal!

% read a catcode back
\the\catcode`\$        % 3
\the\catcode`\%        % 14
\the\catcode`A         % 11
\end{document}

ただし、これは 効きすぎる 道具です。カテゴリはトークン化の時点で凍結されるので、\catcode を書き換えた瞬間から以降の読み込み全体の意味が変わります。戻し忘れれば、まったく無関係な箇所が壊れます。しかも書き換えの影響はグループ({ } や環境)を抜ければ元に戻るため、「プリアンブルでは効いたのに本文で効かない」といった混乱も起きがちです。生の \catcode を書くのは、後述の \makeatletter のような 定型の包み に収まらないときの最後の手段だと考えてください。なお、この書き換えを日常的にやっているパッケージもあります。babel のドイツ語オプションを読み込むと "catcode 13(アクティブ) になり、"a が ä に、二重引用符+左引用符が „ になります——ドイツ語文書で引用符の挙動が急に変わって見えるのは、これが理由です。

% が黙って行を飲む理由、& がエラーになる理由

答えは catcode の値そのものにあります。% は catcode 14——「この文字から行末までを捨てよ」という指示で、捨てたことを一切報告しません。だから Only 50% of the sample survived. と書いて次の行に The rest did not. と続けると、出力は Only 50The rest did not. になり、エラーも警告もゼロ。文章が一行分消えているのに、ログはまったくの静寂です。これは LaTeX でもっとも見つけにくい事故の一つで、パーセント記号を本文に書くときは必ず \% とエスケープしてください。一方 & は catcode 4(表のセル区切り)なので、表の外に書くと ! Misplaced alignment tab character &. と即座に叱られます。$ は catcode 3 で、閉じ忘れれば ! Missing $ inserted になります。

document.tex
% catcode 14: everything after % on this line is discarded, silently
Only 50% of the sample survived.
The rest did not.
% output: "Only 50The rest did not."  -- no error, no warning

% catcode 4 outside a table:
Smith & Sons   % ! Misplaced alignment tab character &.

% the fix in running text:
Only 50\% of the sample survived.

catcode 11 が命令名の切れ目を決める

TeX の 制御語(control word)——\section のような名前付きの命令——は、catcode 0 の文字(ふつう \)に続く catcode 11 の文字の並び として認識されます。つまり命令名を作れるのは、原則 catcode 11 の文字だけ。\section の名前が section で終わるのは、その次に来る空白や { が catcode 11 ではないからです。数字や句読点は catcode 12(その他) なので、\a2 は「\a という命令+文字 2」と読まれます。ここを逆手に取って、ある文字の catcode を 11 に変えれば、その文字を命令名の一部にできる——次節の @ の物語はこの一手で始まります。(制御語の直後の空白が消えることや、\LaTeXlogo が未定義になることといった利用者側の帰結は「記法のルール」のページが担当です。)

\makeatletter とは何か——中身はたった一行

\makeatletter@ の catcode を 11 にするだけの命令 で、対になる \makeatother は 12 に戻すだけです。latex.ltx の定義は正味それぞれ一行しかなく、名前のとおり「@ を letter にする/other にする」という意味です。なぜそれが要るのか。LaTeX のカーネルとパッケージの 内部命令 には、\@startsection(節見出しの組み立て)、\@ifnextchar(次のトークンを覗いて分岐)、\@maketitle(タイトル領域)のように、名前に @ を含むものが大量にあるからです。.sty.cls を読み込むあいだ、\usepackage\documentclass@ を catcode 11 に切り替えてくれているので、パッケージの中ではこれらが一つの名前として通ります。

latex.ltx
% the whole of \makeatletter and \makeatother, in the LaTeX kernel
\DeclareRobustCommand\makeatletter{\catcode`\@11\relax}
\DeclareRobustCommand\makeatother{\catcode`\@12\relax}

% two abbreviations you will meet in internal code (note the values)
\newdimen\p@ \p@=1pt   % 1pt -- "this saves macro space and time"
\newdimen\z@ \z@=0pt   % 0pt, and doubles as the integer 0

ふつうの文書の中では @catcode 12(その他) です。だから本文やプリアンブルにそのまま \p@ と書くと、TeX はそれを「\p という命令+文字 @」と読み、! Undefined control sequence. を出します。エラー表示は l.3 Value: \p で切れて次の行が @ から始まる——命令名がどこで途切れたかが目に見える形で示されるので、この症状を一度覚えておくと診断が速くなります。ちなみに \p@1pt\z@ が 0pt です(0pt のつもりで \p@ を書く取り違えが起きがちです)。latex.ltx に残る「マクロの領域と時間を節約する」というコメントは、メモリが貴重だった時代の名残がそのまま現役で動いている証拠でもあります。

error
! Undefined control sequence.
l.3 Value: \p
             @
The control sequence at the end of the top line
of your error message was never \def'ed.

\makeatletter が必要な場面・書いてはいけない場面

実務的な判断はこの一文に尽きます。.tex 文書(ほぼ常にプリアンブル)で、名前に @ を含む内部命令を書くときだけ必要。それ以外では書きません。とくに .sty.cls の中には書かないでください——読み込み中はすでに @ が catcode 11 なので不要ですし、余計な \makeatother が後続の処理を壊しかねません。典型的な用途は、クラスが定義した内部マクロをプリアンブルで少しだけ手直しすることです。次の例は、タイトル領域を組む内部マクロ \@maketitle を包みの中で再定義しています。

document.tex
\documentclass{article}

\makeatletter                 % @ becomes a letter here
\renewcommand{\@maketitle}{%   % redefine the internal title block
  \begin{center}
    {\LARGE\bfseries \@title}\par
    \vspace{1ex}{\large \@author}\par
  \end{center}%
}
\makeatother                  % ... and goes back to "other" here

\title{Category codes}
\author{A. Author}
\begin{document}
\maketitle
\end{document}

落とし穴は二つです。一つ目は \makeatother の書き忘れ。閉じ忘れると @ が英字のまま後続に持ち越され、本文中のメールアドレスや、@ を特別扱いする一部パッケージが壊れます。\makeatletter を書いたら必ず対で閉じる、と機械的に覚えてください。二つ目は、そもそも 内部命令に触らずに済む道がないか を確かめないこと。公開命令を \renewcommand で上書きできるなら、あるいは目的の機能を提供する正規のパッケージがあるなら、そちらが常に安全です。内部命令はパッケージの更新で予告なく変わり、\makeatletter で書いた再定義は次のリリースで静かに効かなくなります。

なぜ \verblistings は catcode を触るのか

ソースをそのまま印字するには、特殊文字が特殊であることをやめさせる しかありません。\verb の定義はまさにそれをしていますし、verbatim 環境もブロック全体に同じことをします。\dospecials が並べる 11 個の文字——空白・\{}$&#^_%~——に \@makeother を適用し、全部を catcode 12(その他)へ落とす。しかも \@noligs が続けて六文字(左引用符・<>,・アポストロフィ・-)を catcode 13(アクティブ) にし、左引用符二つが丸い開き引用符に化けるようなリガチャを抑えます。つまり \verb は catcode を 下げると同時に上げる 二段構えの操作で、全体が \bgroup に包まれているので終われば自動的に元へ戻ります。

latex.ltx
\def\@makeother#1{\catcode`#1=12\relax}
\def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&%
  \do\#\do\^\do\_\do\%\do\~}

\def\verb{\relax\ifmmode\hbox\else\leavevmode\null\fi
  \bgroup                                  % everything below is local
    \verb@eol@error \let\do\@makeother \dospecials   % all 11 -> catcode 12
    \verbatim@font\@noligs                 % ` < > , ' - -> catcode 13
    \language\l@nohyphenation
    \@ifstar\@sverb\@verb}

ここから、\verb の有名な制限が導けます。\verb は他の命令の引数の中では使えません。引数はマクロが呼ばれる前にトークン化されてしまうので、\verb が catcode を変えるころには手遅れなのです。\footnote{code: \verb|\foo_bar|}! Undefined control sequence.\foo が本物の命令として読まれた)に続いて ! Missing $ inserted._ が下付き文字として読まれた)を出し、マクロの引数の中では ! Extra }, or forgotten $. まで付いてきます。回避策として listings\lstinline があり、こちらは \section{A \lstinline|x_1| heading} がエラーゼロで通り、目次にもそのまま載ります。listings が自前で 40 か所以上 catcode を操作しているのは、まさにこの種の問題を引き受けるためです。

document.tex
% BREAKS: the argument is tokenized before \verb can act
\footnote{code: \verb|\foo_bar|}
%   ! Undefined control sequence.   <argument> ... \verb |\foo
%   ! Missing $ inserted.

% WORKS: \lstinline survives inside a moving argument
\usepackage{listings}
\section{A \lstinline|x_1| heading}   % zero errors, reaches the ToC

トークンの正体を見る — \string\meaning\detokenize

catcode の話が抽象的に感じられたら、実際に覗いてみる のが一番です。\meaning は catcode をそのまま言葉にして返します——\meaning Athe letter A(catcode 11 だから「letter」)、\meaning 7the character 7(catcode 12)、\meaning\bgroupbegin-group character {。この一言で、その文字がいま何者なのかが分かります。\string は命令をバックスラッシュ込みの文字列に開き、\detokenize は引数全体を印字可能な文字列に変えます(### に倍化されるのはトークン列としての正確さを保つためです)。デバッグでは \show も便利で、定義をログに書き出して一時停止します。

命令することTeX Live 2024 での実測出力
\the\catcode文字の catcode を数値で読み出す\the\catcode+バッククォート+$3
\meaningトークンの正体を言葉で説明する\meaning Athe letter A\meaning 7the character 7
\string命令を文字列に開く(\ 込み)\string\frac\frac
\detokenize引数全体を印字可能な文字列に変える\detokenize{\frac{1}{2} #1}\frac {1}{2} ##1
\show定義をログに書き出して一時停止する\show\LaTeXmacro:->\protect \LaTeX

最後に実務の勘所を一つ。「なぜかこの文字だけおかしい」という不具合に当たったら、まず \the\catcode でその文字の番号を確かめてください。babelcsquoteslistingshyperref といったパッケージは、目的のために特定の文字をアクティブにしたり catcode 12 に落としたりします。症状が「特定の一文字だけ壊れる」形をしているなら、原因は九割方 catcode です。それが分かってしまえば、あとは読み込み順を変えるか、その文字を使わないか、パッケージの提供する正規の逃げ道(\%\verb の別の区切り記号、\lstinline)を使うかの三択に絞れます。