Clause
← Уся документація

Англійський оригінал змінився після перекладу, тож частина тексту може бути застарілою. Читати англійською

Перекладено з англійського оригіналу · 1e3a39c8-dirty · 2026-10-26

Parse transforms

Використання parse transforms

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

Clause застосовує {parse_transform, Module} так само, як erlc. Він експортує розібраний модуль як форми OTP abstract format, запускає модуль перетворення на Erlang/OTP 29, встановленому на хості, імпортує повернені форми як новий синтаксис модуля й компілює його (compiler/src/transforms/). OTP потрібен лише під час компіляції модуля, що використовує перетворення; згенерованим програмам він ніколи не потрібен.

Поведінка OTP

За зафіксованим lib/compiler/src/compile.erl (transform_module, foldl_transform, maybe_strip_columns) і спостереженнями під OTP 29.1.1:

Конвеєр Clause

  1. Препроцесувати й розібрати модуль. Помилки препроцесора чи розбору зупиняють роботу тут; перетворення не виконуються для модуля з помилками (відмінність від OTP, який передає помилки далі як форми).
  2. Зібрати перетворення в порядку OTP: опції --parse-transform і parse_transforms проєкту, потім атрибути -compile. Модуль без жодного пропускає решту й ніколи не запускає OTP.
  3. Видалити ці опції з форм і експортувати їх: анотації {Line, Column} так, як їх призначає erl_parse, атрибути file від epp і eof. Наперед визначених функцій у формах ще немає.
  4. Один раз запустити завантажувач для модуля.
  5. Імпортувати повернені форми, перетворивши їх на токени, розташовані за їхніми анотаціями, і розібравши парсером Clause; атрибут file змінює файл наступних форм, як в erl_lint.
  6. Додати наперед визначені функції, потім, як зазвичай, виконати lint, аналіз і компіляцію.

Дії, що запускають перетворення, перелічено в розділі використання parse transforms.

Обмін даними

Імпорт форм

Повернені форми та вхідні файли .abstr (позиційні або sources проєкту) стають токенами, розташованими за їхніми анотаціями, і проходять через парсер Clause, тож отримують ті самі синтаксичні перевірки, що й вихідний код; пріоритет операторів визначає, де додаються дужки. Приймаються {Line, Column}, просто Line (показується як file:line:) і списки властивостей із location і file; generated ігнорується. Рядок 0, який перетворення qlc з OTP надає згенерованому коду, отримує розташування вузла, імпортованого перед ним. Атрибут file змінює файл наступних форм; якщо цей файл існує, імпортований код зберігає свої налагоджувальні рядки й коментарі з вихідним кодом. Некоректні форми є помилками в місці їхньої анотації (unknown expression bogus, expected {Name, Arity}, invalid annotation), і модуль не компілюється; форми OTP {error, _}/{warning, _} повідомляються з їхнім дескриптором, для форматування якого потрібен OTP. Імпорт експортованого модуля й повторний експорт дають ті самі форми.

OTP хоста

Завантажувач

compiler/src/transforms/loader/clause_transform_loader.erl належить проєкту і вбудований у clau. Кожен запуск записує його й запит у новий тимчасовий каталог, clause-transform-* у <build_dir>/tmp (каталог збірки) або, для позиційних вхідних файлів без --build-dir, у системний тимчасовий каталог, який видаляється після запуску; erl компілює й завантажує завантажувач у пам'яті. Запит містить форми, опції, список перетворень, ім'я файлу модуля і вихідні файли перетворень проєкту, які треба зібрати. З каталогом збірки відповідь і вивід завантажувача зберігаються в кеші збірки й використовуються повторно, поки запит і все, від чого він залежить, не змінюються; список sticky-модулів хоста зберігається так само. Завантажувач:

  1. перевіряє реліз OTP;
  2. компілює запитані вихідні файли перетворень проєкту в пам'яті й завантажує їх;
  3. для кожного перетворення: завантажує його, прибирає стовпці, як просить parse_transform_info/0, викликає parse_transform/2 і форматує попередження й помилки в {File, Location, Text} через Module:format_error/1;
  4. записує {ok, Forms, Warnings} або {error, Errors, Warnings} і зупиняється.

Модулі перетворень

