# Ktav (כְּתָב)
[](https://github.com/ktav-lang/spec/tags)

[](https://ktav-lang.github.io/)
**Languages:** [English](README.md) · **Русский** · [简体中文](README.zh.md)
**Песочница:** конвертация JSON / YAML / TOML / INI ⇄ Ktav прямо в браузере — **[ktav-lang.github.io](https://ktav-lang.github.io/)**.
> Простой формат конфигурации. Форма JSON — скаляры, массивы, объекты,
> `null`, `true`, `false` — без пунктуации JSON. Никаких кавычек
> вокруг строк, никаких запятых, никакой таблицы escape-последовательностей.
> Точечные ключи для вложенности, видимые явные маркеры для литеральных
> и многострочных строк.
Этот репозиторий — **каноническая спецификация** формата Ktav.
Реализации на любом языке программирования должны соответствовать
той версии, на которую они ориентированы.
## На вкус
Один пример, задействующий все основные формы формата — пары `:`
(голое число типизируется по форме, всё остальное — String),
ключевое слово Bool, `::` (форсированная литеральная String),
точечные ключи, вложенные составные значения, многострочная строка.
```text
## A config for a SOCKS5 rotator.
port: 20082
log_level: info
debug: true
banned_patterns: [
.*\.onion:\d+
.*\.local
]
upstreams: [
{
host: a.example
port: 1080
weight: 0.7
timeouts: {
read: 30
write: 10
}
}
{
host: b.example
port: 1080
weight: 0.3
}
]
## Dotted keys — flat alternative to nesting.
node.host: a.example
node.port: 1080
## `::` делает строку литеральной — двоеточие внутри пароля сохраняется.
node.auth:: p@ss:word
motd: (
Welcome to the node.
Please behave.
)
```
Разбирается в следующее значение (показано в виде JSON5 — с
комментариями и без кавычек в ключах для читаемости). Обратите
внимание на соответствие значений:
- `:` с голым целым телом (`20082`) — Integer; с голым десятичным
(`0.7`) — Float; любое другое тело (`info`, regex, путь) — String,
дословно, даже для цифроподобного содержимого.
- `: true` / `: false` / `: null` — ключевые Bool / Null.
- `::` — форсированная литеральная String, классификация не применяется.
```json5
{
port: 20082,
log_level: "info",
debug: true,
banned_patterns: [
".*\\.onion:\\d+",
".*\\.local",
],
upstreams: [
{
host: "a.example",
port: 1080,
weight: 0.7,
timeouts: { read: 30, write: 10 },
},
{
host: "b.example",
port: 1080,
weight: 0.3,
},
],
node: {
host: "a.example",
port: 1080,
auth: "p@ss:word",
},
motd: "Welcome to the node.\nPlease behave.",
}
```
### Числа типизируются по лексической форме
Формат определяет тип скаляра по *форме* тела: голое целое
становится Integer, голое десятичное — Float, всё остальное остаётся
String. Маркер не нужен. Ничто, что лишь *похоже* на число, но не
является голым числом (версия, метка), не приводится — а `::`
заставляет настоящее голое число остаться литеральной строкой, когда
это нужно.
```text
retries: 3
version: 1.2
build:: 0007
label: v1.2
```
```json5
{
retries: 3, // голое целое — Integer
version: 1.2, // голое десятичное — Float
build: "0007", // `::` — форсированная литеральная String
label: "v1.2", // не голое число — String
}
```
### С `::` — ключевые слова и скобки как обычные строки
Body, который иначе был бы классифицирован как ключевое слово
(`null`, `true`, `false`), пустое составное (`{}`, `[]`) или начало
составного (`{`, `[`), требует сырого маркера `::`, чтобы стать
обычной String.
```text
## Без `::` было бы Bool true — здесь это строка "true".
on_release:: true
## Начинается с `[` — `::` предотвращает "открытие массива".
regex:: [a-z]+
## IPv6-адрес — та же причина.
ipv6:: [::1]:8080
## Ключевое слово `null` как литеральная четырёхсимвольная строка.
placeholder:: null
```
```json5
{
on_release: "true",
regex: "[a-z]+",
ipv6: "[::1]:8080",
placeholder: "null",
}
```
## Девиз
> **Будь другом конфига, а не его экзаменатором. Конфиг неидеален —
> но он лучший из возможных.**
Каждое правило локально. Каждая строка либо стоит сама по себе, либо
зависит только от явных, видимых скобок выше. Никаких ловушек с
отступами, никаких забытых кавычек, никакой арифметики замыкающих
запятых.
## Зачем Ktav
| Строки без обязательных кавычек | ✗ | ~ | ✗ | ✓ |
| Списки без разделителей (никаких запятых) | ✗ | ✓ | ✓ | ✓ |
| Нечувствительность к пробелам / отступам | ✓ | ✗ | ✓ | ✓ |
| Удобные для человека многострочные строки | ✗ | ~ | ~ | ✓ |
| Встроенные комментарии | ✗ | ✓ | ✓ | ✓ |
| Точечные ключи для плоских правок | ✗ | ✗ | ✓ | ✓ |
| Один парсер, небольшая спецификация | ✓ | ✗ | ~ | ✓ |
> ✓ = свойство есть · ✗ = нет · ~ = частично
Ktav сохраняет форму JSON (вы всегда знаете, что означает документ),
но отбрасывает синтаксис, делающий JSON враждебным для ручного
написания. Сохраняет точечные ключи TOML (удобно для плоских правок
и CLI-переопределений), но отказывается от двухмерного деления TOML
на таблицы и inline.
## Правила на одном экране
Документ Ktav — это неявный объект верхнего уровня. Внутри любого
объекта — пары; внутри любого массива — элементы.
```text
## comment — any line starting with '##'
key: value — scalar pair; bare number → Integer/Float,
any other body → String
key:: value — scalar pair; value is ALWAYS a literal string
key: { ... } — multi-line object; `}` closes on its own line
key: [ ... ] — multi-line array; `]` closes on its own line
key: {} / key: [] — empty compound, inline
key: ( ... ) — multi-line string; common indent stripped
key: (( ... )) — multi-line string; verbatim (no stripping)
value — inside an array: bare item (typed by form)
:: value — inside an array: literal-string item
```
Это весь язык. Никаких запятых, никаких кавычек, никакой таблицы
escape-последовательностей — единственный «escape» это маркер `::`,
и он живёт в разделителе (для пар) или в префиксе строки (для
элементов массива).
### Точечные ключи
Ключами могут быть точечные пути. Эти два документа *идентичны*:
```text
server.host: 127.0.0.1
server.port: 8080
```
```text
server: {
host: 127.0.0.1
port: 8080
}
```
Точечные ключи свободно сочетаются с вложенной формой. Парсер строит
одно и то же дерево в обоих случаях. Полезно для:
- CLI/env-переопределений (`--set server.port=9090`).
- Частичных правок в небольших конфигах без реструктуризации.
- Файлов с плоской основой, обрастающих вложенными секциями по мере
необходимости.
### Строки напрямую
Значение по умолчанию — строка. То, что идёт после `:` (через один
пробел отступа), и есть строка, посимвольно, до конца строки.
Отсутствие кавычек означает отсутствие правил кавычек — пути, URL,
регулярки, токены с пунктуацией просто работают.
```text
pattern: .*\.onion:\d+
url: https://example.com:8080/path?x=1
key: s3cret/with:colons and-dashes
```
Когда строка конфликтовала бы с грамматикой (начинается с `{`, `[`,
`(` или равна ключевому слову вроде `true`), префиксуйте разделитель
через `::`:
```text
literal_bracket:: [
keyword_as_string:: true
```
### Числа, типизированные по форме
Голое число типизируется сразу: `port: 8080` даёт Integer,
`ratio: 0.5` — Float. Решает форма тела: только цифры → Integer;
цифры с десятичной точкой или экспонентой → Float; всё остальное →
String.
```text
port: 8080
ratio: 0.5
offset: -100
eps: 1.5e-10
```
Значения сохраняются в текстовой форме на уровне Value
— `Integer("8080")`, `Float("0.5")` — так что 40-значные целые
переживают round-trip, а `1.2` никогда не окажется случайно
расширенным или усечённым. Потребители на типизированных языках
(Rust + serde, Go) сужают до нужного нативного типа; чтобы оставить
число-подобное значение текстом, форсируйте его через `::`
(`zip:: 01007`).
### Многострочные строки
Две формы, разные цели:
```text
stripped: (
line 1
line 2
relative indent preserved
)
verbatim: ((
line 1
exact leading whitespace preserved
line 3
))
```
`(` срезает общий ведущий отступ — пишите код/текст, который *читается*
в файле хорошо, а значение получается чистым. `((` сохраняет каждый
байт, так что документ round-trip'ится байт-в-байт.
### Ключевые слова
Только в нижнем регистре: `null`, `true`, `false`. Всё остальное —
`Null`, `TRUE`, `yes`, `on` — обычная строка. Никакого волшебного
приведения типов, никакого зависящего от версии списка ловушек.
```text
port: 8080
active: true
timeout: null
```
```json5
{
port: 8080, // голое целое → Integer
active: true, // ключевое слово → нативный JSON bool
timeout: null, // ключевое слово → нативный JSON null
}
```
## Полная спецификация
- **Текущая стабильная:** [Ktav 0.6.0](versions/0.6/spec.ru.md) — выпущена 2026-06-01.
- **Машиночитаемый индекс** всех версий: [`versions.ktav`](versions.ktav).
- **История версий:** [`CHANGELOG.md`](CHANGELOG.md).
## Набор тестов соответствия
Каждая версия поставляется с языконезависимым набором тестов в
[`versions/<v>/tests/`](versions/0.6/tests/). Пары
`<name>.ktav` + `<name>.json` — `.json` это ожидаемый `Value`,
отображённый 1:1 (`Null`→`null`, `Bool`→`bool`,
`String`→`string`, `Array`→`array`, `Object`→`object`). Числа
типизируются по лексической форме: целочисленное тело (`8080`) даёт
JSON-целое, десятичное тело (`0.5`) — JSON-float, а любой другой
скаляр остаётся строкой; `::` форсит литеральную строку, когда
число-подобное значение должно остаться текстом.
Порядок полей объекта значим.
Реализация соответствует версии, если проходит каждый тест из
набора этой версии. Подключайте директорию как git submodule (или
копию).
## Схема версионирования
Версии спецификации используют `MAJOR.MINOR.PATCH`:
| `x.y → x.y.(z+1)` | Редакторский — исправления опечаток, уточнения; соответствующие реализации не затрагиваются. |
| `x.y → x.(y+1)` | Обратно совместимое расширение (новое ключевое слово, новая примитивная форма). |
| `x.y → (x+1).0` | Ломающее изменение грамматики или семантики. |
Внутри любого стабильного `MAJOR` реализация, ориентированная на
`x.0`, MUST разбирать каждый документ, валидный при любом более
позднем `x.y.z`, идентично — в пределах подмножества, которое она
поддерживает.
Директория каждой версии полностью самодостаточна: `spec.md`, набор
соответствия `tests/` и добавления по версиям. Реализации
привязываются к директории версии по пути.
## Структура
```
.
├── README.md this file
├── versions.ktav machine-readable index of versions
├── CHANGELOG.md summary across versions
├── CONTRIBUTING.md how to propose changes
├── LICENSE-MIT MIT License
├── LICENSE-APACHE Apache License 2.0
└── versions/
└── <version>/
├── spec.md the specification document
└── tests/ language-agnostic conformance suite
├── README.md
├── valid/
└── invalid/
```
## Реализации
| Rust (эталонная) | [`ktav-lang/rust`](https://github.com/ktav-lang/rust) | `cargo add ktav` |
| C# / .NET | [`ktav-lang/csharp`](https://github.com/ktav-lang/csharp) | `dotnet add package Ktav` |
| Go | [`ktav-lang/golang`](https://github.com/ktav-lang/golang) | `go get github.com/ktav-lang/golang` |
| Java / JVM | [`ktav-lang/java`](https://github.com/ktav-lang/java) | `io.github.ktav-lang:ktav` на Maven Central |
| JS / TS | [`ktav-lang/js`](https://github.com/ktav-lang/js) | `npm install @ktav-lang/ktav` |
| PHP | [`ktav-lang/php`](https://github.com/ktav-lang/php) | `composer require ktav-lang/ktav` |
| Python | [`ktav-lang/python`](https://github.com/ktav-lang/python) | `pip install ktav` |
Rust crate — эталонный парсер; каждый из остальных биндингов поставляет
сборку `ktav_cabi` (C-ABI обёртка) и предоставляет тот же Ktav
интерфейс — language-agnostic набор `tests/` ниже прогоняется на всех
из них при каждом релизе.
Строите новую реализацию? Начните со `spec.md` целевой версии
([`spec.ru.md`](versions/0.6/spec.ru.md), раздел 8 — Compliance)
и прогоните набор [`tests/`](versions/0.6/tests/) через свой парсер.
## Вклад
Редакторские правки в существующую версию — PR напрямую. Всё
остальное — сначала открывайте issue. См.
[`CONTRIBUTING.ru.md`](CONTRIBUTING.ru.md).
## Поддержите проект
У автора много идей, которые могут быть полезны IT во всём мире, — и
далеко не только для Ktav. Их реализация требует финансирования. Если
вы хотите помочь — пишите на **phpcraftdream@gmail.com**.
## Лицензия
Двойная лицензия **MIT OR Apache-2.0** на ваш выбор. См.
[LICENSE-MIT](LICENSE-MIT) и [LICENSE-APACHE](LICENSE-APACHE).