expl3 / couche LaTeX3

Écrire \usepackage{expl3} sur un LaTeX actuel ne charge absolument rien. expl3 — la couche de programmation issue du projet autrefois nommé « LaTeX3 » — fait déjà partie du format. La ligne 1125 du source du noyau, latex.ltx, le consigne mot pour mot : Skipping: expl3 code already part of the format, et expl3.sty vérifie à la ligne 53 si le code est déjà présent, auquel cas il jette purement et simplement le chargement. Cette page mesure ce que \ExplSyntaxOn change réellement à la lecture des caractères, puis parcourt la façon de lire un nom tel que \module_function:nn, le sens des spécificateurs d’arguments et les types de données — tl, seq, prop, int, fp et les autres.

expl3 est déjà dans le noyau — \fpeval en est la preuve

TeX est né comme processeur de macros, un outil pour définir des commandes avec \def et \newcommand. Mais écrire un gros paquet a révélé l’incohérence des primitives brutes : contrôler l’expansion et manipuler les variables relevait de l’artisanat. expl3 donne de nouveaux noms aux primitives de TeX et e-TeX, nomme fonctions et variables de façon systématique, et inscrit le type de chaque argument dans le nom lui-même. Construit sur de longues années par le LaTeX Project, il tient à la fois de la bibliothèque standard et du langage de programmation pour LaTeX — xparse, siunitx, fontspec et l3keys2e reposent tous dessus.

Et cette couche ne se vend plus séparément. Ouvrez expl3.sty : les lignes 53 à 59 testent si \tex_let:D est déjà défini ; le cas échéant, \@gobble avale l’\input expl3-code.tex en entier. Ce qui subsiste comme paquet est une coquille réduite au nom — elle se déclare d’ailleurs L3 programming layer (loader) — tandis que la substance réside dans le format. De fait, dans un article nu, sans rien de chargé, \ExplSyntaxOn, \tl_new:N et \ProvidesExplPackage sont déjà définis. La preuve la plus nette tient en quatre lignes à partir de latex.ltx ligne 1167 : \fpeval, \inteval, \dimeval et \skipeval y sont définis par \cs_new_eq:NN comme alias de \fp_eval:n, \int_eval:n, \dim_eval:n et \skip_eval:n. Le \fpeval{sqrt(2)} du niveau document est donc la fonction expl3 elle-même, appelée sous un autre nom (voir la page compteurs et longueurs).

Ce que \ExplSyntaxOn change vraiment : l’espace et ~ échangent leurs rôles

Le code expl3 se situe entre \ExplSyntaxOn et \ExplSyntaxOff, et dans cette zone exactement quatre caractères changent de code de catégorie. Faites imprimer \the\catcode par le noyau avant et après : l’espace passe de 10 (espace) à 9 (caractère ignoré), _ de 8 (indice) à 11 (lettre), : de 12 (autre) à 11 (lettre), et ~ de 13 (actif) à 10 (espace). \ExplSyntaxOff restitue exactement 10, 8, 12 et 13. Les codes de catégorie eux-mêmes font l’objet de la page dédiée ; ce qui réjouit ici, c’est que l’espace et ~ ont échangé leurs sièges. L’espace cède le catcode 10 et passe au 9, la case « ignoré », tandis que ~ s’installe sur le 10 libéré. C’est pourquoi, à l’intérieur d’expl3, ~ n’est pas une commande produisant une espace : c’est littéralement un caractère espace.

CaractèreLaTeX ordinaireDans ExplSyntaxOn
(space)10 — espace9 — caractère ignoré. Indentation et sauts de ligne n’atteignent jamais la sortie
~13 — actif (espace insécable)10 — une espace en soi. C’est ce qu’on écrit lorsque la sortie a besoin d’un blanc
_8 — indice11 — lettre. Peut figurer dans un nom de commande
:12 — autre caractère11 — lettre. Peut séparer la signature d’arguments

La conséquence pratique de cet échange tient en une règle : une espace nue à l’intérieur d’expl3 n’atteint jamais la sortie. Pour composer « Fruit: apple », il faut écrire Fruit:~#1 ; remplacez le ~ par une espace ordinaire et vous obtenez « Fruit:apple » d’un seul tenant. À l’inverse, cela signifie qu’on peut indenter le code à volonté et aérer les tokens pour la lisibilité sans rien changer à la sortie. Quand on songe à l’attention que le LaTeX ordinaire réclame au sujet des espaces, c’est une libération appréciable.

document.tex
% no \usepackage{expl3} is needed: it is in the format
\ExplSyntaxOn
  % spaces and newlines here are catcode 9 (ignored); _ and : are letters
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }   % ~ is the real space
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

Lire \seq_put_right:Nn — le nom énonce les types

