Инструментарий: CLI, WebAssembly, MCP и JSON
Эта глава — про инструменты вокруг языка, а не про синтаксис: как поставить компилятор, какими командами проверять модель и что именно летит между процессами, если правило нужно не только в терминале, но и в Node.js, в браузере и у ИИ-агента.
Минимальный пример
модуль «Продажи»
тотальная функция «Скидка»
принимает сумма: неотрицательное, постоянный: признак
возвращает число
обеспечивает «за мелкую покупку скидки нет» если сумма меньше 10000 то результат равен 0 иначе да
пример «мелкая покупка»
дано сумма равно 5000
дано постоянный равно да
ожидается 0
пример «большая покупка постоянного клиента»
дано сумма равно 20000
дано постоянный равно да
ожидается 3000
если сумма не меньше 10000
то (если постоянный то (сумма умножить на 15) делить на 100 иначе (сумма умножить на 10) делить на 100)
иначе 0
Дальше в этой главе — какие команды с ним работают и что каждая отвечает.
Что делает компилятор
Компилятор один — двоичный flang, написанный на самом flang и собранный из C.
Все команды идут через одни и те же проверки: разбор, типы, завершаемость,
ядро доказательств. Поэтому flang check в терминале, кнопка «Проверить» в
песочнице и вызов flang_check у агента отвечают одинаково.
Песочница на странице языка — тот же компилятор, собранный в WebAssembly. Он работает во вкладке браузера, без сервера.
Установка
Язык открыт — github.com/digitable-lol/flang
— и ставится одной командой, без sudo:
$ curl -fsSL https://raw.githubusercontent.com/digitable-lol/flang/main/install.sh | sh
$ flang --version
flang 0.7.29
Установщик выбирает сборку под систему, сверяет её контрольную сумму и кладёт в
~/.local. Версию закрепляет FLANG_VERSION=0.7.29, место — PREFIX. Есть и
Homebrew: brew install digitable-lol/tap/flang-package.
Из исходников нужен только компилятор C и make:
git clone https://github.com/digitable-lol/flang && cd flang
make -C bootstrap -j4
make -C bootstrap install PREFIX=$HOME/.local
У LLVM есть свой flang — компилятор Фортрана. Если стоят оба, побеждает
первый в PATH. Наш печатает flang 0.7.29 первой строкой flang --version.
Команды
| Команда | Что делает | Код возврата |
|---|---|---|
flang check файл |
разбор, типы, завершение, примеры | 0 — замечаний нет, 1 — есть |
flang check файл --proof |
то же и отчёт о доказательствах | 0 — всё доказано, 3 — не всё |
flang test файл |
прогон примеров | 0 — все сошлись |
flang run файл --function «Имя» --args '…' |
вычислить одну функцию | 3 — программа не доказана |
flang emit файл --target язык |
напечатать программу на другом языке | 1 — программа не прошла проверку |
flang ast файл |
разобранная программа JSON-ом | 0 |
flang facts файл --facts факты.json --claims '[…]' |
проверить утверждения на фактах | 1 — опровергнуто |
flang --mcp-mode |
служба для ИИ-помощника | — |
flang lsp --stdio |
языковой сервер для редактора | — |
На примере выше:
$ flang check sales.flang
модуль «Продажи»: функций 1, из них с доказанным завершением 1; типов 0
sales.flang: проверено — разбор, типы, завершаемость, ядро и примеры; замечаний нет
$ flang test sales.flang
sales.flang: примеров 2, прошло 2, не прошло 0
$ flang run sales.flang --function Скидка --args '{"сумма": 20000, "постоянный": true}'
доказано: утверждений 1
3000
Первая строка flang run уходит в поток ошибок: это вердикт о доказанности.
Значение — в стандартный вывод. Программу с недоказанными обещаниями flang run
не считает и выходит кодом 3, пока не дать согласие ключом --trust.
flang emit печатает программу в одну из десяти целей: c, cpp, go, rust, java,
js, ts, elixir, python, csharp. Напечатанное работает без flang:
$ flang emit sales.flang --target python --out printed
проверок при работе снято 1: постусловие доказано ядром обо всех входах и
проверено примерами функции — в напечатанный код оно не едет.
напечатано файлов 4, байт 102406, в printed
проверено перед печатью — разбор, типы, завершаемость и ядро доказательств.
ПРИМЕРЫ НЕ ПРОГНАНЫ: их считает вычислитель на самом языке, и на самых больших
программах он в предел шагов этого бинарника не укладывается — свяжи с ними
печать, и компилятор перестал бы печатать сам себя. Прогоните их отдельно:
flang test <файл>
запустить: cd <каталог> && python3 flang_cli.py <модуль>
Обратите внимание на строку про примеры: emit их не гоняет. В сборке
flang test стоит отдельным шагом до печати.
Ловушка: файлы прежней поверхности
Файл .fts со словами категория, утилита, правило сегодняшний компилятор
принимает и отвечает зелёным, ничего не проверив:
$ flang check order-discount.fts
модуль «Продажи»: функций 0, из них с доказанным завершением 0; типов 1
order-discount.fts: проверено — разбор, типы, завершаемость, ядро и примеры; замечаний нет
$ flang test order-discount.fts
flang test: под «order-discount.fts» не нашлось ни одного .flang
Читать такой ответ надо по числам: функций 0. Утилита функцией не стала,
правила и примеры не исполнялись. Как перенести модель — в главе
«Старые модели».
Три способа позвать правило
Процесс. CI, shell, Python и Go зовут flang run или flang facts и
читают ответ. Аргументы flang run — плоский JSON скаляров. Записей и списков
он не принимает: для них в модели заводят функцию со скалярными аргументами
либо зовут flang facts.
Напечатанный модуль. Node.js и браузер берут модуль, который напечатал
flang emit --target js или --target ts. Это обычная функция без
зависимостей и без компилятора в рантайме. Отдельной JS-библиотеки компилятора
с функциями compile и validate больше нет: старый npm-пакет
@digitable-lol/fts 0.4.7 понимает только прежнюю поверхность. Как это
устроено в сервисе курса — в главе «Node.js и HTTP».
MCP. Агент получает два средства: flang_check (вход — source) и
flang_prove (входы — source и claim). Подключение:
{"mcpServers": {"flang-validator": {"command": "flang", "args": ["--mcp-mode"], "env": {}}}}
Исходник уходит аргументом вызова, а не путём к файлу: агент не получает доступа к файловой системе. Поля «ок» в ответах нет. Каждое обещание приходит одним из слов отчёта — «доказано», «сетка N», «объявлено, не доказано», — и агент не может пересказать «сетку» как «доказано», не соврав.
JSON вместо текста
Единого «канонического документа» модели, как у прежнего FTS, у flang нет. Есть три машиночитаемых ответа, и у каждого своя работа.
flang ast — разобранная программа. Верхние ключи: flang (версия формата),
module, types, functions, legacy. Функция из примера (выдержка):
$ flang ast sales.flang | jq '.functions[0] | {name, total, examples: [.examples[] | {name, args, expected}]}'
{
"name": "Скидка",
"total": true,
"examples": [
{
"name": "мелкая покупка",
"args": {
"сумма": 5000,
"постоянный": true
},
"expected": 0
},
{
"name": "большая покупка постоянного клиента",
"args": {
"сумма": 20000,
"постоянный": true
},
"expected": 3000
}
]
}
Из types фронтенд строит форму, из examples — таблицу контрольных случаев.
Тела функций там тоже есть, деревом. Исполнять его самим не нужно: для этого
есть flang emit.
flang facts — вердикт по утверждению: holds, status (verified или
refuted), why словами и steps — шаги вычисления. Его разбор — в главе
«Модель и границы языка».
flang test --json — итог прогона примеров одной строкой:
$ flang test sales.flang --json | jq '{valid, total, passed, failed}'
{
"valid": true,
"total": 2,
"passed": 2,
"failed": 0
}
JSON-first дисциплина
Не ищите в выводе слово «проверено» — ориентируйтесь на код возврата:
if flang check policy.flang --proof && flang test policy.flang; then
echo "правило доказано, примеры сошлись"
else
echo "правило не прошло: смотрите отчёт" >&2
exit 1
fi
Код 3 у check --proof здесь тоже провал, и это намеренно: «проверено на
примерах» не равно «доказано». Если команда сознательно принимает недоказанное
обещание, это решение пишется в сборке явно, а не прячется за || true.
Практика в песочнице
Нажмите «Проверить» и найдите в отчёте единственное доказанное обещание. Какие три держатся только на примерах?
Добавьте функции «Рассчитать доставку» пример: вес 20, расстояние 100, без срочности и страховки. Посчитайте ожидаемое число вручную по функциям «Базовый тариф» и «Надбавки» и добейтесь, чтобы пример сошёлся.
Найдите функцию «Скидка по сумме». Зачем она нужна, если есть «Рассчитать
скидку»? Подсказка: что принимает --args у flang run.
Типичные ошибки
Запись в --args. flang run принимает только плоский объект скаляров:
$ flang run sales.flang --function Скидка --args '{"сумма": {"x": 1}, "постоянный": true}'
flang run: «--args» разобрать не удалось — ждался плоский объект скаляров, вроде '{"н":10}'
Это код 2 — кривой вызов. Правка — функция со скалярными аргументами или
flang facts.
FLANG_TYPE на входе. Аргументы сверяются с объявленными типами до
вычисления. Строка вместо числа даёт код 1:
$ flang run sales.flang --function Скидка --args '{"сумма": "много", "постоянный": true}'
доказано: утверждений 1
FLANG_TYPE: вызов функции «Скидка»: аргумент «сумма» не соответствует типу неотрицательное
Сервис курса examples/spec/discount-api отвечает на такой вход 400, а не
500: причина в данных запроса, а не в логике сервера.
Чек-лист
- Я знаю, какую команду звать для проверки, для примеров, для одного вычисления и для печати в свой язык.
- Я объясняю разницу между процессом
flang, напечатанным модулем и MCP одной фразой: один компилятор, три способа позвать. - Я нахожу в
flang astтипы, функции и примеры, не читая исходник. - Я проверяю результат по коду возврата и не считаю код 3 успехом.