Clause
← Toute la documentation

L'original anglais a changé depuis cette traduction ; certaines parties peuvent donc être obsolètes. Lire en anglais

Traduit de l'original anglais · 1e3a39c8-dirty · 2026-10-26

Parse transforms

Utiliser les parse transforms

clau --erl "C:/Program Files/Erlang OTP/bin/erl.exe" --transform-path ebin -o app app.erl

Clause applique {parse_transform, Module} comme le fait erlc. Il exporte le module analysé sous forme de formes du format abstrait d'OTP, exécute le module de transformation sur un Erlang/OTP 29 installé sur l'hôte, importe les formes renvoyées comme nouvelle syntaxe du module et compile celle-ci (compiler/src/transforms/). OTP n'est nécessaire que pendant la compilation d'un module qui utilise une transformation ; les programmes générés n'en ont jamais besoin.

Comportement d'OTP

D'après le lib/compiler/src/compile.erl épinglé (transform_module, foldl_transform, maybe_strip_columns) et observé sous OTP 29.1.1 :

Chaîne de Clause

  1. Prétraiter et analyser le module. Les erreurs du préprocesseur ou de l'analyse syntaxique arrêtent ici ; les transformations ne s'exécutent pas sur un module comportant des erreurs (diffère d'OTP, qui transmet les erreurs sous forme de formes).
  2. Collecter les transformations dans l'ordre d'OTP : les options --parse-transform et les parse_transforms du projet, puis les attributs -compile. Un module qui n'en a aucune saute le reste et ne démarre jamais OTP.
  3. Retirer ces options des formes et exporter celles-ci : annotations {Line, Column} telles qu'erl_parse les attribue, attributs file d'epp, et eof. Les fonctions prédéfinies ne font pas encore partie des formes.
  4. Exécuter le chargeur une fois pour le module.
  5. Importer les formes renvoyées en les transformant en jetons situés à leurs annotations et en les analysant avec l'analyseur de Clause ; un attribut file change le fichier des formes suivantes comme dans erl_lint.
  6. Ajouter les fonctions prédéfinies, puis effectuer le lint, l'analyse et la compilation comme d'habitude.

Les actions qui exécutent les transformations sont listées sous utiliser les parse transforms.

Échange

Importer des formes

Les formes renvoyées, et les entrées .abstr (positionnelles ou sources du projet), deviennent des jetons situés à leurs annotations et passent par l'analyseur de Clause, de sorte qu'elles subissent les mêmes vérifications syntaxiques que le source ; la précédence des opérateurs décide où des parenthèses sont ajoutées. {Line, Column}, un Line seul (affiché file:line:) et les listes de propriétés avec location et file sont acceptés ; generated est ignoré. La ligne 0, que la transformation qlc d'OTP attribue au code généré, prend l'emplacement du nœud importé avant elle. Un attribut de fichier change le fichier des formes suivantes ; lorsque ce fichier existe, le code importé garde ses lignes de débogage et ses commentaires de source. Les formes mal formées sont des erreurs à leur annotation (unknown expression bogus, expected {Name, Arity}, invalid annotation) et le module échoue ; les formes {error, _}/{warning, _} d'OTP sont signalées avec leur descripteur, dont le formatage exige OTP. Importer un module exporté et l'exporter de nouveau donne les mêmes formes.

OTP de l'hôte

Chargeur

compiler/src/transforms/loader/clause_transform_loader.erl appartient au projet et est embarqué dans clau. Chaque exécution l'écrit, ainsi que la requête, dans un nouveau répertoire temporaire, clause-transform-* sous <build_dir>/tmp (répertoire de build) ou, pour les entrées positionnelles sans --build-dir, le répertoire temporaire du système, supprimé après l'exécution ; erl compile et charge le chargeur en mémoire. La requête contient les formes, les options, la liste des transformations, le nom de fichier du module et les sources de transformations du projet à construire. Avec un répertoire de build, la réponse et la sortie du chargeur sont gardées dans le cache de build et réutilisées tant que la requête et tout ce dont elle dépend restent inchangés ; la liste des modules collants (sticky) de l'hôte est gardée de la même façon. Le chargeur :

  1. vérifie la version d'OTP ;
  2. compile en mémoire les sources de transformations du projet demandées et les charge ;
  3. pour chaque transformation : la charge, retire les colonnes comme le demande parse_transform_info/0, appelle parse_transform/2 et formate les avertissements et les erreurs en {File, Location, Text} avec Module:format_error/1 ;
  4. écrit {ok, Forms, Warnings} ou {error, Errors, Warnings} et s'arrête.

Modules de transformation

Un module de transformation est recherché dans cet ordre :

  1. Fichiers .beam précompilés dans les répertoires de --transform-path DIR (répétable) et de l'option de projet transform_paths, qui sont placés dans le chemin de code du chargeur avant ceux d'OTP. C'est la seule façon d'utiliser une transformation qui provient d'une bibliothèque de dépendance : construire d'abord cette bibliothèque avec l'OTP de l'hôte (par exemple erlc -o deps/lib/ebin deps/lib/src/*.erl) ; Clause ne construit jamais les dépendances. Le .beam doit provenir de la version majeure d'OTP de l'hôte.
  2. Les sources du projet courant : un Module.erl dans le lot ou ses source_search_paths, compilé en mémoire par le chargeur avec les répertoires d'inclusion et les définitions de la cible à chaque exécution, avec les modules du projet qu'il appelle par leur nom (trouvés de la même façon). La recherche ignore les sources que ignore_dirs ou ignore_modules laissent de côté, comme le fait la recherche de modules. Clause cherche ces sources une fois par lot et les garde en mémoire ; le chargeur les compile tout de même pour chaque module sur lequel la transformation s'exécute, donc précompiler une grosse transformation fait gagner ce temps. Les modules que l'hôte charge depuis ses répertoires collants (kernel, stdlib, compiler, comme ms_transform ou qlc_pt lorsque les sources d'OTP sont sur un chemin de recherche) ne peuvent pas être rechargés, donc les modules propres à l'hôte sont utilisés : leurs sources, et les modules qu'eux seuls appellent, ne sont ni recherchés ni compilés. Clause demande ces modules à l'erl de l'hôte une fois par invocation.
  3. Les modules propres à OTP (ms_transform, qlc) depuis le chemin de code de l'hôte. OTP ne livre erl_id_trans que comme source d'exemple, pas comme module. Leurs en-têtes se résolvent comme tout include_lib (préprocesseur) : associer l'application, comme dans --app-dir "stdlib=C:/Program Files/Erlang OTP/lib/stdlib-8.1" pour include_lib("stdlib/include/ms_transform.hrl"), ou nommer directement la transformation avec -compile({parse_transform, ms_transform}).

Compiler une transformation du projet peut échouer là où son vrai build réussit : en-têtes de dépendances, transformation de la transformation, ou macros que seul un autre outil de build définit. Clause signale alors les messages du compilateur sur le source de la transformation, avec l'indication de la précompiler en .beam avec l'OTP de l'hôte et de passer son répertoire avec --transform-path. Une transformation du projet est aussi compilée par Clause comme module ordinaire lorsqu'elle fait partie du lot ; ses propres appels (erl_syntax, erl_parse, ...) peuvent alors exiger des modules de bibliothèque que Clause ne livre pas, donc garder les transformations hors des sources d'une cible lorsque seule la transformation en a besoin.

Diagnostics

SituationMessage
Pas d'OTP sur l'hôteFile: parse transform 'M' needs Erlang/OTP 29 on the host; pass --erl
--erl ne nomme aucun exécutableFile: erl not found: PATH
Mauvaise version d'OTPparse transforms need Erlang/OTP 29 on the host, found N
Transformation introuvableFile: undefined parse transform 'M' (texte d'OTP)
La transformation a levé une exceptionFile: error in parse transform 'M': ... (texte d'OTP)
Avertissement ou erreur de la transformationFile:Line:Column: Text tel que renvoyé
Le source de la transformation ne compile passes messages, puis l'indication de précompilation
Un chemin de transformation n'existe pasFile: parse transform path is not a directory: DIR
Un .beam d'un chemin de transformation ne se charge pasFile: cannot load parse transform 'M' from PATH: Reason
Le chargeur a échoué, dépassé le délai ou n'a écrit aucune réponseparse transform loader failed: ...
Formes renvoyées invalidesle diagnostic de l'analyseur à l'emplacement de la forme

Options

CLIProjet ([targets.options])Signification
--parse-transform Mparse_transforms = ["m"]Applique M à chaque module, avant ses propres transformations -compile (celles de la CLI d'abord)
--transform-path DIRtransform_paths = ["deps/*/ebin"]Chemin de code pour les fichiers .beam de transformations précompilées ; motifs comme dans source_search_paths
--erl PATH ou none—Exécutable erl de l'hôte ; none n'exécute jamais de code de l'hôte
--print-abstr—Affiche les formes après les transformations au lieu de compiler

Un projet qui utilise sa propre transformation et une transformation précompilée provenant d'une dépendance :

[[targets]]
name = "app"
sources = ["src/*.erl"]

[targets.options]
source_search_paths = ["transforms"]  # transforms/pt_rewrite.erl, compiled for each build
parse_transforms = ["pt_dep"]         # applied to every module of the target
transform_paths = ["deps/*/ebin"]     # deps/ptdep/ebin/pt_dep.beam, built with erlc first