TeX est un langage assez compliqué pour les non-initiés (et même pour les initiés diront certains). Lorsqu’on lit du code, on voit des \expandafter
, et autres primitives bizarres avec le mot « expand », et sur les forums les gens parlent parfois de problèmes d’expansion, de choses qui ne se sont pas expand comme il fallait dans leur commande. Il est temps de lever un peu le voile sur cette fameuse notion d’expansion.
L'expansion, quèsaco ?
En fait, l’expansion est le cœur du fonctionnement de TeX, et ce n’est pas bien compliqué. Pour compiler un document, TeX lit les caractères du document et les assemble en tokens. Un caractère d’échappement suivi d’un chaîne de caractère donne un token (associé à une commande), sinon chaque caractère donne un token (on note qu’une suite de caractères d’espacement est transformés en un unique token d’espace). Par exemple, dans le TeXBook, on nous dit que {\hskip 36 pt}
serait décomposé en huit tokens : {
, \hskip
, 3
, ,
6
, p
, t
et }
.
L’expansion arrive après cette étape. Une fois qu’on a la liste des tokens, on développe ceux qui peuvent l’être (les commandes notamment).
\def\x{x}
\def\y{y}
x vaut x et y vaut y.
Ici, x vaut x et y vaut \y
sera développé en x vaut x et y vaut \y
puis en x vaut x et y vaut y
(si on oublie l’étape de transformation en tokens). Par défaut, TeX développe toujours le token le plus à gauche qui n’est pas développé. Le développement d’un token peut faire apparaître de nouveaux tokens que TeX va alors développer, toujours de gauche à droite (cela veut notamment dire que tant qu’il n’a pas fini de développer tout ce qu’il y avait à développer dans le premier token et ce que son développement à donner, il ne passera pas au suivant).
\def\x{
x \def\y{z}
}
\def\y{y}
x vaut \x et y vaut \y
Ici, le \x
sera développé, mais ce développement a introduit un \def\y{z}
qui sera alors développé (menant à une redéfinition de \y
) et une fois qu’il n’y aura plus rien à développer du côté du \def\y{z}
, le \y
sera développé… Et il affichera « z ». Ça y est, on a vu ce qu’était l’expansion. Il ne nous reste plus qu’à voir certaines de ses subtilités.
Comme on l’a dit plus haut, TeX développe de gauche à droite. La commande \expandafter
permet de changer ce comportement localement. Elle regarde les deux tokens qui viennent juste après et développe le second avant le premier.
\def\nom{nomBis}
\expandafter\def\csname\nom\endcsname{Chouette}
Ici, on demande à TeX de développer \csname
avant \def
. En gros, \csname
prend tout ce qu’il y a jusqu’au \endcsname
et le transforme en nom de commande. Ici, il va donc exécuter le \nom
, et se développer en \nomBiS
. La ligne va dont se développer en \def\nomBis{Chouette}
. Sans le \expandafter
, c’est le \def
qui se serait développé en premier et on n’aurait pas le résultat voulu.
Cette astuce est utilisée dans mes précédents billets pour créer des commandes qui créent des commandes dont le nom est donné en argument.
La définition de commandes
La notion d’expansion semble assez liée à la notion de commande. Logiquement, de l’expansion va apparaître là où il y a des commandes, et nous allons donc nous intéresser aux commandes pour comprendre l’expansion.
Pour commencer, on note que l’argument de \def
n’est pas développé, ce qu’on a pu le voir dans les exemples précédents. Si nous définissons \def\a{a \commande b}
, un appel à \a
sera développé en a \commande b
(lorsqu’il sera développé). TeX offre également une primitive \edef
(pour expanded def) qui développe totalement son argument. Ainsi si nous définissons \edef\a{ \commande b}
, \commande
sera développée dès la définition de \a
.
\def\a{a}
\def\aa{a\a} % ou \edef\aa{a\a}
\def\a{z}
\aa
Avec \def
, le contenu de la commande \aa
est a \a
. Lors de son utilisation, \aa
sera développé en a \a
puis \a
sera développé en z
et on aura « az ». Par contre, avec \edef
, le contenu de la commande \aa
est aa
. Lors de son utilisation, \aa
est juste développé en aa
.
Notons que la commande \meaning
nous permet de voir la signification d’une commande. Elle nous sera utile pour la suite. Par exemple, \meaning\aa
affiche macro:->a\a
avec \def
, mais affiche macro:->aa
avec \edef
.
\edef
et \let
On pourrait être tenté de confondre \edef
et \let
. Mais elles ne font pas la même chose. \let
se contente de créer une nouvelle commande en copiant le contenu d’une commande. On peut le voir avec ce genre de code.
\def\a{a}
\def\aa{\a}
\let\aaa\aa % ou \edef\aaa{\aa}
\def\a{z}
\meaning\aaa
Avec \let
le contenu de \aaa
est \a
et \meaning\aaa
donne macro:->\a2
(on a copié le contenu de \aa
). Avec \edef
, ce contenu est a
(on a tout développé) et \meaning\aaa
donne \macro:->a
. Ainsi, même après avoir redéfini \a
en z
, \aaa
affiche toujours a
dans le cas du \edef
alors qu’il affiche z
dans le cas du \let
.
On peut alors voir \let
comme une primitive qui crée une nouvelle commande en développant la commande que l’on veut copier d’un niveau.
Le comportement de \def
est généralement celui qui est voulu (d’ailleurs, \newcommand
se base sur \def
). Mais celui de \edef
est parfois utile. Voici un exemple d’utilisation. Nous allons créer une commande \recapSection
qui prend en paramètre un nom de commande nom
à créer et un résumé de section résumé
. On crée alors la commande \nom
qui affiche « Résumé de la section <\thesection> : <le résumé> ».
\newcommand{\recapSection}[2]{
\expandafter\edef\csname recapSection#1\endcsname{
Résumé de la section \thesection : #2
}
}
\section{Un}
\recapSection{Un}{Section une}
\section{Deux}
\recapSection{Deux}{Section deux}
\section{Trois}
On fait quelques rappels.
\recapSectionUn. \recapSectionDeux.
Ici, si nous utilisions \def
, \thesection
serait développée au moment de l’utilisation de la commande créée et vaudrait donc le numéro de la section courante et pas celui de la section résumée.
Et c’est maintenant que les choses sérieuses commencent. Nous allons améliorer notre commande \recapSection
, elle va afficher le résumé en utilisant une commmande de style (\texbf
, etc.), cette commande étant stockée dans \style
. Ainsi, on pourra changer le style comme on veut. L’idée est de pouvoir écrire ceci.
\let\style\textbf
\recapSectionUn. % Résumé en gras.
\letstyle\textit
\recapSectionDeux. % Résumé en italique.
Si nous utilisons juste \style{#2}
, \style
sera développé dès la création de la commande de résumé et les styles des résumés de notre code précédent seront celui lorsque \recapSectionUn
a été définie et celui lorsque \recapSectionDeux
a été définie (en fait, on aura même une erreur).
Pour avoir ce qu’on veut, il faut dire à TeX de ne pas développer le \style
, ce qu’on fait avec la primitive \noexpand
qui permet de ne pas développer le token qui la suit. \noexpand<token>
devient toujours <token>
peu importe ce qu’est <token>
. On obtient donc ce code.
\newcommand{\recapSection}[2]{
\expandafter\edef\csname recapSection#1\endcsname{
Résumé de la section \thesection : \noexpand\style{#2}.
}
}
On vérifie avec \meaning
qu’on a bien la commande attendue.
\section{Un}
\recapSection{Un}{Section une}
\section{Deux}
\recapSection{Deux}{Section deux}
\meaning\recapSectionUn
\meaning\recapSectionDeux
C’est parfait, les numéros de chapitre ont bien été développés, mais pas \style
. Et bien sûr, le code d’exemple donne bien le résultat attendu ; le premier résumé est en gras et le second en italique.
Cependant, notre commande est toujours défectueuse. Par exemple, si nous utilisons \emph
dans notre second argument, nous obtiendrons une erreur. Il ne faut pas développer le \emph
immédiatement. Pas de problème, utilisons \noexpand#2
pour ne pas étendre le second argument.
\newcommand{\recapSection}[2]{
\expandafter\edef\csname recapSection#1\endcsname{
Résumé de la section \thesection : \noexpand\style{\noexpand#2}.
}
}
Malheureusement, comme \noexpand
s’applique au token qui vient juste après, cette solution ne fonctionnera que si le \emph
est le premier token de #2
. Heureusement, eTeX fournit \unexpanded
qui prend un argument et permet de ne pas développer cet argument. On va l’utiliser sur #2
, et puisque \style
ne doit pas être développée non plus, on va l’utiliser sur \style{#2}
.
\newcommand{\recapSection}[2]{
\expandafter\edef\csname recapSection#1\endcsname{
Résumé de la section \thesection : \unexpanded{\style{#2}}.
}
}
\noexpand
et \unexpanded
Il y a une autre différence entre \noexpand
et \unexpanded
. \noexpand
change le comportement du token suivant pour qu’il ne soit pas développé (lors de la première expansion). \unexpanded
par contre indique juste qu’il ne faut pas développer le token (à l’intérieur d’un \edef
ou commande similaires). On voit cette différence en les utilisant directement.
\def\a{a}
On affiche \noexpand\a et on affiche \unexpanded{a}.
Le premier \a
ne sera pas développé et on n’affichera rien, le deuxième le sera et la lettre sera affichée. Le résultat est donc « On affiche et on affiche a ».
Il n’est pas essentiel de savoir cela, mais au moins c’est dit (et c’est écrit).
J’ai l’impression que mes billets sur LaTeX sont de plus en plus longs. Bonne ou mauvaise chose, nous verrons bien. On pourrait parler des commandes fragiles et robustes ou encore de la commande \expandonce
de \etoolbox
qui permet de ne développer son argument que d’un seul niveau (allez regarder la définition de \expandonce
et essayez de comprendre pourquoi elle fait ce qui est demandé).
Mais poussé par cette impression de billets de plus en plus long, je me suis arrêté là. En plus, qui dit plus de billets dit plus de vues, dit plus d’argent ! Comment ça y a pas de publicité ici ? Ah, c’est parce que Zeste de Savoir est un site génial, je comprends.
En tout cas, je remercie @Saroupille, c’est grâce à lui que l’idée de ce billet est venu.