Skip to content

Commit cc944fe

Browse files
Самоприменение: печать в C, написанная на flang — и найденный ею дефект эталона
Второй слой компилятора на самом языке: 328 функций, 190 тотальных. Побайтовое совпадение с эталонным src/emit/c.mjs на 66 программах и ноль расхождений — 37 файлов .flang репозитория (включая core/parser.flang на 2675 строк и собственный исходник), 17 моделей .fts через мост совместимости (ими покрыты постусловия, которых в .flang нет) и 12 синтетических программ на батут, самовызов в цикл, литералы всех видов, все виды образцов и десять отказов со сверкой кода и текста. Весь напечатанный C собирается cc -std=c99 -Wall -Wextra -Werror -pedantic без единого предупреждения, включая C, напечатанный для самого этого файла. Печать не переписана заново: экранирование, печать строк и массивов приезжают импортом из core/json.flang, запись числа — встроенная «к строке», то есть тот же Number::toString. ГЛАВНОЕ — дефект эталона, который эта работа нашла. Компонента взаимной хвостовой рекурсии, у которой ВСЕ хвостовые позиции отскоки, давала шаг батута с неиспользованным параметром result: гасились ctx, error, bounce и параметры, но не он. С -Wextra под -Werror это ошибка сборки, то есть эталон печатал некомпилируемый C — того же рода дефект, что чинили ролью в имени. На программах репозитория такой случай не возникает, поэтому дефект и дожил: его нашла печать, написанная на самом языке, когда напечатала собственный исходник. Исправлено в обеих реализациях одинаково — побайтовое совпадение обязано сохраняться, и чинить одну сторону было нельзя. Тест, закреплявший дефект как известный, заменён регрессией: сверка требует совпадения с эталоном, сборка — что результат компилируется. Нетотальных функций 138, четыре причины, все в «Долгах»: обход AST через поиск по ключу (результат поиска анализ частью значения не считает), обход строки по индексу (та же нехватка «символов строки», что в лексере и stdlib), Тарьян (убывает число непосещённых вершин, не структура) и следствия вызовов. 1203 теста flang, 0 падений.
1 parent 92a0337 commit cc944fe

4 files changed

Lines changed: 4033 additions & 0 deletions

File tree