Модуль перетворення шукається в такому порядку:

  1. Попередньо скомпільовані файли .beam у каталогах --transform-path DIR (можна повторювати) і опції проєкту transform_paths, які потрапляють у шлях коду завантажувача перед каталогами OTP. Це єдиний спосіб використати перетворення з бібліотеки-залежності: спершу зберіть цю бібліотеку за допомогою OTP хоста (наприклад, erlc -o deps/lib/ebin deps/lib/src/*.erl); Clause ніколи не збирає залежності. .beam має походити з того самого мажорного релізу OTP, що й хост.
  2. Вихідні файли поточного проєкту: Module.erl у пакеті або в його source_search_paths, який завантажувач компілює в пам'яті з каталогами include і визначеннями цілі під час кожного запуску, разом із модулями проєкту, які він викликає за іменем (знайденими так само). Пошук пропускає вихідні файли, які виключають ignore_dirs або ignore_modules, як це робить пошук модулів. Clause шукає ці вихідні файли один раз на пакет і тримає їх у пам'яті; завантажувач однаково компілює їх для кожного модуля, до якого застосовується перетворення, тож велике перетворення варто попередньо скомпілювати, щоб заощадити цей час. Модулі, які хост завантажує зі своїх sticky-каталогів (kernel, stdlib, compiler, як-от ms_transform чи qlc_pt, коли вихідні файли OTP є в шляху пошуку), не можна перезавантажити, тому використовуються власні модулі хоста: їхні вихідні файли й модулі, які викликають лише вони, не шукаються й не компілюються. Clause запитує в erl хоста ці модулі один раз за виклик.
  3. Власні модулі OTP (ms_transform, qlc) зі шляху коду хоста. OTP постачає erl_id_trans лише як приклад вихідного коду, а не як модуль. Їхні заголовки розв'язуються так само, як кожен include_lib (препроцесор): зіставте застосунок, як у --app-dir "stdlib=C:/Program Files/Erlang OTP/lib/stdlib-8.1" для include_lib("stdlib/include/ms_transform.hrl"), або назвіть перетворення напряму через -compile({parse_transform, ms_transform}).

Компіляція перетворення проєкту може завершитися невдачею там, де його справжня збірка вдається: заголовки залежностей, перетворення самого перетворення або макроси, які визначає лише інший інструмент збірки. Тоді Clause виводить повідомлення компілятора щодо вихідного файлу перетворення з підказкою попередньо скомпілювати його в .beam за допомогою OTP хоста й передати його каталог через --transform-path. Перетворення проєкту також компілюється Clause як звичайний модуль, коли воно входить до пакета; тоді його власним викликам (erl_syntax, erl_parse, ...) можуть знадобитися бібліотечні модулі, яких Clause не постачає, тож не додавайте перетворення до sources цілі, якщо вони потрібні лише перетворенню.

Діагностика

СитуаціяПовідомлення
Немає OTP на хостіFile: parse transform 'M' needs Erlang/OTP 29 on the host; pass --erl
--erl не вказує на виконуваний файлFile: erl not found: PATH
Неправильний реліз OTPparse transforms need Erlang/OTP 29 on the host, found N
Перетворення не знайденоFile: undefined parse transform 'M' (текст OTP)
Перетворення згенерувало винятокFile: error in parse transform 'M': ... (текст OTP)
Попередження чи помилка перетворенняFile:Line:Column: Text як повернуто
Вихідний файл перетворення не компілюєтьсяйого повідомлення, потім підказка про попередню компіляцію
Шлях перетворень не існуєFile: parse transform path is not a directory: DIR
.beam у шляху перетворень не завантажуєтьсяFile: cannot load parse transform 'M' from PATH: Reason
Завантажувач збоїв, перевищив час або не записав відповідіparse transform loader failed: ...
Некоректні повернені формидіагностика парсера в місці форми

Опції

CLIПроєкт ([targets.options])Значення
--parse-transform Mparse_transforms = ["m"]Застосувати M до кожного модуля перед його власними перетвореннями з -compile (спершу ті, що з CLI)
--transform-path DIRtransform_paths = ["deps/*/ebin"]Шлях коду для попередньо скомпільованих файлів .beam перетворень; шаблони як у source_search_paths
--erl PATH або none—Виконуваний файл erl хоста; none ніколи не запускає код хоста
--print-abstr—Надрукувати форми після перетворень замість компіляції

Проєкт, що використовує власне перетворення і попередньо скомпільоване з залежності:

[[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