Skip to content

Commit af52ac4

Browse files
flang: связывание модулей — «использует» начинает действовать
Конструкция `использует «Модуль» из "путь"` разбиралась, но не работала: парсер складывал импорт в legacy, а имена из другого файла оставались несвязанными и падали с FLANG_UNKNOWN_NAME на первом же вызове. Из-за этого stdlib нельзя было применить в решении, а ядро FTS, разложенное по слоям (лексер, парсер, вычислитель, печать JSON), не собиралось в одну программу — то есть переписывание ядра на flang было заблокировано. Импорт сделан слиянием объявлений, а не пространством имён. Пространства имён потребовали бы решить, как выглядит квалифицированное имя по-русски, и тащить это решение во все восемь бэкендов; слияние печатается в любой целевой язык одинаково. Цена — конфликт имён стал ошибкой FLANG_DUPLICATE_NAME с указанием обоих файлов, а не молчаливым перекрытием. Связывание двухпроходное, и это не оптимизация, а необходимость: имя функции без аргументов становится вызовом только если такая функция объявлена, а на первом проходе каждый файл знает лишь свои объявления. Поэтому parse() принял третий аргумент — имена, пришедшие из импортов, — и файлы с импортами разбираются заново уже с полным набором. Без этого транзитивный импорт (В → Б → А) оставлял вызов несвязанным. Отказы громкие: пропавший файл — FLANG_IMPORT_NOT_FOUND, цикл — FLANG_IMPORT_CYCLE с цепочкой, несовпадение имени модуля с заголовком файла — FLANG_IMPORT_NAME. `экспортирует` ограничивает видимое; у входного файла видно всё. Файл без импортов разбирается ровно как прежде — это проверяется побайтовым сравнением AST. Попутно найдено и записано в SPEC ограничение, не связанное со связыванием: вариант, названный ключевым словом (`Да`, `Плюс`, `Больше`), в образце не разбирается, потому что лексер решает раньше, чем известен тип. Диагностика при этом не называет причину — записано долгом. Проверено: 723 теста flang, 0 падений (было 712).
1 parent cd5d3d7 commit af52ac4

7 files changed

Lines changed: 748 additions & 3 deletions

File tree

flang/SPEC.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,24 @@ const verdict = checkFacts(program, { facts: {...}, claims: ["…"] })
286286
свойства как постусловия, примеры как `examples`). Тест обязан прогнать **все**
287287
модели репозитория через оба движка и потребовать совпадения результатов,
288288
включая коды ошибок при нарушении свойств.
289+
290+
## 10. Известные ограничения
291+
292+
### Имя варианта не должно совпадать с ключевым словом
293+
294+
Лексер решает, что перед ним, до того как становится известен тип, поэтому
295+
вариант, названный ключевым словом языка, в образце не разбирается:
296+
297+
| Имя варианта | Во что превращается | Что видно пользователю |
298+
|---|---|---|
299+
| `Да`, `Нет` | литерал признака | `FLANG_TYPE`: образец-литерал вместо варианта |
300+
| `Плюс`, `Минус` | арифметический оператор | `FLANG_PARSE`: ожидался образец |
301+
| `Больше`, `Меньше` | сравнение | `FLANG_PARSE`: ожидался образец |
302+
303+
Обходится переименованием варианта либо явной формой `случай вариант «Имя»`,
304+
которой пользуется stdlib. Диагностика при этом не называет настоящую причину —
305+
это долг: сообщение обязано говорить «имя варианта совпадает с ключевым словом»,
306+
а не «ожидался образец».
307+
308+
Ограничение обнаружено при связывании модулей и не связано с ним: тот же код в
309+
одном файле ведёт себя так же.

flang/bin/flang.mjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,27 @@ async function parseFlang(source, file) {
192192
)
193193
}
194194
if (typeof parse !== "function") throw fail("FLANG_PARSE", "в parser.mjs нет функции разбора")
195+
196+
/* Со стандартного ввода нет каталога, относительно которого разрешается
197+
`использует … из "…"`, поэтому связывание для него не запускаем: честный
198+
FLANG_UNKNOWN_NAME лучше, чем чтение файла из случайного каталога. */
199+
if (file !== "-") {
200+
const { linkProgram, importsOf } = await import(new URL("../src/link.mjs", import.meta.url).href)
201+
/* Разбираем сначала сами: у файла без импортов результат обязан остаться
202+
побайтово прежним, иначе `ast` начнёт отличаться там, где ничего не
203+
менялось. */
204+
const single = parse(source, file)
205+
if (importsOf(single).length === 0) return single
206+
const linked = await linkProgram(file, source, parse)
207+
if (linked.diagnostics.length > 0) {
208+
const error = new Error(linked.diagnostics[0].message)
209+
error.diagnostics = linked.diagnostics
210+
throw error
211+
}
212+
const { diagnostics: _ignored, ...program } = linked
213+
return program
214+
}
215+
195216
/* Ошибку самого разбора не заворачиваем: у неё уже есть код, сообщение и
196217
span — подменять их своим текстом значит потерять место ошибки. */
197218
return parse(source, file)

