El signo de dólar no tiene por sí mismo el poder de abrir las matemáticas. Basta con entregar ese papel a ! y !x_1! se compone como fórmula, sin un solo error, porque lo que decide el papel no es el carácter sino su código de categoría (catcode), ese número de 0 a 15 que TeX asigna a cada carácter que lee. Ese único número explica por qué las órdenes internas de LaTeX están llenas de @, qué hace realmente \makeatletter y por qué % se traga el resto de una línea sin decir palabra. Esta página recorre los dieciséis códigos, la primitiva \catcode que los reescribe, la realidad de una sola línea que hay tras \makeatletter, y cómo examinar cualquier token con \string, \meaning y \detokenize.
Los dieciséis códigos de categoría y qué carácter recibe cada uno
Hay dieciséis códigos de categoría, numerados del 0 al 15, y TeX los usa en la etapa en que corta la fuente en tokens: el análisis léxico, mucho antes de componer nada. Que un carácter inicie una orden, abra un grupo o sea solo tinta se decide con ese único número. En el trabajo diario se notan 0 (inicia una orden), 1 y 2 (agrupación), 3 (matemáticas), 4 (el separador de celdas en tablas), 5 (el fin de línea que construye párrafos), 6 (el marcador de argumento de una macro), 7 y 8 (superíndices y subíndices), el trío 10, 11, 12 que forma el texto corriente, y 14 (comentarios).
| catcode | Papel | Caracteres por defecto (dentro de un documento LaTeX) |
|---|---|---|
0 | Escape: inicia una orden | Barra invertida \ |
1 | Inicio de grupo | Llave izquierda { |
2 | Fin de grupo | Llave derecha } |
3 | Cambio a matemáticas | Signo dólar $ |
4 | Tabulación de alineación: separador de celdas | Ampersand & |
5 | Fin de línea: una línea en blanco crea un párrafo | Retorno de carro (carácter 13) |
6 | Parámetro: argumento de macro | Almohadilla # |
7 | Superíndice | Circunflejo ^ |
8 | Subíndice | Guion bajo _ |
9 | Ignorado: se salta como si no existiera | ningún carácter por defecto en LaTeX |
10 | Espacio | Espacio y tabulador (carácter 9) |
11 | Letra: puede formar un nombre de orden | a–z, A–Z |
12 | Otro: pura tinta | Dígitos, puntuación, @ y todo lo demás |
13 | Activo: el carácter es en sí una orden | Tilde ~ y caracteres de control 1–31 |
14 | Comentario: descarta el resto de la línea | Porcentaje % |
15 | Inválido: produce un error | Carácter nulo (0) y delete (127) |
Conviene leer con atención la fila del 9. La mayoría de las explicaciones dicen «el catcode 9 es el carácter nulo», pero eso describe TeX desnudo (IniTeX). Al recorrer los 256 caracteres en LaTeX con TeX Live 2024, ninguno tiene catcode 9: latex.ltx traslada el carácter nulo al 15 (inválido) y deja el 9 vacío, la única de las dieciséis categorías sin habitante por defecto. No hace nada hasta que uno le asigna un carácter. El paquete listings hace justo eso: mientras lee su propia fuente, baja tabulador, salto de página y retorno de carro al catcode 9 para saltárselos. En el otro extremo, que los caracteres de control 1–31 sean activos (13) es la maquinaria del LaTeX moderno para tratar la entrada UTF-8.
Reescribir el papel de un carácter con \catcode
La correspondencia entre caracteres y categorías no es fija: la primitiva \catcode la reescribe. La forma es «\catcode, luego un acento grave, luego el carácter objetivo, luego =, luego el número nuevo»; \the\catcode seguido del mismo acento grave y carácter devuelve el valor actual. El código siguiente entrega el papel de cambio a matemáticas (catcode 3) a !. Hay cero errores, y tanto !x_1! como !\frac{a}{b}! salen como matemáticas correctas. $ abre las matemáticas no «por ser un dólar» sino «por tener catcode 3», y el encanto de esta maquinaria está en que una sola línea lo demuestra.
\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}Ahora bien, es una herramienta que funciona demasiado bien. Las categorías se congelan en la tokenización, así que desde el momento en que se reescribe una, todo lo leído después cambia de sentido; si se olvida restaurarla, se rompe algo totalmente ajeno. Además el cambio se deshace al cerrarse un grupo ({ } o entorno), lo que genera su propia confusión: «funcionaba en el preámbulo pero no en el cuerpo». Considere un \catcode en crudo el último recurso, para casos que no caben en una envoltura establecida como el \makeatletter de más abajo. Los paquetes, en cambio, lo hacen de forma rutinaria: cargue babel con su opción alemana y " pasa a catcode 13 (activo), de modo que "a da ä y unas comillas rectas seguidas de acento grave dan „. Por eso las comillas cambian de comportamiento de repente en un documento alemán.
Por qué % se come una línea en silencio y & produce un error
La respuesta está en el propio valor del catcode. % tiene catcode 14 —«descarta todo desde aquí hasta el fin de línea»— y nunca informa de lo que ha descartado. Así, Only 50% of the sample survived. seguido en la línea siguiente de The rest did not. imprime Only 50The rest did not., con cero errores y cero avisos. Ha desaparecido una línea de prosa y el registro guarda un silencio absoluto. Es uno de los fallos más difíciles de detectar en todo LaTeX; en el texto corriente, un signo de porcentaje se escribe siempre \%. En cambio & tiene catcode 4 (el separador de celdas), de modo que escribirlo fuera de una tabla produce de inmediato ! Misplaced alignment tab character &. Y $ tiene catcode 3, por lo que dejarlo sin cerrar da ! Missing $ inserted.
% 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.El catcode 11 decide dónde acaba un nombre de orden
Una palabra de control de TeX —una orden con nombre como \section— se reconoce como un carácter de catcode 0 (normalmente \) seguido de una serie de caracteres de catcode 11. Solo los caracteres de catcode 11 pueden formar normalmente el nombre de una orden, y por eso mismo \section termina en section: el carácter siguiente, un espacio o una {, no es de catcode 11. Los dígitos y la puntuación son catcode 12 (otro), así que \a2 se lee como «la orden \a, luego el carácter 2». Dele la vuelta y obtendrá el movimiento clave: cambie el catcode de un carácter a 11 y podrá usarlo dentro de nombres de orden. La historia de @ de la sección siguiente empieza justo ahí. (Las consecuencias para el usuario —el espacio que desaparece tras una palabra de control, \LaTeXlogo quedando indefinido— corresponden a la página de reglas de sintaxis.)
Qué es \makeatletter: una sola línea de código
\makeatletter no hace nada más que poner el catcode de @ en 11, y su pareja \makeatother no hace más que devolverlo a 12. En latex.ltx cada definición ocupa literalmente una línea, y los nombres lo dicen: haz de @ una letra, haz de @ un «otro». ¿Por qué hace falta? Porque el núcleo y los paquetes de LaTeX están llenos de órdenes internas cuyo nombre contiene @: \@startsection (montar un título de sección), \@ifnextchar (mirar el token siguiente para ramificar), \@maketitle (el bloque de título) y cientos más. Mientras \usepackage y \documentclass leen un .sty o .cls, cambian @ a catcode 11, de modo que dentro de un paquete esos nombres se leen como órdenes únicas.
% 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 0Dentro de un documento normal, @ tiene catcode 12 (otro). Escriba \p@ en el texto o el preámbulo y TeX leerá «la orden \p, luego el carácter @», y se detendrá con ! Undefined control sequence. La pantalla de error se corta en l.3 Value: \p y sigue en la línea siguiente con @: se ve literalmente dónde quedó truncado el nombre de la orden, así que vale la pena memorizar este síntoma para diagnosticar rápido. De paso: \p@ vale 1pt; el que significa 0pt es \z@, y confundirlos es un desliz habitual. El comentario que sigue en latex.ltx —que la abreviatura «ahorra espacio de macro y tiempo»— es un vestigio de una época de memoria escasa que sigue funcionando sin cambios.
! 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.Cuándo hace falta \makeatletter y cuándo no debe escribirse
La regla práctica cabe en una frase: solo hace falta dentro de un documento .tex —casi siempre el preámbulo— cuando se escribe una orden interna cuyo nombre contiene @. En ningún otro sitio. En particular, no lo escriba dentro de un .sty o .cls: mientras se leen, @ ya tiene catcode 11, así que es innecesario, y un \makeatother de más puede romper lo que venga después. El uso típico es un ajuste ligero, en el preámbulo, de una macro interna definida por la clase. El ejemplo siguiente redefine \@maketitle, la macro que compone el bloque de título, dentro de la envoltura.
\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}Hay dos trampas. La primera es olvidar \makeatother: si no se cierra, @ sigue siendo letra en todo lo que venga después y rompe una dirección de correo del texto o cualquier paquete que trate @ de forma especial. Hágalo mecánico: se abre con uno y se cierra con el otro. La segunda es no comprobar si se puede evitar tocar los internos por completo. Si una orden pública puede redefinirse con \renewcommand, o si ya existe un paquete en regla que ofrece lo que se busca, esa vía siempre es más segura. Las órdenes internas cambian sin aviso al actualizarse un paquete, y una redefinición escrita dentro de \makeatletter dejará de surtir efecto en silencio en la siguiente versión.
Por qué \verb y listings tienen que tocar los catcodes
Para imprimir código fuente exactamente como está escrito solo hay un camino: impedir que los caracteres especiales sean especiales. Es literalmente lo que hace \verb, y el entorno verbatim hace lo mismo con un bloque entero. Aplica \@makeother a los once caracteres que enumera \dospecials —espacio, \, {, }, $, &, #, ^, _, %, ~— y los baja todos a catcode 12 (otro). Después \@noligs va en sentido contrario y vuelve catcode 13 (activos) a seis más: el acento grave, <, >, ,, el apóstrofo y -, para que dos acentos graves no se fundan sin aviso en una comilla de apertura tipográfica. Así que \verb baja y sube catcodes a la vez, y como todo va dentro de un \bgroup, al terminar se revierte automáticamente.
\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 ahí se sigue la famosa restricción de \verb: \verb no puede usarse dentro del argumento de otra orden. El argumento se tokeniza antes incluso de llamar a la macro, así que cuando \verb cambia algún catcode ya es demasiado tarde. \footnote{code: \verb|\foo_bar|} produce ! Undefined control sequence. (porque \foo se leyó como orden real) seguido de ! Missing $ inserted. (porque _ se leyó como subíndice), y dentro de un argumento de macro se añade ! Extra }, or forgotten $. La salida es \lstinline de listings: \section{A \lstinline|x_1| heading} compila con cero errores e incluso llega intacta al índice general. Las cuarenta y tantas manipulaciones de catcodes dentro de listings existen precisamente para absorber problemas de este tipo.
% 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 ToCVer dentro de un token: \string, \meaning, \detokenize
Si los códigos de categoría siguen pareciendo abstractos, el remedio más rápido es mirarlos. \meaning enuncia el catcode con palabras: \meaning A da the letter A (catcode 11, de ahí «letter»), \meaning 7 da the character 7 (catcode 12) y \meaning\bgroup da begin-group character {. Una sola frase dice qué es ahora mismo un carácter. \string descompone una orden en sus caracteres, barra invertida incluida, y \detokenize convierte un argumento entero en texto imprimible; el # que sale como ## se duplica para seguir siendo fiel como lista de tokens. Para depurar, \show también resulta cómodo: escribe una definición en el registro y hace una pausa.
| Comando | Qué hace | Salida medida bajo TeX Live 2024 |
|---|---|---|
\the\catcode | Lee el catcode de un carácter como número | \the\catcode + acento grave + $ da 3 |
\meaning | Describe un token con palabras | \meaning A da the letter A; \meaning 7 da the character 7 |
\string | Descompone una orden en caracteres, con la barra invertida | \string\frac da \frac |
\detokenize | Convierte un argumento entero en texto imprimible | \detokenize{\frac{1}{2} #1} da \frac {1}{2} ##1 |
\show | Escribe una definición en el registro y hace una pausa | \show\LaTeX da macro:->\protect \LaTeX |
Un último instinto práctico. Ante un fallo del tipo «solo este carácter se comporta mal», compruebe primero su número con \the\catcode. Paquetes como babel, csquotes, listings o hyperref vuelven activos ciertos caracteres o los bajan a catcode 12 para sus propios fines. Si el síntoma se limita a un único carácter, la causa es un código de categoría nueve de cada diez veces. Sabido eso, la solución se reduce a tres opciones: cambiar el orden de carga, evitar el carácter, o usar la salida que el paquete ya ofrece (\%, otro delimitador para \verb, \lstinline).