expl3 / couche LaTeX3

expl3 (« expl-three ») est le langage de programmation qui fait fonctionner LaTeX moderne de l’intérieur. Issu de l’effort autrefois appelé « LaTeX3 », il est aujourd’hui chargé par défaut dans le noyau LaTeX. Il va bien au-delà de ce que l’on écrit avec \newcommand : variables, structures de données, arithmétique entière et même nombres flottants, une vraie couche de programmation sur laquelle reposent xparse, siunitx et fontspec. Cette page présente le passage à sa syntaxe, son système de noms particulier et ses modules principaux.

Ce qu’est expl3

TeX est fondamentalement un processeur de macros : on définit des commandes avec \def ou \newcommand. Mais écrire un gros package révèle vite l’incohérence des primitives TeX brutes : le contrôle de l’expansion et la gestion des variables deviennent affaire d’artisanat. expl3 renomme les primitives TeX et e-TeX, nomme fonctions et variables systématiquement, et rend explicites les types d’arguments de chaque fonction. Développé pendant des années par le LaTeX Project, il est à la fois bibliothèque standard et langage de programmation pour LaTeX.

Un fait pratique compte : expl3 est chargé par défaut dans les noyaux LaTeX modernes. Le manuel officiel interface3 l’indique clairement : avec un noyau LaTeX 2ε à jour, ce matériau est chargé comme partie du format. Il n’est donc plus nécessaire d’écrire \usepackage{expl3}. La plupart des utilisateurs ne l’écrivent pas directement ; ils le rencontrent via xparse (\NewDocumentCommand) et des packages comme siunitx, fontspec ou l3keys2e.

Changer de syntaxe — \ExplSyntaxOn

Le code expl3 s’écrit dans une zone délimitée par \ExplSyntaxOn et \ExplSyntaxOff. Dans cette zone, deux choses changent dans la lecture des caractères, c’est-à-dire leurs codes de catégorie. D’abord, les espaces et retours à la ligne sont ignorés, ce qui permet d’indenter le code et de l’aérer. Ensuite, _ (souligné) et : (deux-points) deviennent des « lettres », donc utilisables dans les noms de commandes ; LaTeX ordinaire l’interdit, mais expl3 s’en sert pour ses noms longs et descriptifs.

Comme les espaces sont ignorés, produire un vrai espace dans la sortie exige une autre notation. Dans une zone expl3, un espace explicite s’écrit ~ (tilde). Pour composer « Item: apple », on écrit donc Item:~#1, car un espace brut disparaîtrait. Une fois cette règle acquise, le reste se lit et s’écrit comme dans un langage de programmation ordinaire.

document.tex
% expl3 はカーネルに含まれるので \usepackage{expl3} は不要
\ExplSyntaxOn
  % ここでは空白・改行が無視され、 _ と : が命令名に使える
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

Schéma de nommage et signatures d’arguments

Un nom de fonction expl3 a la forme \⟨module⟩_⟨description⟩:⟨arg-signature⟩. Jusqu’au premier _, c’est le module (type de données ou domaine) ; jusqu’au :, le nom descriptif ; après le :, la signature d’arguments (arg-spec). Dans \seq_put_right:Nn, par exemple, le module est seq, la description put_right, et la signature Nn. Chaque lettre de la signature dit comment traiter l’argument avant de le transmettre.

Les principaux spécificateurs d’arguments sont ci-dessous, d’après le manuel officiel interface3. N et n sont les plus fondamentaux et suffisent longtemps. Les formes expansives x, e, o, f, ainsi que V et v qui extraient des valeurs de variables, peuvent venir ensuite.