flang/core/SPEC.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Ядро FTS на flang — контракт
2+
3+
Цель: переписать ядро FTS (сейчас `src/*.ts`, 3155 строк) на flang, чтобы
4+
печатать его в C и получить нативный `fts` без Node — и дальше в остальные
5+
целевые языки.
6+
7+
## Почему это стало возможно только сейчас
8+
9+
В `tools/ftsc/self/meta.fts` записано ограничение, из-за которого ядро нельзя
10+
было написать на самом FTS:
11+
12+
> в ядре FTS строка является типом поля, но не значением, над которым можно
13+
> вычислять
14+
15+
Парсер — это вычисления над строками. flang снимает ровно это ограничение:
16+
строки в нём данные, а `flang/stdlib/strings.flang` уже даёт `Разбить по
17+
символу`, `Позиция подстроки`, `Начинается с`, `Считать символ`.
18+
19+
## Разбиение и границы
20+
21+
Слои пишутся независимо и стыкуются только через типы этого файла. Никакой
22+
слой не разбирает текст повторно и не заглядывает внутрь чужого слоя.
23+
24+
```
25+
текст .fts ──[лексер]──> список «Токен» ──[парсер]──> «Документ»
26+
27+
┌─────────────────┴───────────────┐
28+
[вычислитель] [печать JSON]
29+
значение / диагностика строка, байт в байт
30+
как у ядра на TS
31+
```
32+
33+
## 1. Токены — контракт лексера
34+
35+
```flang
36+
тип «Токен»
37+
вариант ОтступБольше
38+
вариант ОтступМеньше
39+
вариант КонецСтроки
40+
вариант Имя содержит текст: строка
41+
вариант Число содержит значение: число
42+
вариант Конец
43+
```
44+
45+
Правила:
46+
47+
- `«имя в кавычках»` и `имя без кавычек` дают один и тот же вариант `Имя`;
48+
кавычки-ёлочки снимаются, внутренние пробелы сохраняются. Различать их
49+
дальше не нужно: ядро на TS этого различия тоже не хранит.
50+
- Комментарий `//` до конца строки выбрасывается лексером.
51+
- Пустая строка не порождает `КонецСтроки`.
52+
- Отступ — только пробелы, шаг фиксируется первым отступом файла; рваный
53+
отступ — диагностика `FTS_INDENT`.
54+
- Число — целое или с точкой, минус допустим. Разбор обязан совпадать с
55+
`Number(...)` ядра.
56+
57+
## 2. Документ — контракт парсера
58+
59+
Дословно повторяет `FtsDocument` из `src/model.ts`. Суммы типов вместо
60+
размеченных объединений TypeScript:
61+
62+
```flang
63+
тип «Операнд»
64+
вариант Значение содержит значение: строка // скаляр в текстовом виде
65+
вариант Поле содержит имя: строка
66+
вариант Процент содержит процент: число, поле: строка
67+
вариант Результат
68+
69+
тип «Сравнение»
70+
вариант Равно
71+
вариант НеРавно
72+
вариант НеМеньше
73+
вариант НеБольше
74+
вариант Больше
75+
вариант Меньше
76+
77+
объект «Условие»
78+
поле: строка
79+
сравнение: «Сравнение»
80+
операнд: «Операнд»
81+
82+
объект «Правило»
83+
имя: строка
84+
условия: список «Условие»
85+
действие: «Действие» // Присвоить | Добавить, оба с операндом
86+
87+
объект «Свойство»
88+
имя: строка
89+
сравнение: «Сравнение»
90+
операнд: «Операнд»
91+
92+
объект «Пример»
93+
имя: строка
94+
вход: список «Привязка» // имя поля → скаляр
95+
ожидается: строка
96+
97+
объект «Утилита»
98+
имя: строка
99+
принимает: строка
100+
возвращает: строка
101+
начальное: строка
102+
правила: список «Правило»
103+
свойства: список «Свойство»
104+
примеры: список «Пример»
105+
```
106+
107+
Соответствие поверхности: `не меньше``НеМеньше`, `равен``Равно`,
108+
`да`/`нет` → скаляр признака, `N процентов от поля X``Процент`.
109+
110+
## 3. Семантика вычислителя
111+
112+
Обязана совпадать с `src/utility.ts` и с таблицей в `flang/SPEC.md`:
113+
114+
| Вопрос | Решение |
115+
|---|---|
116+
| проценты | `(процент / 100) * значение` — именно в этом порядке |
117+
| равенство скаляров | `Object.is` |
118+
| порядок правил | сверху вниз, `Добавить` накапливает, `Присвоить` затирает |
119+
| свойство | постусловие на **всей** области входа, проверяется после всех правил |
120+
| нарушено свойство | код `FTS_UTILITY_PROPERTY`, текст ровно как у ядра |
121+
| пример не сошёлся | код `FTS_EXAMPLE_MISMATCH` |
122+
123+
Свойство проверяется и тогда, когда ни одно правило не сработало — именно
124+
из-за этого процентный предел на отрицательной сумме падал.
125+
126+
## 4. Тотальность
127+
128+
Всё ядро обязано лежать в тотальном классе: `fts check` не имеет права
129+
зависнуть. Циклы «пока есть символы» выражаются свёрткой по списку символов —
130+
он структурно убывает. Где тотальность не доказывается, признак `тотальная`
131+
не ставится, а место записывается в этот файл как долг с объяснением.
132+
133+
## 5. Критерий готовности — дифференциальная сверка
134+
135+
Ядро считается верным не тогда, когда проходят его собственные тесты, а когда
136+
на **всех** `.fts` репозитория старое ядро на TS и новое на flang дают
137+
побайтово совпадающий JSON — включая коды и тексты диагностик. Приём уже
138+
отработан в `flang/src/compat.mjs`: 19 593 входа, ноль расхождений.

flang/examples/import-check.flang

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
модуль «Проба импорта»
2+
использует «Списки» из "../stdlib/lists.flang"
3+
4+
тотальная функция «Сумма пробы»
5+
принимает элементы: список числа
6+
возвращает число
7+
«Сумма» от элементы
8+
пример «Четыре числа»
9+
дано элементы равно [1, 2, 3, 40]
10+
ожидается результат равен 46

0 commit comments

Comments
 (0)