You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Самоприменение: печать в 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 падений.
Copy file name to clipboardExpand all lines: flang/self/SPEC.md
+78Lines changed: 78 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -135,3 +135,81 @@ newer», потому что компилятор языка написан на
135
135
же таблице, что и алфавиты: латиница и кириллица. Ключевых слов на других
136
136
письменностях в языке нет, поэтому расхождением это станет только вместе с
137
137
пунктом 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
+
лимит ловит превращение печати в перебор. Компилятору такое разрешено
0 commit comments