The emitter kept its own list of reserved words and number shapes,
apart from the parser's, and the two disagreed: the string yes was
written plain and read back as a boolean, as were 0x1F, 1_000 and
.inf. The parser's reading of a plain scalar is now one type that
both use, and the emitter quotes what does not read back as a string.
Also quoted: the merge key, the document end marker, and a string
with a CR+LF break, which was written raw.
Seven correctness fixes to the recently landed anchors/styles, canonical,
timestamp/binary, and full-node-key work, each reproduced and covered by a
regression test.
- Stream dump doubled the `---` marker under explicitDocumentStart or
canonical: the single-document body now renders marker-free and the
stream path owns marker emission, so a two-document stream reparses as
two documents, not four.
- Tagged flow-mapping keys used block-context safety, so a key like `a,b`
emitted bare and reparsed as several entries; flow keys now use the
flow-aware plain-scalar check.
- Plain scalars ending in `:` reparse as an empty-valued mapping key, so
both the model and tagged emit paths now fall back to quoting them.
- An alias mapping key pointing at a defined non-scalar anchor raised the
misleading undefinedAlias; it now raises expectedScalarKey, and a truly
undefined alias key still raises undefinedAlias.
- Tagged duplicate-key detection keyed on source spelling: it now keys on
the resolved scalar value plus effective tag, so 0x1 and 1 collide while
1 and "1" do not and custom-tagged keys stay distinct.
- Dumping a tree nested past the emitter limit overflowed the stack.
- Timestamp now accepts a leap-second value of 60, counted as 60 elapsed
seconds in the epoch math.
- Tagged.Dumper honors sortKeys in its block and canonical paths for
all-plain-string-key mappings, matching the model dumper.
Nesting-guard design: dump is public and non-throwing across 90+ call
sites, so making it throwing would be a breaking change. A tree nested
deeper than the parser's own maxNestingDepth cannot round-trip through
PureYAML (the parser rejects it), so no faithful emission exists. Each
dump entry point now measures depth iteratively (no recursion, so the
guard itself cannot overflow) and, past the limit, returns a documented,
reparseable marker document that resolves to null. Under the limit the
existing recursive renderers run byte-identically, so parsed trees and
every realistic hand-built tree are unaffected. This mirrors the parser's
enforceNestingLimit, which bounds depth before any recursive consumer.
Completes issue #10. Canonical mode follows libyaml's canonical layout
on both the Model and Tagged dump paths (tags explicit, scalars double
quoted, flow one entry per line, anchors and aliases preserved on the
Tagged path). explicitDocumentStart/End wrap the body in --- and ...
markers. lineWidth wraps overflowing flow collections one entry per
line and folds plain block scalars at single-space boundaries, vetoing
any fold that would manufacture syntax, so values always reparse
unchanged.
Closes#10
Closes two of the four emitter gaps a feature-parity audit against Yams
found (PureYAMLYamsComparison's FEATURE-PARITY.md): no configurable
indentation and no sorted-key output.
indentWidth replaces every hardcoded indent + 2 in Dumper.swift/
Dumper.Mapping.swift, verified with round-trip tests since indentation is
purely a rendering choice and must never change what a document means.
sortKeys sorts a mapping's pairs by their string key in both block and flow
style, but only when every key in that mapping is a plain string; a mapping
with any complex (mapping/sequence) key stays in insertion order, since
there is no well-defined ordering across mixed key kinds. Off by default,
since insertion order is what makes a duplicate-key-preserving Mapping
round-trip meaningfully in the first place.
width-based line wrapping and canonical mode are each a correctness surface
of their own and are deliberately left for later, not folded into this.
A CR+LF pair forms a single Swift extended grapheme cluster, so quote()'s Character-level switch matched neither '\\r' nor '\\n' and emitted the line break unescaped. The folding parser then read the dumped value back differently, breaking parse/dump/parse stability for any string containing CRLF (surfaced as a round-trip failure on the Zoom OpenAPI corpus seed under PUREYAML_RUN_FULL_CORPUS). Iterating over unicode scalars escapes CR and LF independently. Adds a dumper regression test.
Add structured validation diagnostics, JSON and Yams comparison gates, and lazy token/event parsing so production behavior is measured without retaining full intermediate parser arrays.