flang/self/SPEC.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,81 @@ newer», потому что компилятор языка написан на
135135
же таблице, что и алфавиты: латиница и кириллица. Ключевых слов на других
136136
письменностях в языке нет, поэтому расхождением это станет только вместе с
137137
пунктом 2.
138+
139+
### `self/emit-c.flang`
140+
141+
Состояние: 328 функций, 190 тотальных, 138 обычных. Напечатанный C совпадает с
142+
эталоном (`src/emit/c.mjs`) **побайтово** на 37 программах `.flang` репозитория
143+
(`flang/stdlib`, `flang/examples`, `flang/core`, включая собственный исходник) и
144+
на 17 моделях `.fts` через `compat.mjs` — вместе с постусловиями, которых в
145+
`.flang` репозитория нет. Плюс 12 синтетических программ: батут, батут без
146+
параметров, одноимённые вариант и функция, самовызов в цикл, литералы всех
147+
видов, все виды образцов, настройки печати и десять отказов с кодом и текстом.
148+
Проверка: `flang/test/self-emit-c.test.mjs`. Весь напечатанный C собирается
149+
`cc -std=c99 -Wall -Wextra -Werror -pedantic` без единого предупреждения.
150+
151+
Печать чисел и экранирование строк не написаны заново: `«Заменить всё»`,
152+
`«Экранировать»`, `«Печать строки»` и `«Печать массива»` приезжают импортом из
153+
`flang/core/json.flang`, а запись числа — это встроенная `к строке`, то есть тот
154+
же `Number::toString`, что у эталона. AST приходит как значение JSON — сумма
155+
типов `«Значение»` оттуда же, а не свой типизированный AST: эталон тоже работает
156+
с голым объектом, различает «ключа нет» и «ключ со значением ничто» и принимает
157+
`bind` образца то записью, то списком.
158+
159+
**Нетотальные функции (138) — четыре причины.**
160+
161+
| Что | Почему не доказывается |
162+
|---|---|
163+
| печать выражений: «Печать значения», «Печать хвоста», «Печать разбора», «Печать ветви», «Цепочка случаев», «Печать свёртки», «Печать цикла», «Печать литерала», «Печать списка», «Печать полей», «Проверка образца», «Связывание образца» и их спутники (около 70 функций) | взаимная рекурсия по AST идёт не по прямому аргументу: тело `пусть` берётся как `«Взять поле» от узла`, а поле обобщённого значения JSON анализ (`src/totality.mjs`) частью значения не считает — он видит поле варианта и хвост списка, а не результат поиска по ключу. Типизированный AST это снял бы, но ценой перевода AST в тесте, то есть проверки теста самим собой |
164+
| обход строки по индексу: «Слова с индекса», «Слова дальше», «Слова», «Только символы слова», «Байтов в строке с», «Байтов в строке», «Пробелы» | убывает разность «длина минус индекс», то есть число. Та же нехватка «символов строки», что названа в долгах `self/lexer.flang` и `stdlib/strings.flang` |
165+
| разбор графа вызовов: «Обойти вершину», «Ребро Тарьяна», «Ребро вглубь», «Ребро внутри», «Корень Тарьяна», «Закрыть вершину», «Снять компоненту», «Снять вершину стека», «Компоненты связности» | Тарьян убывает по «числу непосещённых вершин» и по стеку, который растёт и укорачивается; ни то, ни другое не структурное убывание. Глубина рекурсии ограничена числом функций программы — тем же, чем ограничен эталон |
166+
| следствие: «Змейка», «Свежее имя», «Массив имён», «Текстовый литерал», «Собрать общее», «Печать функции», «Печать тел», «Печать программы» и прочие, вызывающие перечисленное выше | вызывают обычные функции |
167+
168+
**Расхождения с эталоном.** Ни одно не видно на входах репозитория; каждое
169+
проверено прямым сравнением с `src/emit/c.mjs`.
170+
171+
1. **Длина строки в байтах считается по таблице диапазонов.** Рантайм хранит
172+
длину литерала и в кодовых точках, и в байтах UTF-8; `Buffer.byteLength`
173+
эталону даёт её сразу, а в языке кода символа нет. Поэтому однобайтовые (весь
174+
ASCII) и двухбайтовые (U+0080…U+07FF, кроме кодовых точек, неустойчивых к NFC
175+
поодиночке — они в литерал попасть не могут, лексер нормализует) выписаны
176+
перечислением, а остальное считается трёхбайтовым. Расхождение: символ вне
177+
BMP эталон считает за четыре байта, эта печать — за три. Эмодзи в строковом
178+
литерале даст неверное поле `bytes`; закрывается встроенной формой «код
179+
символа», а не расширением таблицы.
180+
2. **Транслитерация не нормализует к NFC.** `naming.mjs` начинает с
181+
`String.normalize("NFC")`, встроенной формы для этого нет. Расхождение: имя,
182+
записанное разложенной формой (`и` + U+0306 вместо `й`), эталон
183+
транслитерирует как `y`, эта печать — как `i` плюс пропущенный знак.
184+
3. **Слово в `words()` — это `[A-Za-z0-9]`, а не `\p{L}\p{N}`.** После
185+
транслитерации кириллица уже латинская, а спросить категорию Unicode нечем.
186+
Расхождение: имя с греческой или армянской буквой эталон оставит в
187+
идентификаторе, эта печать примет её за разделитель слов.
188+
4. **Проверки «поле должно быть списком» не воспроизводятся.** Эталон бросает
189+
`FLANG_PARSE` на `args`, `params`, `postconditions` и `cases`, которые не
190+
массивы; здесь обобщённое значение отдаёт в таких местах пустой список.
191+
Расхождение видно только на AST, которого парсер не порождает.
192+
193+
**Дефект эталона, найденный этой сверкой — исправлен.** Компонента взаимной
194+
хвостовой рекурсии, у которой ВСЕ хвостовые позиции — отскоки, давала шаг
195+
батута с неиспользованным параметром `result`: эталон гасил `ctx`, `error`,
196+
`bounce` и параметры, но не его. С `-Wextra` под `-Werror` это ошибка сборки,
197+
то есть `src/emit/c.mjs` печатал некомпилируемый C — в точности того же рода
198+
дефект, что уже чинили ролью в имени.
199+
200+
На программах репозитория такой случай не возникает, поэтому дефект и дожил до
201+
печати, написанной на самом языке: нашла его она, напечатав собственный
202+
исходник. Исправлено одинаково в обеих реализациях — побайтовое совпадение
203+
обязано сохраняться, поэтому чинить одну сторону было нельзя. Тест
204+
«шаг батута без единого значения в хвосте гасит result и собирается» держит
205+
их вместе: сверка требует совпадения с эталоном, сборка — что результат
206+
компилируется.
207+
208+
По этой же причине «Слова дальше» и «Обход хвоста ветвей» возвращают итог через
209+
`пусть`, а не прямо из ветки: иначе не собирался бы сам `self/emit-c.flang`.
210+
211+
**Лимит шагов.** Печать самой большой программы репозитория (собственный
212+
исходник, 3300 строк, 100 КБ C) укладывается между 60 и 100 миллионами шагов
213+
интерпретатора; тест поднимает лимит до ста миллионов. Это не формальность:
214+
лимит ловит превращение печати в перебор. Компилятору такое разрешено
215+
(см. начало этого файла), факт-чекеру — нет.

0 commit comments

Comments
 (0)