Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ For the format specification's own history, see the

### Added

- Rust-only source-editing API: `ktav::edit::edit_value` accepts an escaped dotted path, and `edit_value_segments` accepts a slice/vector of literal segments. Both replace an existing value without reformatting the document, preserve surrounding bytes and comments, and return a new `String`.
- Lossless source edit operations `ktav::edit::{insert_value, remove_value, rename_key}` (and their `*_segments` variants): append a new Object key with an explicit `AlreadyExists` collision error and no hidden overwrites, delete a leaf/subtree/array element while keeping empty ancestors and container forms, and rename an Object member in place preserving order, value and comments. Same path grammar, inline readability thresholds, comment-loss refusals and post-edit semantic verification; invalid sources fail closed with `Unsupported`. Exposed to hosts as six additive, backward-compatible ABI 2 exports — `ktav_insert_value`, `ktav_insert_value_segments`, `ktav_remove_value` (two input buffers, no replacement), `ktav_remove_value_segments`, `ktav_rename_key` and `ktav_rename_key_segments` (UTF-8 literal decoded key); new bindings must require these symbols, and no ABI version is bumped.
- Source-editing API: `ktav::edit::edit_value` accepts an escaped dotted path, and `edit_value_segments` accepts a slice/vector of literal segments. Both replace an existing value without reformatting the document, preserve surrounding bytes and comments, and return a new `String`.
- Context-sensitive object keys and array indexes, `\.`/`\\` path escapes, minimal raw-marker transitions, unchanged-value no-ops and post-edit semantic verification. Typed `EditError` rejects malformed paths, missing targets and unsupported lossless replacements, including whole noncontiguous objects and structural changes that would discard comments.
- Exact-text regression tests and EN/RU/ZH usage examples. No syntax, existing parser/formatter API or package-version change; this feature is not part of the published 0.8.1 crate.

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Cargo-конвенцией: до 1.0 bump MINOR считается ломающ

### Добавлено

- Lossless-операции исходного редактирования `ktav::edit::{insert_value, remove_value, rename_key}` (и их `*_segments`-варианты): добавление нового ключа Object с явной ошибкой коллизии `AlreadyExists` и без скрытых перезаписей; удаление листа/поддерева/элемента массива с сохранением пустых предков и форм контейнеров; переименование члена Object на месте с сохранением порядка, значения и комментариев. Та же грамматика путей, пороги inline-читаемости, отказы при потере комментариев и семантическая верификация результата; невалидный исходник — fail closed `Unsupported`. Доступно хостам как шесть аддитивных, обратно совместимых экспортов ABI 2 — `ktav_insert_value`, `ktav_insert_value_segments`, `ktav_remove_value` (два входных буфера, без replacement), `ktav_remove_value_segments`, `ktav_rename_key` и `ktav_rename_key_segments` (UTF-8 литеральный decoded ключ); `EditAlreadyExists` отображается в `EditAlreadyExists`; новым биндингам нужны эти символы, ABI-версия не меняется.
- Rust API редактирования исходного текста: `ktav::edit::edit_value` принимает экранированный dotted-путь, `edit_value_segments` — слайс/вектор буквальных сегментов. Обе функции заменяют существующее значение без форматирования документа, сохраняют окружающие байты и комментарии и возвращают новый `String`.
- Контекстное различение ключей объектов и индексов массивов, escape-последовательности пути `\.`/`\\`, минимальное переключение raw-маркера, неизменный текст при совпадении значений и семантическая проверка результата. Типизированный `EditError` отвергает ошибочные пути, отсутствующие цели и неподдерживаемые lossless-замены, включая целые noncontiguous-объекты и структурные правки с потерей комментариев.
- Регрессионные тесты точного текста и примеры EN/RU/ZH. Синтаксис, существующий API парсера/formatter и версия пакета не меняются; функционал не входит в опубликованный crate 0.8.1.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

### 新增

- 无损源编辑操作 `ktav::edit::{insert_value, remove_value, rename_key}`(及各自的 `*_segments` 变体):追加新的 Object 键,冲突时报显式 `AlreadyExists` 且绝不隐蔽覆盖;删除叶子/子树/数组元素时保留空祖先和容器形式;在原位重命名 Object 成员并保留顺序、值与注释。沿用相同的路径语法、行内可读性阈值、注释丢失拒绝及编辑后语义验证;无效源以 `Unsupported` 失败闭合。通过六个增量且向后兼容的 ABI 2 导出提供给宿主——`ktav_insert_value`、`ktav_insert_value_segments`、`ktav_remove_value`(两个输入缓冲区,无替换参数)、`ktav_remove_value_segments`、`ktav_rename_key` 和 `ktav_rename_key_segments`(UTF-8 字面已解码键);`EditAlreadyExists` 映射为 `EditAlreadyExists`;新绑定必须要求这些符号,ABI 版本不变。
- Rust 源文本编辑 API:`ktav::edit::edit_value` 接受带转义的点分路径,`edit_value_segments` 接受字面路径段切片/向量。两者均在不重新格式化文档的情况下替换已有值,保留周围字节与注释,并返回新的 `String`。
- 按上下文区分对象键与数组索引,支持路径转义 `\.`/`\\`、最小化 raw 标记调整、值不变时保留原文,以及编辑后的语义验证。类型化的 `EditError` 拒绝错误路径、不存在的目标及不支持的无损替换,包括整个不连续对象和会丢失注释的结构修改。
- 精确文本回归测试及 EN/RU/ZH 用法示例。不改变语法、现有解析器/formatter API 或包版本;此功能不包含在已发布的 0.8.1 crate 中。
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -917,10 +917,20 @@ Expansion opens the containing inline wrapper and only the affected nested wrapp

A short paired benchmark lives in `benches/source_edit.rs`. Build it separately with `cargo bench --bench source_edit --no-run --message-format=json`, then run the reported executable with `baseline` or `candidate` as its argument. Apply an external 15-second process timeout (for example, `caps 15 <benchmark-executable> candidate` on Windows). Each case uses a 20 ms warmup and a 300 ms sample; setup and exact-output validation are outside the measured interval. Compare alternating A/B and B/A runs on the same toolchain; short-run wall-clock results are not a CI regression gate.

Only existing values can be replaced: no automatic container creation, removal or renaming. An unchanged value returns the original text, including its original numeric spelling. New values are supplied as `Value`; a numeric-looking `Value::String` stays a string, using a raw marker where necessary.
Replacement is joined by `insert_value`, `remove_value` and `rename_key` (see the section below): a new final Object key, deletion of a leaf/subtree/Array element, and an in-place member rename. An unchanged value returns the original text, including its original numeric spelling. New values are supplied as `Value`; a numeric-looking `Value::String` stays a string, using a raw marker where necessary.

`EditError::InvalidPath` reports a malformed textual path and byte offset; `NotFound` covers missing keys, invalid/out-of-range array indexes and descent through a scalar. `Unsupported` rejects an invalid source, an unrepresentable replacement or a replacement for which this implementation cannot preserve the surrounding text. In particular, a whole object assembled from separate dotted/reopened definitions has no single replacement range, and replacing a container must not delete its internal comments. Its individual leaves remain editable. Inline values use escapes for CR and LF. Outside inline compounds, CR-containing strings and some context-dependent multiline/container replacements are rejected rather than silently rewritten. Every returned patch is parsed and checked against the intended value tree; errors never return a partially edited document.

### Insertion, removal and renaming (unreleased)

`insert_value(source, path, value)` adds a new final Object key under an existing Object parent. The pair is appended as a whole line: at the end of the parent for block and implicit-root containers, as an inline segment before the closer for inline containers; the source EOL and the presence or absence of the final newline are preserved. An existing key is `EditError::AlreadyExists` — insertion never overwrites; insertion into an Array is an explicit `Unsupported`; missing intermediates and descent through a scalar are `NotFound`. Inline strings use the inline escaper (commas, brackets, backslash, line breaks, numeric-looking bodies); when the readability rules require it, the inline parent is expanded to block form; when a lossless block form is unavailable (a CR, a `))` line), the verified inline form is kept.

`remove_value(source, path)` deletes an existing leaf, a whole subtree, or one Array element (the tail shifts). Empty ancestors are not pruned: removing the only dotted leaf materializes the emptied dotted chain as a `k: {}` line at the same position, while a real block keeps its braces and an inline container keeps its inline form. Comment lines inside a removed container are refused with `Unsupported`; comments adjacent to the target are preserved. The BOM is kept as source metadata and never enters an edit range.

`rename_key(source, path, new_key)` renames a member in place, replacing only the last addressed key segment: dotted prefixes, quotes and escapes of the source key are kept byte-for-byte, and the new key is spelled with the standard bare/quoted encoding. An identical key is a byte-identical no-op; an existing sibling target is `AlreadyExists`; an Array index or an unrepresentable key is `Unsupported`. Order, value, separator bytes and comments are untouched.

All three operations have `*_segments` variants taking decoded literal segments, share the textual path grammar of `edit_value`, and run the same parse–splice–reparse–verify pipeline; an invalid source fails closed with `Unsupported`.

### Source-edit cookbook

These examples describe the unreleased 0.9 API, not the published 0.8.1 package. They replace existing values only: keys are not renamed, missing paths are not created, and entries are not removed. Each snippet is independent; Rust `?` assumes an enclosing `Result`-returning function. Go examples panic only for these fixed, known-valid inputs; applications should handle the returned error.
Expand Down Expand Up @@ -1082,11 +1092,11 @@ The Go, Java, PHP, C#, JS and Python bindings share the Rust core. The off-by-de
ktav::declare_cabi!();
```

The **unreleased 0.9 preparation uses ABI 2**, with twelve symbols: nine document functions (`ktav_loads`, `ktav_loads_strict`, `ktav_dumps`, `ktav_dumps_force_strings`, `ktav_emit_canonical`, `ktav_format`, `ktav_canonical_from_source`, `ktav_edit_value`, `ktav_edit_value_segments`), `ktav_free`, `ktav_version` and `ktav_abi_version`. Hosts must reject an ABI-1 binary rather than silently falling back to a different backend. This does not publish or change a binding package version.
The **unreleased 0.9 preparation uses ABI 2**, with eighteen symbols: fifteen document functions (`ktav_loads`, `ktav_loads_strict`, `ktav_dumps`, `ktav_dumps_force_strings`, `ktav_emit_canonical`, `ktav_format`, `ktav_canonical_from_source`, `ktav_edit_value`, `ktav_edit_value_segments`, `ktav_insert_value`, `ktav_insert_value_segments`, `ktav_remove_value`, `ktav_remove_value_segments`, `ktav_rename_key`, `ktav_rename_key_segments`), `ktav_free`, `ktav_version` and `ktav_abi_version`. The six object-mutation functions are additive ABI 2 exports: existing binaries keep working unchanged, and bindings that need insert/remove/rename must require the new symbols — there is no hidden pure fallback. Hosts must reject an ABI-1 binary rather than silently falling back to a different backend. This does not publish or change a binding package version.

Both edit functions take three length-delimited byte inputs: Ktav source, path and replacement JSON wire value. The first path is UTF-8 dotted text; the second is a JSON array of literal decoded strings. Tagged numeric values (`$i` / `$f`) preserve scalar types and precision; the replacement may itself be a scalar. Success returns UTF-8 Ktav source, not a JSON string, using the same lossless editing and readability rules as `ktav::edit`.
Both edit functions take three length-delimited byte inputs: Ktav source, path and replacement JSON wire value. The first path is UTF-8 dotted text; the second is a JSON array of literal decoded strings. Tagged numeric values (`$i` / `$f`) preserve scalar types and precision; the replacement may itself be a scalar. Success returns UTF-8 Ktav source, not a JSON string, using the same lossless editing and readability rules as `ktav::edit`. The mutation family adds `ktav_insert_value(_segments)` for appending a new Object key, `ktav_remove_value(_segments)` with exactly two input buffers (source and path, no replacement), and `ktav_rename_key(_segments)` whose `new_key` input is a UTF-8 literal decoded key — not JSON and not path syntax.

Every error uses the existing ten-field JSON envelope. Source diagnostics retain the 0.9 raw-byte locations. Edit failures use `EditInvalidPath`, `EditNotFound` or `EditUnsupported`, without invented source positions: `body` carries the decimal path byte offset for `EditInvalidPath`, the reason for `EditUnsupported`, or null for `EditNotFound`. Transport JSON/UTF-8 errors outside the source use `Message`; the host surfaces `message` as its error text.
Every error uses the existing ten-field JSON envelope. Source diagnostics retain the 0.9 raw-byte locations. Edit failures use `EditInvalidPath`, `EditNotFound`, `EditAlreadyExists` (body null; insertion never overwrites) or `EditUnsupported`, without invented source positions: `body` carries the decimal path byte offset for `EditInvalidPath`, the reason for `EditUnsupported`, or null for `EditNotFound` and `EditAlreadyExists`. Transport JSON/UTF-8 errors outside the source use `Message`; the host surfaces `message` as its error text.

The full signatures, ownership contract and artifact naming convention (`ktav_cabi-windows-amd64.dll`, `libktav_cabi-darwin-arm64.dylib`, `libktav_cabi-linux-amd64.so`, `$KTAV_LIB_PATH`) are specified in [docs/CABI.md](docs/CABI.md).

Expand Down
Loading
Loading