Un nom de fonction expl3 a la forme \⟨module⟩_⟨description⟩:⟨arg-signature⟩. Jusqu’au premier _ se trouve le module (un type de données ou un domaine) ; jusqu’au :, le nom descriptif ; après le :, la signature d’arguments. Dans \seq_put_right:Nn, le module est seq (séquences), la description put_right (ajouter à droite) et la signature Nn. Chaque lettre de la signature indique comment l’argument correspondant est traité avant d’être transmis. Lire le nom, c’est savoir combien d’arguments la fonction prend et ce qu’il advient de chacun — lire les types sans ouvrir le manuel, tel est précisément le but de cette convention.

SpécificateurSignification
NAucune transformation ; un token unique (souvent une séquence de contrôle).
nAucune transformation ; un groupe de tokens entre accolades.
cTransforme l’argument en séquence de contrôle via \csname avant usage.
V / vTransmet la valeur d’une variable (V depuis un token unique, v construit d’abord le nom).
oDéveloppe l’argument une seule fois avant usage.
x / eDéveloppement complet (x équivaut à \edef, non développable ; e utilise \expanded).
fDéveloppe de gauche à droite jusqu’au premier token non développable.
pUn texte de paramètres TeX (#1#2…) ; sert à définir une fonction.
T / FCode exécuté si le test est vrai / faux. Souvent apparié en TF, comme \tl_if_empty:nTF ; des variantes T seule et F seule existent aussi.

Les variables suivent le même style mais commencent par une lettre indiquant la portée. l_ signifie local (modifié seulement dans le groupe TeX courant), g_ global, c_ constante. Un identifiant de type termine le nom : _tl (liste de tokens), _int (entier), _seq (séquence), _prop (liste de propriétés), _clist (liste à virgules), _fp (virgule flottante), _str (chaîne), _bool (booléen), etc. Ainsi \l_my_name_tl se lit d’emblée « variable locale de type liste de tokens » et \g_counter_int « entier global ». Chaque module fournit également des variables de brouillon — des temporaires jetables — telles que \l_tmpa_tl et \l_tmpb_int.

Voir la différence entre :Nn et :Nx

n conserve ce que vous avez écrit ; x grave la valeur telle qu’elle est à l’instant. Plutôt que de l’expliquer dans l’abstrait, il est plus rapide de vider le contenu dans le journal avec \tl_show:N. Placez le même { [ \l_src_tl ] } dans deux variables, l’une par \tl_set:Nn, l’autre par \tl_set:Nx, et le journal indique : > \l_a_tl=[\l_src_tl ]. et > \l_b_tl=[FIRST]. La version n détient le token \l_src_tl lui-même ; la version x est déjà devenue FIRST, la valeur au moment de l’affectation. Changez ensuite \l_src_tl en SECOND et composez les deux : la version n imprime « [SECOND] », la version x « [FIRST] ». Autrement dit, n garde une référence et x un instantané. La plupart des situations où l’on dit que « le contrôle de l’expansion est difficile » sont en réalité ce choix pris à l’envers. \tl_show:N mérite d’être retenu comme premier réflexe en cas de blocage.

document.tex
\ExplSyntaxOn
  \tl_new:N \l_src_tl  \tl_set:Nn \l_src_tl { FIRST }
  \tl_new:N \l_a_tl    \tl_set:Nn \l_a_tl { [ \l_src_tl ] }   % reference
  \tl_new:N \l_b_tl    \tl_set:Nx \l_b_tl { [ \l_src_tl ] }   % snapshot

  \tl_show:N \l_a_tl   % log: > \l_a_tl=[\l_src_tl ].
  \tl_show:N \l_b_tl   % log: > \l_b_tl=[FIRST].

  \tl_set:Nn \l_src_tl { SECOND }
  Nn~stored:~\tl_use:N \l_a_tl \par   % prints [SECOND]
  Nx~stored:~\tl_use:N \l_b_tl \par   % prints [FIRST]
\ExplSyntaxOff

Les types de données : créer, définir, utiliser

expl3 se divise en modules par type de données, et tous partagent le même rythme : créer, définir, utiliser. On déclare avec \⟨type⟩_new:N, on affecte une valeur avec \⟨type⟩_set:Nn, on la récupère avec \⟨type⟩_use:N ou l’un de ses proches. Les gestes ne changent pas avec le type : en apprendre un permet de deviner les autres. Seule la définition de fonction diffère un peu : \cs_new:Npn définit une nouvelle fonction et signale une erreur si le nom existe déjà (cs pour control sequence). \cs_set:Npn définit également, mais seulement dans le groupe TeX courant, et sans se plaindre d’une redéfinition. Toutes deux utilisent :Npn : N est la fonction à définir, p son texte de paramètres (#1#2…), n son corps, le texte de remplacement.

ModuleCe qu’il contientFonctions courantes et résultats mesurés
tlListe de tokens — la variable la plus basique, utilisable comme une chaîne\tl_new:N / \tl_set:Nn (l’ancien contenu disparaît) / \tl_use:N / \tl_show:N
strChaînes — pour comparer en tant que caractères et non en tant que tokens\str_if_eq:nnTF { a~b } { a~b } renvoie same
intEntiers — arithmétique entière avec les opérateurs usuels\int_eval:n { 2 + 3 * 4 } vaut 14 (\inteval en est l’alias)
fpVirgule flottante — avec des fonctions scientifiques comme sin, sqrt, pi\fp_eval:n { sqrt(2) } vaut 1.414213562373095 ; \fp_eval:n { 2 * pi } vaut 6.283185307179586
seqSéquences — listes avec accès aux deux extrémités (utilisables en piles)\seq_put_right:Nn ajoute, \seq_map_inline:Nn parcourt chaque élément comme #1, \seq_use:Nn joint avec un séparateur
propListes de propriétés — dictionnaires associant des clés à des valeurs\prop_put:Nnn range variable, clé, valeur ; \prop_item:Nn récupère
clistUne liste de valeurs séparées par des virgulesAprès \clist_set:Nn \l_c_clist { 1,2,3 }, \clist_use:Nn \l_c_clist { ~+~ } donne « 1 + 2 + 3 »
boolBooléens — ce qui remplace \newif\bool_new:N / \bool_set_true:N / \bool_if:NTF pour brancher
document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  \seq_new:N \l_example_fruits_seq

  % define a function that appends one item
  \cs_new:Npn \example_add:n #1
    { \seq_put_right:Nn \l_example_fruits_seq {#1} }

  \example_add:n { apple }
  \example_add:n { banana }
  \example_add:n { cherry }

  % each item arrives as #1; ~ is a real space
  \seq_map_inline:Nn \l_example_fruits_seq
    { Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}

Compilez cela et « Fruit: apple », « Fruit: banana » et « Fruit: cherry » sortent sur trois lignes distinctes. Le #1 de \cs_new:Npn est l’argument de la fonction définie, tandis que celui de \seq_map_inline:Nn est chaque élément parcouru — tous deux arrivent en type n, le contenu entre accolades. Si les _ et : dans les noms de commande ne posent aucun problème, c’est précisément parce qu’on se trouve dans une zone \ExplSyntaxOn.

l3keys et l’usage d’expl3 pour écrire un paquet

Pour doter vos propres commandes ou paquets d’options de la forme key = value, l’outil standard est l3keys. On déclare les clés avec \keys_define:nn { module } { ... } et on les règle avec \keys_set:nn { module } { key = value }. Une déclaration de clé porte une propriété suffixée telle que .tl_set:N (ranger dans une variable liste de tokens), .bool_set:N (en faire un booléen), .code:n (exécuter du code quelconque) ou .initial:n (valeur par défaut). Ce mécanisme était à l’origine relié aux options de paquet par un paquet nommé l3keys2e, mais son cœur a depuis été absorbé dans le noyau LaTeX2ε et s’utilise sans aucun paquet sous les noms \DeclareKeys et \ProcessKeyOptions (la page sur l’écriture de paquets en rédige un).

Pour la rédaction ordinaire de documents, on n’aura pratiquement jamais à écrire d’expl3 directement. Mais dès qu’il s’agit d’écrire un paquet ou une classe, expl3 est aujourd’hui le standard de fait. Un appariement courant consiste à recevoir la commande destinée à l’utilisateur avec \NewDocumentCommand (xparse) et à en implémenter le corps en expl3. Deux choses sont à distinguer : la spécification d’arguments de xparse (niveau document, comme m, O{...}, s) et la signature d’arguments d’expl3 (niveau programmation, comme N, n) ne sont pas la même chose. La première est détaillée sur la page xparse. Enfin, si la première ligne d’un .sty est \ProvidesExplPackage, la syntaxe expl3 s’active automatiquement à partir de là — inutile d’écrire \ExplSyntaxOn.

  • Encadrez le code par \ExplSyntaxOn\ExplSyntaxOff. À l’intérieur, une espace nue prend le code de catégorie 9 (ignoré) ; utilisez ~ quand la sortie a besoin d’un blanc.
  • Aucun \usepackage{expl3} n’est nécessaire : il est dans le noyau, et expl3.sty jette le chargement de lui-même. Dans un .sty, \ProvidesExplPackage dispense aussi de \ExplSyntaxOn.
  • Les fonctions s’appellent \⟨module⟩_⟨description⟩:⟨signature⟩ ; les variables \⟨scope⟩_⟨name⟩_⟨type⟩, avec la portée l_ / g_ / c_.
  • Ne confondez pas n et x. Le choix dépend de ce que vous voulez : conserver une référence ou graver la valeur courante. En cas de doute, affichez avec \tl_show:N et regardez.
  • N’inventez pas de noms de commande. La nomenclature est stricte ; le manuel officiel interface3 (texdoc interface3) est la source primaire.