Le signe dollar n’a en lui-même aucun pouvoir d’ouvrir les mathématiques. Confiez ce rôle à ! et !x_1! sera composé comme une formule, sans la moindre erreur — car ce qui décide du rôle n’est pas le caractère mais son code de catégorie (catcode), ce nombre de 0 à 15 que TeX attache à chaque caractère lu. Ce seul nombre explique pourquoi les commandes internes de LaTeX regorgent de @, ce que fait réellement \makeatletter, et pourquoi % avale la fin d’une ligne sans un mot. Cette page parcourt les seize codes, la primitive \catcode qui les réécrit, la réalité tenant en une ligne de \makeatletter, et la façon d’examiner n’importe quel token avec \string, \meaning et \detokenize.
Les seize codes de catégorie et quel caractère reçoit lequel
Il existe seize codes de catégorie, numérotés de 0 à 15, et TeX les emploie à l’étape où la source est découpée en tokens : l’analyse lexicale, bien avant toute composition. Qu’un caractère commence une commande, ouvre un groupe ou ne soit que de l’encre, cela se règle par ce seul nombre. Au quotidien, on remarque surtout 0 (début de commande), 1 et 2 (groupement), 3 (mathématiques), 4 (séparateur de cellule dans les tableaux), 5 (la fin de ligne qui construit les paragraphes), 6 (marqueur d’argument d’une macro), 7 et 8 (exposants et indices), le trio 10, 11, 12 qui compose le texte courant, et 14 (commentaires).
| catcode | Rôle | Caractères par défaut (dans un document LaTeX) |
|---|---|---|
0 | Échappement — début d’une commande | Antislash \ |
1 | Début de groupe | Accolade gauche { |
2 | Fin de groupe | Accolade droite } |
3 | Bascule mathématique | Signe dollar $ |
4 | Tabulation d’alignement — séparateur de cellule | Esperluette & |
5 | Fin de ligne — une ligne vide crée un paragraphe | Retour chariot (caractère 13) |
6 | Paramètre — argument de macro | Dièse # |
7 | Exposant | Accent circonflexe ^ |
8 | Indice | Tiret bas _ |
9 | Ignoré — sauté comme s’il était absent | aucun caractère par défaut dans LaTeX |
10 | Espace | Espace et tabulation (caractère 9) |
11 | Lettre — peut former un nom de commande | a–z, A–Z |
12 | Autre — simple encre | Chiffres, ponctuation, @ et tout le reste |
13 | Actif — le caractère est lui-même une commande | Tilde ~ et caractères de contrôle 1–31 |
14 | Commentaire — jette la fin de la ligne | Pourcent % |
15 | Invalide — déclenche une erreur | Caractère nul (0) et delete (127) |
La ligne 9 mérite attention. La plupart des exposés écrivent « catcode 9, c’est le caractère nul », mais cela décrit TeX nu (IniTeX). En balayant les 256 caractères sous LaTeX avec TeX Live 2024, aucun n’a le catcode 9 : latex.ltx déplace le caractère nul vers 15 (invalide) et laisse 9 vide — la seule des seize catégories sans occupant par défaut. Elle ne sert à rien tant qu’on ne lui affecte pas soi-même un caractère. Le package listings fait précisément cela : pendant qu’il lit sa propre source, il abaisse tabulation, saut de page et retour chariot au catcode 9 pour qu’ils soient ignorés. À l’autre extrémité, si les caractères de contrôle 1–31 sont actifs (13), c’est la machinerie du LaTeX moderne pour traiter l’entrée UTF-8.
Réécrire le rôle d’un caractère avec \catcode
La correspondance entre caractères et catégories n’est pas figée : la primitive \catcode la réécrit. La forme est « \catcode, puis un accent grave, puis le caractère visé, puis =, puis le nouveau numéro » ; \the\catcode suivi du même accent grave et du même caractère relit la valeur courante. Le code ci-dessous confie le rôle de bascule mathématique (catcode 3) à !. Il y a zéro erreur, et !x_1! comme !\frac{a}{b}! sortent en mathématiques correctes. $ ouvre les mathématiques non pas « parce que c’est un dollar » mais « parce qu’il a le catcode 3 » — et le charme de cette mécanique tient à ce qu’une seule ligne le démontre.
\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}C’est toutefois un outil qui fonctionne trop bien. Les catégories sont figées à la tokenisation : dès l’instant où l’on en réécrit une, tout ce qui est lu ensuite change de sens, et si l’on oublie de restaurer, c’est un endroit sans rapport qui casse. La modification est en outre annulée à la fermeture d’un groupe ({ } ou environnement), d’où une confusion propre : « ça marchait dans le préambule, pas dans le corps ». Considérez un \catcode brut comme le dernier recours, réservé aux cas qui n’entrent pas dans une enveloppe établie comme le \makeatletter ci-dessous. Les packages, eux, le font couramment : chargez babel avec son option allemande et " devient catcode 13 (actif), de sorte que "a donne ä et qu’un guillemet droit suivi d’un accent grave donne „. C’est pourquoi les guillemets changent soudain de comportement dans un document allemand.
Pourquoi % mange une ligne en silence et pourquoi & déclenche une erreur
La réponse tient à la valeur du catcode elle-même. % a le catcode 14 — « jeter tout d’ici à la fin de la ligne » — et il ne signale jamais ce qu’il a jeté. Ainsi Only 50% of the sample survived. suivi à la ligne de The rest did not. imprime Only 50The rest did not., avec zéro erreur et zéro avertissement. Une ligne de texte a disparu et le journal reste parfaitement muet. C’est l’un des accidents les plus difficiles à repérer dans tout LaTeX ; dans le texte courant, un signe pourcent s’écrit toujours \%. En revanche & a le catcode 4 (séparateur de cellule), si bien qu’en écrire un hors d’un tableau vaut aussitôt un ! Misplaced alignment tab character &. Et $ a le catcode 3, d’où le ! Missing $ inserted lorsqu’on oublie de le refermer.
% 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.Le catcode 11 décide où s’arrête un nom de commande
Un mot de contrôle TeX — une commande nommée comme \section — est reconnu comme un caractère de catcode 0 (généralement \) suivi d’une suite de caractères de catcode 11. Seuls les caractères de catcode 11 peuvent normalement former un nom de commande, et c’est précisément pourquoi \section s’arrête à section : le caractère suivant, espace ou {, n’est pas de catcode 11. Chiffres et ponctuation sont de catcode 12 (autre), si bien que \a2 se lit « la commande \a, puis le caractère 2 ». Retournez cela et vous obtenez le geste clé : changez le catcode d’un caractère en 11 et il devient utilisable dans les noms de commandes. L’histoire de @ de la section suivante commence exactement là. (Les conséquences côté utilisateur — l’espace avalé après un mot de contrôle, \LaTeXlogo devenu indéfini — relèvent de la page sur les règles de syntaxe.)
Ce qu’est \makeatletter : une seule ligne de code
\makeatletter ne fait rien d’autre que porter le catcode de @ à 11, et son pendant \makeatother ne fait que le ramener à 12. Dans latex.ltx, chaque définition tient littéralement en une ligne, et les noms le disent : faire de @ une lettre, faire de @ un « autre ». Pourquoi en a-t-on besoin ? Parce que le noyau et les packages de LaTeX regorgent de commandes internes dont le nom contient @ : \@startsection (assemblage d’un titre de section), \@ifnextchar (coup d’œil au token suivant pour brancher), \@maketitle (le bloc de titre) et des centaines d’autres. Pendant que \usepackage et \documentclass lisent un fichier .sty ou .cls, ils basculent @ en catcode 11, si bien qu’à l’intérieur d’un package ces noms se lisent comme des commandes uniques.
% 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 0Dans un document ordinaire, @ a le catcode 12 (autre). Écrivez \p@ dans le texte ou le préambule et TeX lit « la commande \p, puis le caractère @ », puis s’arrête sur ! Undefined control sequence. L’affichage de l’erreur se coupe à l.3 Value: \p et reprend à la ligne suivante par @ : on voit littéralement où le nom de commande a été tranché, symptôme qu’il vaut la peine de retenir pour diagnostiquer vite. Au passage, \p@ vaut 1pt ; c’est \z@ qui signifie 0pt, et confondre les deux est une erreur fréquente. Le commentaire toujours présent dans latex.ltx — l’abréviation « économise de l’espace macro et du temps » — est un vestige d’une époque de mémoire rare, encore en service tel quel.
! 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.Quand \makeatletter est nécessaire, et quand il ne faut pas l’écrire
La règle pratique tient en une phrase : il n’est nécessaire que dans un document .tex — presque toujours le préambule — lorsqu’on écrit une commande interne dont le nom contient @. Nulle part ailleurs. En particulier, ne l’écrivez pas dans un .sty ou un .cls : pendant leur lecture, @ est déjà de catcode 11, c’est donc inutile, et un \makeatother de trop peut casser la suite. L’usage typique est un ajustement léger, dans le préambule, d’une macro interne définie par la classe. L’exemple ci-dessous redéfinit \@maketitle, la macro qui met en page le bloc de titre, à l’intérieur de l’enveloppe.
\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}Il y a deux pièges. Le premier est d’oublier \makeatother : si vous ne le fermez pas, @ reste une lettre pour tout ce qui suit et casse une adresse mail dans le texte ou tout package qui traite @ spécialement. Rendez le geste mécanique : on ouvre avec l’un, on ferme avec l’autre. Le second est de ne pas vérifier si l’on peut éviter complètement de toucher aux internes. Si une commande publique peut être redéfinie par \renewcommand, ou si un package en règle fournit déjà l’effet voulu, cette voie est toujours plus sûre. Les commandes internes changent sans préavis lors d’une mise à jour, et une redéfinition écrite dans \makeatletter cessera silencieusement d’agir à la version suivante.
Pourquoi \verb et listings doivent toucher aux catcodes
Pour imprimer du code source exactement tel qu’il est écrit, il n’y a qu’une voie : empêcher les caractères spéciaux d’être spéciaux. C’est littéralement ce que fait \verb, et l’environnement verbatim fait de même sur tout un bloc. Il applique \@makeother aux onze caractères énumérés par \dospecials — espace, \, {, }, $, &, #, ^, _, %, ~ — les faisant tous descendre au catcode 12 (autre). Puis \@noligs va dans l’autre sens et rend six caractères de plus de catcode 13 (actifs) : l’accent grave, <, >, ,, l’apostrophe et -, afin que deux accents graves ne se fondent pas discrètement en un guillemet ouvrant typographique. \verb abaisse donc et relève les catcodes à la fois ; comme le tout est enfermé dans un \bgroup, tout revient automatiquement à la fin.
\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}De là découle la fameuse restriction de \verb : \verb ne peut pas s’employer dans l’argument d’une autre commande. L’argument est tokenisé avant même l’appel de la macro ; quand \verb change un catcode, il est bien trop tard. \footnote{code: \verb|\foo_bar|} donne ! Undefined control sequence. (car \foo a été lu comme une vraie commande) puis ! Missing $ inserted. (car _ a été lu comme un indice) ; dans un argument de macro s’y ajoute ! Extra }, or forgotten $. La sortie de secours est \lstinline de listings : \section{A \lstinline|x_1| heading} compile sans la moindre erreur et passe même dans la table des matières. La quarantaine de manipulations de catcodes dans listings sert exactement à absorber ce genre de problèmes.
% 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 ToCVoir l’intérieur d’un token : \string, \meaning, \detokenize
Si les codes de catégorie paraissent encore abstraits, le remède le plus rapide est de les regarder. \meaning énonce le catcode en toutes lettres : \meaning A donne the letter A (catcode 11, d’où « letter »), \meaning 7 donne the character 7 (catcode 12), et \meaning\bgroup donne begin-group character {. Une seule phrase dit ce qu’un caractère est en ce moment. \string décompose une commande en ses caractères, antislash compris, et \detokenize transforme un argument entier en texte imprimable — le # qui ressort en ## se dédouble pour rester fidèle en tant que liste de tokens. Pour le débogage, \show est également commode : il écrit une définition dans le journal et met en pause.
| Commande | Ce qu’elle fait | Sortie mesurée sous TeX Live 2024 |
|---|---|---|
\the\catcode | Lit le catcode d’un caractère sous forme de nombre | \the\catcode + accent grave + $ donne 3 |
\meaning | Décrit un token en toutes lettres | \meaning A donne the letter A ; \meaning 7 donne the character 7 |
\string | Décompose une commande en caractères, antislash compris | \string\frac donne \frac |
\detokenize | Transforme un argument entier en texte imprimable | \detokenize{\frac{1}{2} #1} donne \frac {1}{2} ##1 |
\show | Écrit une définition dans le journal et met en pause | \show\LaTeX donne macro:->\protect \LaTeX |
Un dernier réflexe pratique. Face à un bug de la forme « il n’y a que ce caractère-là qui déraille », commencez par vérifier son numéro avec \the\catcode. Des packages comme babel, csquotes, listings ou hyperref rendent certains caractères actifs ou les abaissent au catcode 12 pour leurs propres besoins. Si le symptôme se limite à un seul caractère, la cause est un code de catégorie neuf fois sur dix. Une fois cela su, la réparation se réduit à trois options : modifier l’ordre de chargement, éviter le caractère, ou emprunter la sortie de secours que le package fournit déjà (\%, un autre délimiteur pour \verb, \lstinline).