Инструмент: команды flang и контракт вывода
Прежде чем разбирать синтаксис, полезно получить инструмент, который скажет,
правы вы или нет. У flang это один файл — flang/bin/flang.mjs, 533 строки,
запускается напрямую из исходников.
Установка, которой нет
Пакет в реестре не опубликован; package.json репозитория помечен
"private": true и называется @digitable/fts. Отдельного пакета для flang
нет вовсе. Поэтому запуск выглядит так:
git clone https://github.com/digitable-lol/flang.git
cd flang
node flang/bin/flang.mjs check flang/stdlib/lists.flang --pretty
Ни npm install, ни сборки для этого не нужно. Приятная особенность: весь путь
.flang — лексер, парсер, типы, тотальность, интерпретатор, связывание модулей,
кодогенерация — написан на ES-модулях без единой зависимости, поэтому работает
на голом Node. В README.md заявлена версия 20 или новее; наши прогоны шли на
Node 24.18.0.
Шесть команд
Справка выводится без аргументов:
Использование:
flang check <файл> [--pretty]
flang run <файл> --function «Имя» --args '{"поле": 1}' [--pretty]
flang test <файл> [--pretty]
flang facts <файл> --facts факты.json --claims '["…"]' [--steps N] [--pretty]
flang ast <файл> [--pretty]
flang emit <файл> --target <язык> [--out каталог] [--cli|--no-cli]
[--index-base 0|1] [--max-depth N] [--pretty]
flang version
Замечание для тех, кто читает flang/SPEC.md: в разделе 6 указан набор
check | run | test | ast | fmt. Команды fmt не существует — CLI отвечает
unknown command 'fmt'. Зато существуют facts и emit, которых в том разделе
нет. Спецификация отстаёт от кода; когда они расходятся, верить надо коду.
check
Разбор, типы и тотальность за один проход:
$ node flang/bin/flang.mjs check flang/stdlib/lists.flang --pretty
{
"valid": true,
"module": "Списки",
"functions": [
{ "name": "Длина", "total": true },
{ "name": "Приписать в начало", "total": true },
…
],
"types": [],
"diagnostics": []
}
Поле total здесь — не то, что написал автор, а то, что доказал анализ.
test
Исполняет примеры, лежащие внутри функций:
$ node flang/bin/flang.mjs test flang/stdlib/lists.flang --pretty
{
"valid": true,
"total": 45,
"passed": 45,
"failed": 0,
"results": [ … ]
}
Отдельно отметим: примеры, возвращающие списки, сравниваются структурно и
проходят. В flang/examples/leetcode/index.json записано обратное — что
сравнение идёт через Object.is и любой пример со списком проваливается. На
момент нашей проверки это уже неверно: 45 примеров lists.flang, среди которых
множество списочных, проходят все. Файл index.json местами описывает состояние
языка на несколько часов раньше — см. главу «Чего в языке пока нет».
run
Вызов одной функции:
$ node flang/bin/flang.mjs run проба.flang \
--function "Сумма больше порога" --args '{"суммы":[1,2],"порог":10}' --pretty
{
"function": "Сумма больше порога",
"args": { "суммы": [1, 2], "порог": 10 },
"result": false
}
--args принимает объект «имя параметра → значение». Для функции, пришедшей из
FTS-утилиты — то есть с единственным параметром-записью, — CLI умеет обернуть
плоский объект сам: это сделано в bindArguments, чтобы форма --args не
менялась при переходе с FTS.
facts
Встраиваемый факт-чекинг. Утверждение — это строка вида
<терм> <оператор> <терм>, и грамматика термов узкая (flang/src/factcheck.mjs):
факт «Имя»— значение факта;поле «Имя» факта «Имя»— поле записи из фактов;«Функция» от факт1, факт2— вызов с фактами в аргументах;- литерал: число,
да,нет,ничто, строка в кавычках.
Операторы — равно, не равно, больше, меньше, не больше, не меньше и
их английские аналоги.
$ node flang/bin/flang.mjs facts факты.flang --facts факты.json \
--claims '["«Сумма больше порога» от суммы, порог равно да"]' --pretty
{
"ok": true,
"results": [ {
"claim": "«Сумма больше порога» от суммы, порог равно да",
"holds": true,
"why": "«Сумма больше порога» от факта «суммы», факта «порог» = да;
требование «равно да» выполнено",
"status": "verified"
} ]
}
В ответе есть массив steps — след разбора, проверки тотальности и вычисления.
Это не отладочный вывод, а часть смысла режима: ответ должен быть
воспроизводимым и объяснимым.
Ненулевой код возврата при ok: false — чтобы CI мог на нём падать. Но сам JSON
уходит в stdout, а не в stderr: опровергнутое утверждение — это результат
работы, а не сбой инструмента.
ast
Печатает программу в канонический JSON — тот самый контракт между слоями из раздела 5 спецификации. Полезно, когда надо понять, во что превратилась конструкция; разбор формата в главе «Синтаксис по факту».
emit
Печать в целевой язык. Разобрана отдельно в главе «Кодогенерация»;
здесь достаточно знать, что целей пять и что список берётся не из константы в
CLI, а из содержимого каталога flang/src/emit:
$ node flang/bin/flang.mjs emit проба.flang --target zzz
{"error":"неизвестная цель «zzz»; доступны: c, go, js, python, rust", …}
Устройство «список целей — это содержимое каталога» уже окупилось: Rust и
Python появились как два новых файла рядом с c.mjs, go.mjs и js.mjs, и
ни справка, ни диагностика неизвестной цели правки не потребовали.
Контракт вывода
Он унаследован от FTS и соблюдается всеми командами:
- результат — JSON в stdout;
- диагностика — JSON в stderr;
- отказ — ненулевой код возврата, причём
2для ошибки вызова (не хватает ключа, кривой JSON) и1для ошибки модели или вычисления.
Смысл в том, что вокруг FTS уже есть скрипты, агенты и CI, умеющие читать этот
формат. Из шапки bin/flang.mjs: «Расхождение в контракте вывода стоило бы
дороже, чем любая „улучшенная“ подача».
Практическое следствие: --pretty нужен только человеку. В пайплайне вывод
идёт одной строкой и режется jq.
Три вида входного файла
CLI принимает не только .flang:
| Расширение | Что происходит |
|---|---|
.flang, .fl |
разбор parser.mjs, затем связывание модулей link.mjs |
.json |
готовый AST, читается как есть |
.fts |
модель FTS, переводится мостом compat.mjs |
Если расширения нет, формат угадывается по содержимому: текст, начинающийся с
{, читается как JSON, остальное — как FTS.
Важная оговорка про .fts. Мост подгружает собранное ядро FTS
(dist/src/index.js), поэтому без npm install && npm run build эта ветка не
работает:
$ # на свежем клоне, до npm install && npm run build
$ node flang/bin/flang.mjs check examples/utilities/discount.fts
{"error":"Cannot find module '…/dist/src/index.js' imported from …/flang/bin/flang.mjs",
"diagnostics":[{"code":"ERR_MODULE_NOT_FOUND", …}]}
Каталог dist/ под контролем версий не лежит, так что в свежем клоне его нет по
определению; импорт стоит в flang/bin/flang.mjs, строка 288. Диагностика
честная, но не подсказывает решение. Ветка .flang от сборки не зависит вовсе.
Про stdin
Файл - означает стандартный ввод, и одна деталь здесь сделана намеренно: для
stdin связывание модулей не запускается. Причина в комментарии:
Со стандартного ввода нет каталога, относительно которого разрешается
использует … из "…", поэтому связывание для него не запускаем: честныйFLANG_UNKNOWN_NAMEлучше, чем чтение файла из случайного каталога.
Такие места в репозитории попадаются часто: решение принято, альтернатива названа, причина отказа записана рядом.
Дальше — глава «Синтаксис по факту», синтаксис по фактическим файлам.