SpécificateurSens
NAucune manipulation ; un token unique (souvent une séquence de contrôle).
nAucune manipulation ; un groupe de tokens entre accolades.
cTransforme l’argument en séquence de contrôle via \csname avant usage.
V / vPasse la valeur d’une variable (V depuis un token unique, v construit d’abord le nom).
oDéveloppe l’argument une fois avant usage.
x / eExpansion complète (x comme \edef, non expansible ; e utilise \expanded).
fDéveloppe jusqu’au premier token non expansible de gauche à droite.
pUn texte de paramètres TeX (#1#2…), utilisé lors de la définition de fonctions.

Les variables sont nommées de la même façon, mais commencent par une lettre de portée. l_ signifie local (modifiable seulement dans le groupe TeX courant), g_ global, c_ constant. 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 (flottant), etc. \l_my_name_tl se lit donc comme « variable locale de liste de tokens », et \g_counter_int comme « variable entière globale ». Chaque module fournit aussi des variables scratch, temporaires jetables, comme \l_tmpa_tl et \l_tmpb_int.

Modules principaux

expl3 est divisé en modules par type de données, et tous suivent le même rythme : créer, régler, utiliser. Commençons par définir des fonctions. \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 aussi, mais seulement dans le groupe TeX courant et sans se plaindre d’une redéfinition. Les deux utilisent :Npn : N est le nom de la fonction, p le texte de paramètres (#1#2…), n le corps, ou texte de remplacement.

Ensuite, les listes de tokens (tl) : la variable la plus basique, utilisable comme une chaîne. On la déclare avec \tl_new:N, on remplace son contenu par \tl_set:Nn et on la compose avec \tl_use:N. Les entiers (int) suivent la même forme : \int_new:N déclare, \int_set:Nn affecte, et \int_eval:n évalue une expression entière puis renvoie sa valeur, par exemple \int_eval:n { 2 + 3 * 4 } donne 14.

Les séquences (seq) sont des listes avec accès aux deux extrémités, utilisables comme piles. On les crée avec \seq_new:N, on ajoute à droite avec \seq_put_right:Nn, et \seq_map_inline:Nn parcourt tous les éléments en les passant à votre code sous la forme #1. Les listes de propriétés (prop) sont des dictionnaires, des correspondances clé-valeur. \prop_new:N en crée une, et \prop_put:Nnn stocke une paire clé/valeur (Nnn signifiant variable, clé, valeur).

Les listes à virgules (clist) traitent exactement ce que leur nom indique, des valeurs séparées par des virgules, et toutes les commandes commencent par \clist_ (\clist_new:N, \clist_set:Nn, etc.). Enfin, les flottants (fp) : \fp_eval:n évalue une expression décimale et prend en charge de nombreuses fonctions scientifiques comme sin, sqrt et pi, par exemple \fp_eval:n { sqrt(2) } ou \fp_eval:n { 2 * pi }. Il faut surtout le distinguer de \int_eval:n, limité aux entiers.

Un petit exemple — fonction et séquence

Combinons tout cela. L’exemple ci-dessous définit avec \cs_new:Npn une fonction \example_add:n qui ajoute un élément à une séquence, l’appelle plusieurs fois, puis imprime tous les éléments façon liste avec \seq_map_inline:Nn. Remarquez que _ et : dans les noms de commandes ne posent aucun problème, car nous sommes dans une zone \ExplSyntaxOn.

document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  % シーケンスを 1 個用意する
  \seq_new:N \l_example_fruits_seq

  % 「右端に 1 項目追加する」関数を定義する
  \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 }

  % 全項目を出力する(各項目が #1 に入る。 ~ は空白)
  \seq_map_inline:Nn \l_example_fruits_seq
    { Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}

À la compilation, « Fruit: apple », « Fruit: banana » et « Fruit: cherry » apparaissent sur trois lignes. Le #1 de \cs_new:Npn est l’argument de la fonction définie, tandis que le #1 de \seq_map_inline:Nn est l’élément parcouru ; dans les deux cas, il arrive comme type n. Notez le ~ dans Fruit:~#1 : remplacé par un espace brut, il serait ignoré et donnerait « Fruit:apple ».

Usage réel

Pour rédiger un document ordinaire, on a rarement besoin d’écrire expl3 directement. Mais dès que l’on écrit un package ou une classe, expl3 est désormais le standard de fait. Une combinaison courante consiste à recevoir une commande utilisateur avec \NewDocumentCommand (xparse) et à implémenter son corps en expl3. Distinguez bien la spécification d’arguments xparse (m, O{...}, s, au niveau document) de la signature d’arguments expl3 (N, n, au niveau programmation). La première est détaillée sur la page xparse.

Apprendre expl3 permet d’écrire clairement des traitements difficiles avec \newcommand seul. Quelques points à retenir :

  • Toujours entourer le code de \ExplSyntaxOn\ExplSyntaxOff. À l’intérieur, les espaces sont ignorés ; utilisez ~ quand il faut en produire un.
  • Pas besoin de \usepackage{expl3} : il est dans le noyau. Dans un .sty, on peut l’écrire directement après la déclaration du package.
  • Les fonctions sont \⟨module⟩_⟨description⟩:⟨signature⟩ ; les variables \⟨scope⟩_⟨name⟩_⟨type⟩, avec l_ / g_ / c_ pour la portée.
  • N’inventez pas de noms de commandes. Le nommage est strict ; le manuel officiel interface3 (texdoc interface3) fait autorité.