# Набор тестов соответствия Ktav

**Languages:** [English](README.md) · **Русский** · [简体中文](README.zh.md)

Языконезависимый набор тестов для парсеров Ktav. Каждая
соответствующая реализация должна прогонять этот набор через свой
парсер и проходить каждый тест.

## Структура

```
tests/
├── valid/
│   ├── <category>/
│   │   ├── <name>.ktav        Input document.
│   │   └── <name>.json        Expected Value, serialized as JSON.
└── invalid/
    ├── <name>.ktav            Input document.
    └── <name>.json            { "error": "<category>" } — expected failure.
```

## JSON-оракул

`Value` в Ktav отображается в JSON напрямую — без объектов-обёрток:

| Ktav `Value`            | JSON                                   |
|-------------------------|----------------------------------------|
| `Null`                  | `null`                                 |
| `Bool(true)`            | `true`                                 |
| `Bool(false)`           | `false`                                |
| `String(s)`             | `"s"`                                  |
| `Integer(s)`            | *JSON-число без десятичной точки* — например `8080`, `-100`, `0`, `99999999999999999999` |
| `Float(s)`              | *JSON-число с десятичной точкой* — например `0.5`, `-1.5`, `0.0`, `1.5e-10`, `2.5E+8` |
| `Array([v1, v2, …])`    | `[ v1, v2, … ]`                        |
| `Object([(k1, v1), …])` | `{ "k1": v1, … }`                      |

**Обычные скаляры (значения `:`-пар) остаются строками на уровне
Value** в Ktav — типизация делегирована потребителю (например,
serde на стороне Rust). Ожидаемый вывод теста `"port": "8080"`
корректен для `port: 8080` (обычная пара), а `"port": 8080` — нет.

**Integer и Float различаются по наличию десятичной точки.** Оракул
записывает `Integer(s)` как `s` без точки, а `Float(s)` — как `s`
с точкой. Грамматика требует точку в body Float, так что
неоднозначности нет: `Float("0")` не существует (это
`InvalidTypedScalar`), а Integer всегда без точки.

**Точность.** Для чисел, превышающих диапазон i64/f64 (например, в
`integer_large` 20-значное целое), реализация, которая разбирает
оракульный `.json`, должна использовать парсер чисел
произвольной точности — либо сравнивать через текстовую форму,
сохраняемую Integer/Float на уровне Value. Реализации без такой
возможности могут сверяться round-trip-равно: прогнать оракул
через собственный Ktav→JSON-путь и сравнить JSON-деревья.

### Пример

```text
# foo.ktav
port:i 8080
ratio:f 0.5
version: 1.2
```

```json
{
  "port": 8080,
  "ratio": 0.5,
  "version": "1.2"
}
```

## Как реализации его используют

1. Распарсьте `<name>.ktav` своей реализацией, получив
   in-memory Value.
2. Сериализуйте этот Value в JSON канонической сериализацией своей
   реализации (**сохраняя порядок вставки полей объекта**).
3. Сравните побайтово — или хотя бы структурно с семантикой
   упорядоченных объектов — с `<name>.json`.

Для `invalid/*` шаг 1 должен вернуть ошибку, а категория этой
ошибки должна совпадать с объявленной в `<name>.json` под
ключом `"error"`.

## Категории ошибок

Соответствуют разделу 6 спецификации:

- `UnbalancedBracket` — закрывающая `}`/`]` без парной открывающей
  или EOF при открытом составном значении либо многострочной строке.
- `MismatchedBracket` — закрывающая `}` для открытой `[` или
  наоборот.
- `DuplicateName` — дублирующийся ключ внутри объекта.
- `PathConflict` — раскрытие точечного ключа конфликтует с
  существующим скаляром.
- `InvalidKey` — ключ содержит запрещённые символы.
- `EmptyKey` — пустая строка перед первым `:` в строке-паре.
- `OrphanLine` — строка без двоеточия и не являющаяся закрывающей
  внутри объекта.
- `InlineNonEmptyCompound` — inline `{...}` / `[...]` с содержимым.
- `InvalidTypedScalar` — body типового маркера `:i` / `:f` не
  совпадает с требуемой грамматикой integer / float.
- `MissingSeparatorSpace` — разделитель пары (`:`, `::`, `:i`, `:f`)
  или маркер элемента массива (`::`, `:i`, `:f`) склеен с body без
  пробела и без EOL между ними (§ 6.10).

## Запуск против Rust-реализации

См. [`ktav-lang/rust`](https://github.com/ktav-lang/rust) — её
набор тестов подключает эту директорию как git submodule (или
копию) и прогоняет каждую пару.

## Добавление нового теста

Предпочтительный поток:

1. Напишите `.ktav`-файл с чётко сфокусированным сценарием — один
   концепт на тест.
2. Прогоните его через любую соответствующую реализацию, снимите
   канонический JSON-вывод, сохраните как `<name>.json` (или, для
   невалидных входов, снимите категорию ошибки и запишите
   `{"error":"<cat>"}`).
3. Откройте PR. CI каждой реализации проверит, что она согласуется
   с оракулом.

Называйте тесты описательно — `empty_list.ktav`, а не `t1.ktav`.
