Licence CC BY-NC-ND

La TeXnique : Où on parle d'expansion

On va développer un peu les choses

Publié :
Auteur :
Catégories :
Temps de lecture estimé : 14 minutes

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.

Différence entre \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}}.
   }
}
Différence entre \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. :)

1 commentaire

Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte