- Jet Killer, un greffon Gimp pour se débarasser de la palette « jet »
- La télé-information client envoie-t-elle les trames de manière régulière ?
— Je comprends pas pourquoi mon code compile pas, j’ai juste rajouté une note de bas de page dans ma section ?
— Hum, essaie de rajouter un\protect
ici, chez moi ça marche. Source : Beaucoup d’endroits.
Voici un petit bout de discussion assez classique. Je crois qu’on a tous l’impression que personne ne comprend cette histoire de commandes fragiles à protéger, mais que tout le monde l’utilise comme un sort de magie noire qui règle tous les problèmes possibles. Un peu comme un certain « authentique mage vaudou » qui répare apparemment les PCs par télépathie et rend votre voisine amoureuse de vous… Dans ce billet, on va tenter d’expliquer ces exploits (ceux de \protect
, hein) !
Pour bien comprendre ce billet, il faut savoir le système d’expansion de TeX. J’en parle dans ce précédent billet.
C'est dangereux de sortir !
La première chose à expliquer est la notion d'argument mouvant. Il s’agit tout simplement des arguments que LaTeX sauvegarde (dans un fichier auxiliaire par exemple) pour les relire plus tard. Ce sont typiquement les arguments qui se retrouvent plus tard dans la table des matières, la table des figures et autres, donc les arguments des commandes comme \section
et \caption
. Par exemple, la commande \tableofcontents
lit le contenu du fichier .toc
pour savoir ce qu’elle doit afficher. Le contenu de ce fichier a été écrit lors des compilations précédentes par les \chapter
, \section
et autres commandes de sectionnement.
La commande à argument mouvant développe son argument pour le copier dans le fichier auxiliaire. Par exemple, observons le fichier .toc
obtenu avec ce code.
\newcommand{\test}{Test}
\tableofcontents
\section{Section de \test}
Le fichier .toc
contient \contentsline {section}{\numberline {1}Section de Test}{1}%
. En particulier, il contient Test
. Ainsi, lors de la prochaine compilation, \tableofcontents
lira le contenu du fichier .toc
et saura quoi afficher. On va jouer un peu avec ça en créant une erreur, ça nous permettra de comprendre la suite. Pour créer une erreur, on va empêcher le développement de \test
grâce à \unexpanded
et on va déclarer \test
après l’utilisation de \tableofcontents
.
\tableofcontents
\newcommand{\test}{Test}
\section{Section de \unexpanded{\unexpanded{\test}}}
La première compilation est OK, mais la seconde donne une erreur undefined control sequence sur la commande \test
! En fait, cette fois c’est \test
et pas Test
qui apparaît dans le fichier .toc
et donc \tableofcontents
veut afficher \test
qui est inconnue puisque pas encore définie. Ce test nous apprend que ce qui a été écrit dans le fichier auxiliaire peut être du code qui sera potentiellement lu et développé plus tard. Et c’est là que les ennuis commencent.
Si vous êtes attentifs, vous pourriez vouloir me dire que j’ai quand même bien forcé pour obtenir cette erreur. Les ennuis, j’ai bien creusé pour les trouver quoi. Déjà, si l’argument mouvant est développé, à la fin il n’y aura que des éléments primitifs de TeX et cette erreur n’arrivera pas. Et c’est vrai. Mais quelque chose de pire peut arriver, l’argument peut être développé en du code incorrect.
On pourrait se dire que seul du code incorrect peut être développé en code incorrect. Malheureusement, certaines commandes peuvent être développées en du code incorrect dans le contexte de sauvegarde d’argument. Ce sont exactement ces commandes que l’on qualifie de fragiles. Les commandes qui sont développées en du code correct durant la sauvegarde d’argument sont dites robustes. Maintenant que c’est dit, il nous reste à voir pourquoi un mauvais développement peut arriver, et bien sûr comment l’empêcher.
Tomber malade en sortant
Pour expliquer comment un mauvais développement peut arriver, il faut d’abord se rendre compte que TeX ne fonctionne pas juste à base d’expansion. Il y a de l’expansion, mais il y a aussi de l’exécution. L’expansion correspond juste à remplacer un token par sa signification. C’est juste du remplacement.
Par exemple, la commande \def
n’est pas développée. Quand TeX arrive devant un \def
, il ne le développe pas, il regarde le token qui vient juste après et il change la signification de ce token (la commande qu’on est en train de définir) ; c’est une phase d’exécution. En gros, tout ce qui ne correspond pas à remplacer un token par sa signification correspond à de l’exécution.
Bien sûr, lorsqu’un document est compilé, l’exécution et le développement travaillent en quelque sorte ensemble. TeX croise un token, il le développe et ensuite il l’exécute si c’est possible. Et ainsi de suite.
Le problème, c’est que dans certains contextes appelé expansion-only context, seul l’expansion se fait et pas l’exécution. Et c’est ça qui peut mener à du code incorrect. Vous l’aurez compris, quand TeX écrit un argument mouvant dans un fichier, il est dans un contexte où seul l’expansion se fait. Voyons quelques exemples avec \def
pour mieux comprendre cela.
\def\arg{Bonjour}
\newcommand\test{\def\arg{Test}\arg}
\tableofcontents
\section{\test}
La première compilation se passe bien, on a une section dont le titre est « Test ». Mais la seconde compilation nous donne l’erreur « Missing control sequence inserted ». Regardons le contenu du fichier .toc
.
\contentsline {section}{\numberline {1}\def Bonjour{Test} Bonjour}{1}%
On n’a pas \arg
, mais Bonjour
à la place. Expliquons cela en développant \test
dans un contexte sans exécution.
- On le développe en
\def\arg{Test}\arg
. - On lit le
\def
, mais il n’y a pas d’exécution. - On lit
\arg
, on le développe enBonjour
. - On garde
{
, pareil pourT
,e
,s
,t
et}
. - On lit de nouveau
\arg
, on le développe enBonjour
.
Voici comment on se retrouve avec du code incorrect à parti d’un code qui correct. Félicitations, nous avons créé une commande fragile ! Bien sûr, il y en a déjà dans LaTeX (\footnote
par exemple).
Protégez-vous !
Maintenant qu’on sait ce qui pose problème, il n’est pas difficile de régler les choses. Si on ne développe pas \test
, il n’y aura pas de problème. Le problème, c’est qu’on ne sait pas combien de fois l’argument sera développé (\section
pourrait l’envoyer à une autre commande qui la développe et l’envoie à une autre commande, etc.).
La commande \protect
règle cela. En gros, voici le fonctionnement de \protect<token>
.
- Si on est en contexte normal,
\protect
ne fait rien (en fait il se développe en\relax
), donc on obtient quelque chose équivalent à<token>
. - Si on est en contexte sans exécution,
\protect
est défini comme étant\noexpand\protect\noexpand
et\protect<token>
se développe donc en\protect<token>
. On est revenu au point de départ et la commande est toujours protégé.
Donc il nous suffit d’utiliser \protect
devant les commandes fragiles qui sont dans des arguments mouvants.
\def\arg{Bonjour}
\newcommand\test{\def\arg{Test}\arg}
\tableofcontents
\section{\protect\test}
Nous remarquerons que si une commande est fragile, alors les commandes qui l’utilisent (sans la protéger) sont également fragiles. Mais LaTeX nous donne la possibilité de créer des commandes robustes (même si elles utilisent une commande fragile sans la protéger) avec la commande \DeclareRobustCommand
. On peut donc écrire ce code.
\def\arg{Bonjour}
\DeclareRobustommand\test{\def\arg{Test}\arg}
\tableofcontents
\section{\protect\test}
Et finalement, notons que certains packages (etoolbox
et xparse
par exemple) offrent également de quoi déclarer des commandes robustes et même de quoi rendre une commande fragile robuste !
Parfois, un argument peut poser problème même s’il n’est pas fragile. C’est ce qui arrive dans ce sujet. Comme quoi cette histoire de fragilité est vraiment embêtante !