Библиотеки на языке
Язык, на котором нечего написать, кроме примеров из справочника, языком не
работает. Здесь — три библиотеки, которые написаны на flang целиком: ни
Go, ни питона, ни JavaScript, ни Makefile в деревьях нет вовсе, и это не
обещание в README, а проверка в их конвейерах (tools/licensing.flang плюс
нарочно тупой сторож над git ls-files).
Все три выросли из существующих инструментов портала, а не написаны с нуля
ради примера. Экран и чтение окружения digitdisk были написаны
на Go; их переписали на flang, сверили с оригиналом на общей сетке входов и Go
убрали — так вышли flang-tui и flang-env. Арифметику ленты окон перенесли из
digitwm тем же способом — переписали на flang и сверили с
исходным ribbon.c побайтно на 526 871 входе, — но с другим концом: C там не
убрали, а обернули. Десять функций ribbon.c стали тонкими обёртками, а саму
арифметику печатает flang-ribbon, пришпиленный в дерево digitwm субмодулем
(ribbon-flang/).
| Репозиторий | Что держит | Строк на flang | Печатается в |
|---|---|---|---|
| flang-tui | раскладка терминального экрана: полосы, обрезка, укладка, вкладки, прокрутка | 3533 | Go, C |
| flang-ribbon | арифметика ленты окон: смещения, ширина колонки, высота окна, вставка, панели | 1432 | C, Go |
| flang-env | разбор значений окружения: глубина цвета, язык, NO_COLOR |
1210 | Go, C (слой 1) |
Числа flang-tui и flang-env сняты с деревьев 2 сентября 2026: flang-tui на
617fe0d, flang-env на 37120d0, компилятор — 0.6.2. Числа flang-ribbon —
тем же способом и в тот же день, на 66d3493. Команда, которая их печатает:
git ls-files '*.flang' | xargs wc -l. Во все три суммы входит общая обвязка
— лицензионный сторож (303 строки, дословно один и тот же файл во всех трёх
деревьях) и список команд ярлыки.flang (свой в каждом дереве, длина разная —
считает число собственных ярлыков); собственно библиотечного кода в flang/
— 3122 строки в flang-tui, 1035 в flang-ribbon и 807 в
flang-env.
flang-tui — экран терминала
Что держит. Шесть модулей, и граница у всех одна: данные плюс ширина и высота → готовые строки. Ни одного поручения, ни одного чтения, ни одной записи.
| Модуль | Файл | Что делает |
|---|---|---|
| «Screen» | flang/screen.flang |
полоса заполнения, обрезка по печатаемым ячейкам, укладка раздела в высоту, кадр целиком |
| «Format» | flang/format.flang |
размеры, доли, проценты, длительности, счётчики разрядами, прочерк |
| «Colour» | flang/colour.flang |
истинный цвет, куб xterm-256, шестнадцать базовых |
| «History» | flang/history.flang |
история бегущего графика и строка его знаков |
| «Scroll» | flang/scroll.flang |
прокрутка: положение, высота окна, длина содержимого, нажатие → положение |
| «Tabs» | flang/tabs.flang |
вкладки: состояние плюс нажатие → состояние |
Сырой режим терминала, чтение клавиш, буфер вывода и SIGWINCH остаются у
хозяина. Отсюда и польза: то, что здесь лежит, — чистые тотальные функции, а
значит, их можно напечатать в чужой язык и там же проверить.
Как подключается. Не пакетом, а печатью: ./ярлык печать кладёт каждый
модуль в свой каталог out-go/<имя> и out-c/<имя>, и напечатанное уезжает
внутрь чужого двоичного файла — без рантайма, без Node, без интерпретатора.
Одна правка напечатанного делается sed, а не рукой: у всех целей печати имя
модуля Go одно (flangprogram), и после печати оно переписывается на
flang<имя> — иначе шесть библиотек в одной сборке столкнулись бы именами.
Чем проверяется. ./ярлык проверка — это flang check и flang test.
Ведомость — вывод flang check --proof, и слова в ней не смешиваются:
| Функций | Все тотальны | Утверждений | доказано | сетка | объявлено, не доказано | Примеров | |
|---|---|---|---|---|---|---|---|
| Шесть модулей | 81 | 81 из 81 | 67 | 12 | 46 | 9 | 508 |
«Доказано» — про все входы; «сетка N» — посчитано на N значениях автора, это не доказательство; «объявлено, не доказано» — считает рантайм на тех входах, что придут. Из 67 утверждений доказаны 12.
Сверка с оригиналом кончилась, и это сказано вслух. 494 467 входов против живого Go-эталона, 0 расхождений — но гонялась она руками и в конвейере не стояла ни разу. Сверщик убран из дерева вместе с последним Go; ответы эталона перенесены в модули 362 примерами, которые идут на каждый толчок. 0,07 % сетки, зато всегда. Пример закрепляет значение и не заметит, если изменится digitdisk, — обратного здесь никто не утверждает.
Цена обещаний названа числом. Кадр 200×50, go test -bench, три прогона,
1 сентября 2026: напечатанный в Go код с постусловиями — 55,9 мс на кадр,
без них — 10,1 мс, ручной Go — 0,13 мс. То есть обещания стоят 5,3 раза
(18 кадров в секунду против 99). Отсюда правило: утверждение пишется, но
рабочая сборка печатается без него — проверка живёт в flang check и
flang test, а не в горячем цикле.
flang-ribbon — лента окон
Что держит. Четыре модуля, вопрос тот же, что у flang-tui: данные плюс размеры экрана → куда что встаёт. Ни X11, ни событийного цикла, ни чтения конфига.
| Модуль | Файл | Что решает |
|---|---|---|
| «Viewport» | flang/viewport.flang |
смещение по ленте и вниз по стеку; обрезка смещения после смены размера экрана |
| «Geometry» | flang/geometry.flang |
ширина колонки от доли и от пресета, высота окна в стеке, целочисленное деление C99 |
| «Placement» | flang/placement.flang |
куда встаёт новое окно, что получает фокус после закрытия |
| «Strut» | flang/strut.flang |
пересекаются ли панель и область, сколько она забирает от края, что остаётся двум панелям друг против друга |
Эталон — digitwm, файл ribbon.c, десять функций
ribbon_policy_*, коммит bfd5d85 (пришпилен нарочно: сверять против уехавшего
эталона нельзя). Перенос — не копирование текста: ни одной строки C из digitwm
в этом дереве нет, вся арифметика заново записана на flang.
Как подключается. Печатью, не пакетом: flang emit flang/geometry.flang --target c --out out-c/geometry, и так же в Go. Девять функций эталона —
прямая замена один в один; десятая, ширина колонки, в оригинале читает
глобальный Conf.ribbonwidth[preset] — здесь таблицу долей передают доводом, а
хозяину остаётся обёртка в одну функцию. Так это уже устроено и на практике
(проверено 3 сентября 2026): в дереве digitwm ribbon.c держит десять именно
таких тонких обёрток, а саму арифметику приносит ribbon-flang/ — пришпиленный
субмодуль flang-ribbon с уже собранным C (ribbon-flang/out-c/).
Чем проверяется. ./ярлык проверка — flang check и flang test.
Ведомость — вывод flang check --proof:
| Функций | Все тотальны | Утверждений | доказано | сеткой | Примеров | |
|---|---|---|---|---|---|---|
| Четыре модуля | 21 | 21 из 21 | 27 | 6 | 21 | 136 |
«Доказано» — про все входы; «сеткой» — постусловие пересчитано на входах примеров, а не выведено, и ведомость называет это прямо: «нарушений не искали, это не доказательство». У эталона нет пары для одной функции — «Пара вместе», сумма двух смежных панелей: доказательства у неё нет вовсе, только закон («вместе две панели не займут больше, чем есть в области»), пересчитанный в напечатанном коде на 4536 входах и ни разу не нарушенный.
Сверка с оригиналом — команда, а не архив. У flang-tui и flang-env сверщик
снят из дерева вместе с последним Go; здесь digitwm остаётся живым хозяином, и
tools/compare.sh остаётся в дереве. Он клонирует digitwm на пришпиленном
коммите и сверяет sha256 ribbon.c (уехавший эталон не сверяют), вырезает
десять функций тем же приёмом, которым это делает сам digitwm
(tools/no-x-build.sh), требует ровно один неопределённый символ в
объектнике — Conf, — и на 526 871 входе сравнивает три потока ответов
побайтно: эталон на C, напечатанное в C, напечатанное в Go. Результат прогона
2 сентября 2026: 0 расхождений, 526 871 строка, 14 458 160 байт, сверено
cmp.
Расхождение уже было — и вот чем оно оказалось. Первый прогон дал 606
расхождений из 526 871, все на ширине колонки. Виновата была не арифметика, а
постусловие: у flang, как у IEEE-754, есть +0 и −0, и (0 − 0) × −20 даёт
именно −0 — для которого непронормализованное постусловие «частное и остаток
восстанавливают делимое» отвечало отказом там, где эталон спокойно отвечал
нулём. Число по обе стороны было одно и то же, расходилась способность его
подтвердить. Лечит строка плюс 0, приём из flang/stdlib/numbers.flang;
разбор остался в шапке функции «Деление нацело», а сам случай — примером
внутри «Ширины колонки».
Негативный контроль ловит и собственную ловушку. ./ярлык наоборот ломает
копию дерева семью способами и требует красного каждый раз — включая проверку,
что flang check --proof "$m" | tail -8 без pipefail возвращает код tail,
а не проверки. Именно эта ловушка полгода держала «зелёную» ведомость
доказательств в двух соседних репозиториях.
Что осталось у хозяина. ribbon.c — 1542 строки, перенесено 297 (строки
78–374, десять функций с комментариями). Остальные 1245 строк — сорок функций
в двух группах: обход списков и память (TAILQ_*, malloc, free — 65 строк
на них одних) и связь с оконной системой (одиннадцать имён из wsi.h по
nm -u ribbon.o, а не по grep: ribbon_client_insert находится грепом и как
часть client_insert, что даёт двадцать вместо одиннадцати). Ни одной из этих
1245 строк в flang-ribbon нет: библиотека, которая знает про client_show,
перестаёт быть библиотекой и становится половиной оконного менеджера.
flang-env — окружение
Что держит. Два слоя, и граница между ними — не вкус, а свойство языка.
Слой 1 — чистые тотальные функции. Хозяин передаёт строки, библиотека решает:
сколько цвета терпит терминал (NO_COLOR, TERM, COLORTERM), годен ли он
под экран, на каком языке говорить, какими знаками писать числа. Печатается во
все девять целей и уезжает внутрь чужого двоичного файла.
Слой 2 — план, который читает окружение сам. Исполняет его flang io — тот
же двоичный компилятор, что собирается одним cc. JavaScript при исполнении
не участвует ни байтом.
Переменная — это сумма, а не строка: вариант «Задано» с значение равным …
против вариант «Не задано». Пустая строка и отсутствие — разные вещи, и тип
это знает. Заведено ровно из-за NO_COLOR, который действует самим своим
наличием.
Как подключается — и где отказывает. Слой 1 печатается куда угодно; слой 2 — нет, и об этом говорит сам компилятор:
$ flang emit flang/read-env.flang --target go --out /tmp/x
flang emit: печать отказала — FLANG_PLAN_UNSUPPORTED: цель «go» не умеет
печатать объявление «план».
Коды возврата: go, c, rust уходят 1; js, который план печатает, —
3 («напечатано, но проверено не всё»). Все четыре сторожит ./ярлык граница, и он стоит в конвейере. Отсюда два пути, и оба настоящие: кому нужно,
чтобы окружение читала сама библиотека, — зовёт flang io; кому нужно, чтобы
разбор жил внутри его двоичного файла, — берёт напечатанный модуль «Env» и
передаёт строки сам.
Чем проверяется. Ведомость слоя 1: 25 функций, все тотальны, 9 утверждений (4 доказаны, 5 — сеткой), 103 примера. Сверка с digitdisk — 402 входа, 0 расхождений — тоже кончилась и тоже заменена примерами: 41 из 103 примеров слоя 1 суть ответы эталона, посчитанные им самим на закреплённом коммите, а не набранные руками.
По плану ведомость не печатается, и двоичный объясняет почему: законов
плана он не считает, и пустой раздел читался бы как «законов не объявлено» —
неправда. Сам этот отказ тоже проверка: ./ярлык отказ требует и кода 2, и той
фразы, которой отказ объясняется.
Цена flang io названа числом, по 10–50 холодных прогонов на строку:
| Что | На прогон |
|---|---|
flang io flang/read-env.flang (шесть переменных, план целиком) |
1,08 с |
flang check той же программы |
1,17 с |
printenv TERM |
4,0 мс |
| Программа на Go, читающая те же шесть переменных | 24 мс |
Вывод числом, а не мнением: звать flang io из горячего цикла нельзя — это
в 270 раз дороже одного printenv. Почти вся цена не в чтении, а в проверке:
flang io перед исполнением судит программу тем же путём, что flang check, и
это видно по второй строке. Место flang io — старт, установка, CI, разовая
справка; место напечатанного слоя 1 — всё остальное.
Чего не сверяли — сказано тоже. Разбор языка и правила чисел сверять не с
чем: fmt в Go к языку нечувствителен. Таблица языков (36 языков с запятой в
дробной части, 20 из них с пробелом в разрядах) набрана по glibc и стоит в
ведомости как «объявлено, не доказано».
Общее у всех троих: make здесь нет
Точка входа — ./ярлык, 112 строк на sh, из них 56 кода — дословно один и
тот же файл во всех трёх деревьях. Списка команд в нём нет ни строкой: он
спрашивает строку у двоичного и запускает её. Сам список — ярлыки.flang,
программа: проверена типами, с примерами, а «Сколько ярлыков» несёт число
обещанием, а не прозой. Длина этого списка своя у каждой библиотеки — он
называет её собственные команды.
Одно, что Makefile нёс, выброшено нарочно — список имён модулей. Он мог
разойтись с деревом молча: седьмой модуль просто не проверялся бы и не
печатался, а всё оставалось бы зелёным. Вместо списка стоит flang/*.flang;
расходиться нечему.
Все три — под BSD-2-Clause, деревья написаны с нуля. Поведение сверено с оригиналом по его открытому исходнику: flang-tui и flang-env — с digitdisk, flang-ribbon — с digitwm. Ни строки чужого кода ни в одной из трёх нет.
См. также
- Описание языка — что за язык, два класса программ, печать в девять языков
- Справочник конструкций — карточка с примером на каждую форму
- Практикум — восемь ступеней, вердикт выносит исполнение
- digitdisk — программа, из которой выросли flang-tui и flang-env
- digitwm — программа, из которой перенесена арифметика flang-ribbon