Opt-in located parse (parseLocated / parseStreamLocated) on the hand-rolled
engine, with exact scalar end positions. Resolved against main's extracted
parseScalarNode by threading the token end mark through it.
An explicit key that is only a node property (`? &e` then `: &a`) parsed to an
`expectedNode` error. Unlike the implicit form (`&a : v`), the scanner emits no
synthesized empty scalar for an explicit key, so the block parser read the key
via parseNode, consumed the property, then hit the `:` value indicator with no
node and threw.
parseNode now treats a `:` in node position as an empty key carrying any
properties, reusing emitEmptyNodeOrThrow (the node it already produced for a
property at end of input) without consuming the `:`, so the mapping machinery
reads the `:` as the value indicator. To stay within the cyclomatic-complexity
limit, the scalar case is extracted to parseScalarNode.
yaml-test-suite PW8X now passes (parse-level 314/333 to 315/333). should-error
rejections and value-level matches are unchanged, and a value-level test pins
the produced tree for the anchored and tagged property-only explicit keys.
Port the located value tree onto the hand-rolled engine, reusing the
engine-agnostic located layer (Model.LocatedValue, LocatedPair,
SourcePosition, SourceRange, and the located composer helper) authored
on the conformance branch. The streaming composer now builds a parallel
located tree behind an opt-in tracksLocations flag, exposed as
parseLocated / parseStreamLocated; the plain Value path stays
byte-identical, gated behind the flag.
The scalar event carries the scanner token's end mark, so a located
scalar spans the whole token (end exclusive, one past the last
character) rather than being start-anchored. The hand-rolled scanner
already tracked token end marks, so this surfaces information the engine
was already computing.
Also stop emitting a spurious null document for a comment-only or empty
stream: YAML 1.2 gives such a stream zero documents, while the
single-document value API still maps it to null, so parse("") is
unchanged.
A whole-suite test proves the located projection equals a plain parse
for every passing case; the value, event-dump, and conformance suites
stay green (507 tests).
Content after a '...' document-end marker begins a new bare document, but
the scanner rejected it as an unsupported multi-document stream and the
parser expected the document to end at the marker. A stream like
'--- scalar / ... / key: value' or 'a / ... / b' is two documents.
The scanner now reopens the stream when content follows '...', and
parseDocument returns at a '...' so the outer stream loop parses the next
document, bare or marked. A trailing '...' with nothing after it still
leaves a single document. The single-document composer still rejects a
multi-document stream, so 'parse' is unchanged. Two tests that asserted
content after '...' is an error are corrected, since the construct is valid
per the grammar and the official test-suite.
Measured against the official YAML test-suite, parse-level conformance
rises from 288/333 (86.5%) to 289/333 (86.8%) with no regression.
A node property always attaches to a node, and an empty node is allowed, so
an anchor or tag with nothing after it (a bare '&anchor', a 'key: &a', or a
'!!str'/'!!null' with no value) denotes that property applied to a null
node. parseNode rejected it as a missing node.
parseNode now emits a null scalar carrying the properties when a node
position holds only an anchor or tag, via a small helper to stay within the
complexity limit; with no properties, a missing node is still an error. A
'!!str' empty node resolves to the empty string and '!!null' to null. The
error-path test that asserted a dangling property throws is corrected, as
the construct is valid per the grammar and the official test-suite.
Measured against the official YAML test-suite, parse-level conformance
rises from 287/333 (86.2%) to 288/333 (86.5%), with value-level up one and
no regression.
An alias is a bare reference and cannot carry node properties, so '&b *a'
(an anchor on an alias) and a tag on an alias are malformed. parseNode
ignored any pending anchor or tag when it reached an alias token, dropping
the property and accepting the document.
The alias case now rejects a pending anchor or tag, and moves to its own
parseAlias helper so parseNode stays within the complexity limit. A bare
alias and an anchor or tag on a real node are unaffected.
Measured against the official YAML test-suite, parse-level conformance
rises from 266/333 (79.9%) to 267/333 (80.2%) with no regression.
A comment terminates a plain scalar, so a multi-line plain scalar cannot
resume after one. The parser accepted 'key: word1' then a '#' line then
' word2', and 'a' then 'b # ...' then 'c', merging the trailing content
back into the value ('word1 word2', 'a b c').
The scanner folds a genuine multi-line plain scalar into a single token
and splits it into a value-line scalar, an indent, and a further scalar
only when a comment broke its continuation folding. That token shape
therefore always denotes a comment-interrupted scalar, so the parser now
rejects it rather than re-merging. The re-merge path and its helper are
removed as they only ever served this malformed shape. A real multi-line
plain scalar and a trailing comment after a value stay valid.
Measured against the official YAML test-suite, parse-level conformance
rises from 264/333 (79.3%) to 266/333 (79.9%) with no regression.
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.