A line that begins with a document marker ('---' or '...') followed by a
boundary is forbidden inside a multi-line scalar, since it is ambiguous
with a document boundary. A multi-line quoted scalar folded such a line
into its value instead of rejecting it.
scanQuotedScalar now checks, on crossing each line break, whether the next
line begins a document marker, and rejects the scalar if so. The check
requires the marker at the line start with a following boundary, so inline
dashes ('a --- b'), '---' with no boundary ('---b'), an indented '---',
and dots not at a line start stay ordinary content.
Measured against the official YAML test-suite, parse-level conformance
rises from 268/333 (80.5%) to 270/333 (81.1%) with no regression.
A block collection must begin on the line after its node properties, so a
block sequence whose first entry sits on the same line as its anchor or tag
('&a - x', '!!seq - x') is malformed. The parser accepted it, anchoring or
tagging the inline sequence.
parseBlockSequence now rejects a sequence whose first block entry shares a
line with the anchor or tag that introduced it, via a small helper to stay
within the complexity and brace-style limits. An anchor or tag on its own
line above the sequence, inside a sequence item ('- &a x'), before a flow
sequence ('&a [x]'), or on a scalar is unaffected; the mapping form is
left alone, since '&a key: value' is a valid anchored key.
Measured against the official YAML test-suite, parse-level conformance
rises from 267/333 (80.2%) to 268/333 (80.5%) with 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.
A block implicit key must lie on a single line, so a quoted scalar whose
opening and closing quotes sit on different lines cannot be one. The
scanner synthesized the implicit key regardless, so '"c\n d": 1' and the
single-quoted form parsed as a mapping with a folded key.
When synthesizing the implicit key for a quoted scalar followed by ':',
the scanner now rejects the construct if the scalar spanned a line break.
The restriction is block-only: a flow mapping key may span lines, so the
check is gated on block context, and a multiline quoted value, a single-
line quoted key, and an explicit key (which carries no ':' directly after
the scalar) are all unaffected.
Measured against the official YAML test-suite, parse-level conformance
rises from 261/333 (78.4%) to 264/333 (79.3%) with no regression.
A block sequence cannot be an implicit key's value on the same line as its
':' (key: - a); the value must begin on a following line, or use flow
style. The scanner accepted the inline block entry and parsed it as the
key's sequence value.
The block-entry case now rejects a '-' that starts a sequence when the
line already holds an implicit-key mapping value, reusing the same
same-line-pair flag as the inline nested mapping rule. As there, an
explicit- or null-key ':' opens its line and does not set the flag, so a
compact sequence value after it stays valid, as do a sequence on the next
line, a nested sequence, a flow sequence, and a negative number (key: -1).
Measured against the official YAML test-suite, parse-level conformance
rises from 260/333 (78.1%) to 261/333 (78.4%) with no regression.
A document-end marker takes no node on its own line: only a comment or
blank may follow. The scanner skipped the rest of the marker's line, so
content such as the 'invalid' in '... invalid' was silently dropped and
the document accepted.
scanDocumentMarker now distinguishes the two markers after skipping spaces:
a document start may carry the root node on its line and leaves it for the
main loop, while a document end rejects any real content (a comment or
blank still passes). A '...' not followed by an indicator boundary is not
a marker at all and remains an ordinary plain scalar, so '...junk' still
parses as content.
Measured against the official YAML test-suite, parse-level conformance
rises from 255/333 (76.6%) to 256/333 (76.9%) with no regression.
The YAML grammar requires every directive to head an explicit document,
so a directive must be followed by a '---' marker. The scanner accepted a
directive with no document at all: '%YAML 1.2' alone, or '%YAML 1.2' then
a '...' document-end marker, parsed without error.
The scanner now records the line of a directive awaiting its document,
clears it at a document start, and rejects a still-pending directive at a
document-end marker and at stream end. Both scan drivers finalize through
the same stream-end path, so the check lives in one helper called from
each. The rule applies to any directive, including an unrecognized one
(which is otherwise still ignored); the directive test that relied on an
unrecognized directive parsing without a '---' is corrected to include
one, since that leniency is not permitted by the grammar.
Measured against the official YAML test-suite, parse-level conformance
rises from 253/333 (76.0%) to 255/333 (76.6%) with no regression.
The %YAML directive takes exactly one parameter, the version, and at most
one may appear per document. The scanner checked only that a valid version
was present: it ignored any further tokens, so '%YAML 1.2 foo' was
accepted, and it did not track that a %YAML directive had already been
seen, so a repeated directive was accepted too. It now rejects an extra
parameter (allowing only a trailing comment) and a second %YAML directive,
resetting the per-document flag at each document boundary. A well-formed
directive, with or without a trailing comment, is unaffected.
Measured against the official YAML test-suite, parse-level conformance
rises from 251/333 (75.4%) to 253/333 (76.0%) with no regression.
The double-quoted escape decoder recognized only a subset of escapes
(" \ \/ \b \f \n \r \t \u) and silently passed any other character
through. That accepted invalid escapes such as \. and \', and mangled
valid ones the subset omitted: \x and \U hex escapes, \v \e \0, the named
\N \_ \L \P, and a backslash before a space, were emitted as their literal
letters (\x0d became the text x0d).
The decoder now maps every escape in the YAML 1.2 table to its character,
reads \x (8-bit) and \U (32-bit) hex escapes alongside \u, treats a
backslash before a line break as an escaped line break that joins the
lines with no separator, and rejects an unknown or truncated escape with
a diagnostic. Single-character escapes move to a lookup table so the
decoder stays within the complexity limit.
Measured against the official YAML test-suite, parse-level conformance
rises from 247/333 (74.2%) to 249/333 (74.8%) as two invalid documents
are now rejected, and value-level from 166 to 170 of comparable cases
(90.4%) as the previously-mangled escapes decode correctly, with no
regression.
A '#' that begins a comment must be preceded by whitespace or open the
line; glued to earlier same-line content (e.g. "value"# c, [ a, b ]#c,
>#c, a flow entry then ,#c) it is neither a comment nor, in those
positions, plain-scalar content, so the document is malformed. Such input
was previously accepted as a comment.
A '#' that opens a line, follows a space, or sits inside a plain scalar
(a#b, where it stays literal content and never reaches this check) is
unaffected. The scanner tracks whether a real token has been scanned on
the current physical line, reset on each line break, rather than comparing
token line numbers: the streaming tokenizer drains its token buffer
between scans, and a block scalar's blank line emits an empty scalar
attributed to the next line, either of which would misread as content.
Measured against the official YAML test-suite, parse-level conformance
rises from 243/333 (73.0%) to 247/333 (74.2%): four wrongly-accepted
invalid documents are now rejected, with no valid document regressing.
The scalar-scanning routines move to Scanner.Scalars.swift to keep the
file within the length limit.
emptyDocument, expectedMappingKey, and tabIndentation were never thrown
anywhere in the parser. Empty and comment-only documents parse to null,
the mapping-key path throws unexpectedToken with an "expected mapping
key" message, and tabs in indentation are reported by the validation
layer's PreflightScanner rather than the parser. Drop the dead cases and
their switch arms and description test assertions. No reachable behavior
changes; the PropertyMutation.tabIndentation test mutation is unrelated
and untouched.
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.