flang и FTS: что унаследовано, что своё
Первое, что стоит уяснить перед чтением репозитория
digitable-lol/flang: он назван по
языку, но бо́льшая часть его содержимого — это FTS. Сам flang живёт в
подкаталоге flang/; в корне лежат ядро FTS на TypeScript (src/), его
документация, редакторская обвязка и девять инструментов в tools/.
Ещё несколько часов назад это приходилось выяснять самостоятельно: README.md
начинался словами «FTS — Formal Type Surface» и упоминал flang одним пунктом
списка. Теперь README переписан от языка и отвечает на вопрос сам — разделом
«How FTS and flang relate» и абзацем, который стоит привести целиком, потому
что так о себе пишут редко:
What is not settled, stated plainly. The TypeScript core is still the production implementation of FTS and still the reference the flang rewrite is measured against — not the other way round. […] So the repository is named for the language it is becoming, while a large part of its contents is still the FTS toolchain.
Различие практическое, а не педантичное. Почти вся инфраструктура репозитория принадлежит FTS, и приписывать её flang было бы прямым враньём о состоянии суточного языка.
Что чему принадлежит
| Что | Кому принадлежит |
|---|---|
flang/ — SPEC.md, src/, bin/, stdlib/, core/, examples/, test/ |
flang |
src/, schema/, docs/ |
FTS |
tools/ — девять инструментов, среди них ftsc, ftsvm, ftspec |
FTS |
editors/ — tree-sitter, VS Code, Chroma, Linguist |
FTS |
benchmarks/, web/, examples/ в корне |
FTS |
README.md, AGENTS.md, CONTRIBUTING.md в корне |
обоим |
Пара следствий, которые легко перепутать.
Восемь целевых языков — это не flang. README.md обещает печать в C, Rust,
C#, Java, Elixir, Go, Python и TypeScript; это возможности tools/ftsc,
проектного компилятора FTS-моделей. У flang бэкендов пять, и их список видно из
ответа самого инструмента на несуществующую цель:
$ node flang/bin/flang.mjs emit проба.flang --target zzz
{"error":"неизвестная цель «zzz»; доступны: c, go, js, python, rust", …}
Списки сближаются, но не совпадают. Четыре цели flang есть и у ftsc — C, Go,
Python, Rust, — а пятая, js, своя: ftsc печатает TypeScript. В обратную
сторону у ftsc остаются C#, Java и Elixir, которых у flang нет вовсе. Rust и
Python, кстати, появились у flang уже после того, как была написана первая
редакция этого трека.
Подсветки синтаксиса для flang нет. Каталог editors/ содержит
tree-sitter-fts, vscode-fts, chroma/fts.xml и запись для Linguist — все
про .fts. Ни грамматики, ни расширения, ни описания языка для .flang в
репозитории нет. Подробнее — в главе «Чего в языке пока нет».
Бенчмарки — не про flang. В benchmarks/ лежит воспроизводимый стенд и
базовая линия на Apple M1 Max, где измеряется компиляция и валидация
FTS-модели. Замеров производительности интерпретатора flang или сгенерированного
им кода в репозитории нет.
Что flang взял у FTS
Взял он поверхность и контракты.
Отступный синтаксис без скобок. Ни фигурных скобок, ни точек с запятой, ни стрелок как структурных элементов. Вложенность выражается отступом, шириной табуляции считаются два пробела.
Имена как фразы. «Рассчитать скидку», «постоянный клиент» — имя пишется в
ёлочках или обычных кавычках и может содержать пробелы. Это решение FTS, и flang
его повторяет буквально, вплоть до нормализации NFC в лексере.
Две равноправные поверхности. Русская и английская компилируются в один AST.
Таблица ключевых слов в flang/src/lexer.mjs устроена как «канонический
идентификатор → список поверхностей»:
let: ["пусть", "let"],
if: ["если", "if"],
then: ["то", "then"],
else: ["иначе", "else"],
match: ["разбор", "match"],
case: ["случай", "case"],
Парсер не знает, на каком языке написан исходник.
Формат диагностик. { code, message, severity, span } — ровно как в ядре.
Коды у flang свои (FLANG_LEX, FLANG_PARSE, FLANG_TYPE,
FLANG_NOT_TOTAL, …), но форма записи общая, чтобы редакторы и CI, уже
умеющие читать вывод FTS, читали и вывод flang.
Контракт CLI. Результат — JSON в stdout, диагностика — JSON в stderr, отказ —
ненулевой код возврата. В шапке flang/bin/flang.mjs это объяснено прямо:
расхождение в контракте вывода стоило бы дороже, чем любая «улучшенная» подача.
Арифметика. Числа — IEEE-754 double, проценты считаются как
(процент / 100) * значение именно в этом порядке. Не из любви к деталям: смена
порядка множителей меняет последний бит мантиссы, и уже сгенерированный код
разошёлся бы с новым.
Что flang добавил
FTS-модель описывает предметное решение: объекты с полями, утилиты с правилами, свойства и примеры. Циклов, рекурсии, списков и строковых вычислений там нет — и это осознанное ограничение исполняемой спецификации, а не недоделка.
flang добавляет ровно то, без чего нельзя написать программу:
- суммы типов (
тип «Токен»с вариантами) и сопоставление с образцом; - списки как полноценные значения, с
голова/хвост,отобразить,отфильтровать,свёртка; - строки как данные, а не только как тип поля;
- рекурсию;
- локальные связывания
пусть; - модули с импортом.
Одну вещь он сознательно не добавил: функции не являются значениями первого
класса. Это не забыто, а записано в flang/SPEC.md, раздел 3, вместе с
обоснованием — см. главу «Система типов».
Обещание совместимости
Центральный тезис записан в flang/SPEC.md, раздел 1:
вся существующая FTS-модель — это валидная программа flang, целиком лежащая в тотальном классе
И тут же — почему это не декларация:
Обратная совместимость не опциональна:
flangобязан принимать любой.ftsбез правок и давать тот же результат, что ядро FTS. Это проверяется тестом на всех моделях репозитория.
Механика — модуль flang/src/compat.mjs, мост из FtsDocument в AST flang.
Объект становится записью, утилита — тотальной функцией, правила —
последовательностью независимых если, свойства — постусловиями, примеры —
примерами. В шапке модуля перечислено, что именно повторяется буквально, а не
«по смыслу»: порядок правил, короткое замыкание условий внутри правила, порядок
множителей в процентах и проверка свойств после всех правил.
Отдельное решение стоит отметить: код и текст ошибки нарушенного свойства едут
в AST данными, полем postconditions функции, а не знанием, зашитым в
интерпретатор. Иначе совпадение кодов ошибок между двумя движками зависело бы
от реализации и было бы случайным.
Масштаб проверки заявлен в корневом README.md: сверка с ядром на 19 593
входах, ноль расхождений.
Что из этого следует для практики
Ничего срочного. FTS-модель по-прежнему исполняется ядром FTS, и переходить на flang ради тех же моделей незачем. Значение обещания в другом: оно фиксирует, что flang не «новый язык рядом», а надстройка, у которой есть проверяемое обязательство перед предшественником. Для языка, который меняется ежедневно, это единственная часть, на которую можно опереться.
Практическая оговорка: чтобы CLI прочитал .fts, нужно сначала собрать ядро
FTS — мост подгружает dist/src/index.js. Без сборки получите честный
ERR_MODULE_NOT_FOUND. Файлы .flang работают без этого; подробности в
главе «Инструмент».
Дальше — глава «Два класса программ», про решение, ради которого язык вообще устроен так, а не иначе.