flang — полный язык поверх FTS Инструмент: команды flang и контракт вывода
0%

Инструмент: команды flang и контракт вывода

Инструмент: команды 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 лучше, чем чтение файла из случайного каталога.

Такие места в репозитории попадаются часто: решение принято, альтернатива названа, причина отказа записана рядом.

Дальше — глава «Синтаксис по факту», синтаксис по фактическим файлам.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше