How an edit is made¶
yamldoc has its own YAML engine. Knowing roughly how it works explains why the
guarantees in What is preserved can be as strong as
they are, and why the API has the shape it has.
Source, syntax, semantics¶
Parsing keeps three things apart.
The source is the bytes you handed in, decoded from UTF-8, UTF-16 or UTF-32 but never rewritten; every later structure points into it by byte span.
The syntax is an immutable tree of occurrences over that source: scalars,
mappings, sequences and aliases, each with its style, tag, anchor and exact
location, plus the directives and every token in between, comments included.
It says nothing about meaning; yes is a plain scalar spelt yes.
The semantics is a graph composed from the syntax under a schema profile:
the YAML version's rules for what a plain scalar means, which keys are equal,
what an alias binds to. Composition is lazy, cached per revision, and can fail
in part without taking the syntax with it, which is how Validate can point
at a dangling alias in a file that still round-trips exactly.
A Snapshot is a syntax revision plus its lazily composed semantics. It is
immutable, which is what lets one be read while another is being written.
A transaction¶
Edit opens a transaction against the current revision. Each command inside
it does four things:
- States its intention independently of the source: "this occurrence now has this value", "this entry is gone", "this subtree corresponds to that one". The statement lives in its own model, built from the semantic graph, not from the bytes.
- Plans the new source for the edit's footprint alone, in the style of the source around it, and records which existing bytes the result still depends on.
- Applies the plan to a private working copy of the syntax, without
re-parsing, and produces the next working revision. Its semantics are
derived from the previous graph when only one scalar's value changed,
and composed afresh otherwise. Every
Nodefrom the previous working revision is now stale, which is why the API asks you to select again. - Records which comment belongs to which owner.
When the callback returns, publication re-parses the emitted bytes from scratch, composes them, and compares the result against every statement from step 1: the values, the correspondence between old and new occurrences, the retained bytes from step 2, and the comment ownership from step 4. Only if all of it agrees is the new revision installed. The check takes nothing from the writer but its bytes: they are parsed as a fresh file would be, and compared with a statement built before the writer ran, so a bug in the writer produces an error, not a corrupted file. What the check cannot catch is a bug the parser shares with it; the test suite's independent expectations cover that side (spec 0008, D10).
Why the limits¶
Everything the engine does is charged against a budget: source bytes, nodes,
depth, comment count, work per transaction. A library fed untrusted files has
to be bounded, and a bounded engine has to say what its bounds are. The zero
Options uses defaults sized for configuration files; the numbers are
provisional and will be replaced by measured ones.
Why not goccy¶
The first version of this library was built on goccy/go-yaml, the best
parser and concrete syntax tree available in Go. It could round-trip a file
but not edit one faithfully: comment alignment collapsed, multi-line flow
collections reflowed, CRLF became LF, and several corruption cases had to be
detected and refused rather than handled, because a tree that is re-emitted
cannot promise to leave any byte alone. The decision to own the engine, and
what it had to do, is spec 0008 on the
wiki;
the investigation that led to it is the report it cites.