PureYAML/Sources/Parsing/TokenEventParser.Flow.swift
Mihaela Mihaljevic ce0585f77e test(conformance): track all 402 suite cases; fix 3 regressions
The gap-only manifest stopped guarding a case once it was fixed, so later
work silently regressed previously-fixed cases (caught only by an external
full-suite run, not the gated sweep or full corpus). Replace it with a full
manifest of all 402 official cases (Tests/Fixtures/yaml-test-suite-manifest.yaml),
each carrying its conformant expectation and a passing/gap status, with every
case's files vendored. The gated sweep now asserts every passing case still
passes (the regression guard) and every gap still reproduces, so any regression
fails the build.

The honest gate-enforced count is 329/402 passing (the earlier 348 came from a
more lenient one-time oracle).

Repairs three regressions the new gate revealed: the bucket-6 tab rule
over-rejected tabs in block-scalar content and as separation before a scalar
value (root cause: the batch scanner and the incremental TokenCursor had
divergent line-start loops; unified onto a single scanStep, fixing 96NN/00,
96NN/01, R4YG, Y79Y/001, DK95/00, DK95/03, DK95/07, 6CA3, Q5MG, J3BT); an anchor
on a prior line before an alias key wrongly rejected 26DV; and a tag-only flow
key mis-parsed WZ62. Genuine tab-indentation errors (4EJS, Y79Y/000) still reject.
2026-06-24 00:16:28 +02:00

204 lines
9.3 KiB
Swift

extension PureYAML.Parsing.TokenEventParser {
/// Parses a flow sequence, mirroring libyaml's `flow_sequence_entry`
/// productions. Each entry is either a flow node or, when introduced by a
/// `mappingKey` (`?`) or recognized as a single-pair `key: value`, a one-entry
/// flow mapping nested directly in the sequence.
mutating func parseFlowSequence(
properties: PureYAML.Parsing.NodeProperties,
) throws -> PureYAML.Parsing.NodeResult {
let start = try expect("flow sequence start") { kind in
if case .flowSequenceStart = kind {
return true
}
return false
}
let startMark = properties.mark ?? start.mark
var endMark = start.endMark
try emit(.sequenceStart(
anchor: properties.anchor,
tag: properties.tag,
style: .flow,
mark: startMark,
))
while cursor.current != nil {
if case .flowSequenceEnd = cursor.current?.kind {
let end = try expect("flow sequence end") { kind in
if case .flowSequenceEnd = kind {
return true
}
return false
}
endMark = end.endMark
try emit(.sequenceEnd(mark: endMark))
return PureYAML.Parsing.NodeResult(kind: .sequence, endMark: endMark)
}
if case .flowEntry = cursor.current?.kind {
_ = try cursor.advance()
continue
}
endMark = try parseFlowSequenceEntry().endMark
if cursor.current?.kind.isFlowTerminator == false {
throw unexpectedToken(expected: "flow entry or flow sequence end")
}
}
throw unexpectedToken(expected: "flow sequence end")
}
/// Parses a single flow-sequence entry. A `mappingKey` (`?`, or one spliced in
/// by the simple-key mechanism before a `key: value` pair) produces a
/// single-pair flow mapping wrapping the entry; otherwise the entry is a plain
/// flow node.
mutating func parseFlowSequenceEntry() throws -> PureYAML.Parsing.NodeResult {
if cursor.current?.kind.isMappingKey == true {
return try parseFlowSequenceSinglePair()
}
return try parseNode()
}
/// Wraps an explicit-`?` single-pair entry of a flow sequence in a one-entry
/// flow mapping, mirroring libyaml's `flow_sequence_entry_mapping_*` states.
mutating func parseFlowSequenceSinglePair() throws -> PureYAML.Parsing.NodeResult {
let keyIndicator = try expect("mapping key") { $0.isMappingKey }
try emit(.mappingStart(anchor: nil, tag: nil, style: .flow, mark: keyIndicator.mark))
try parseFlowSinglePairKeyValue(keyIndicator: keyIndicator)
let mark = cursor.current?.mark ?? keyIndicator.endMark
try emit(.mappingEnd(mark: mark))
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: mark)
}
mutating func parseFlowMapping(
properties: PureYAML.Parsing.NodeProperties,
) throws -> PureYAML.Parsing.NodeResult {
let start = try expect("flow mapping start") { kind in
if case .flowMappingStart = kind {
return true
}
return false
}
let startMark = properties.mark ?? start.mark
var endMark = start.endMark
try emit(.mappingStart(
anchor: properties.anchor,
tag: properties.tag,
style: .flow,
mark: startMark,
))
while cursor.current != nil {
if case .flowMappingEnd = cursor.current?.kind {
let end = try expect("flow mapping end") { kind in
if case .flowMappingEnd = kind {
return true
}
return false
}
endMark = end.endMark
try emit(.mappingEnd(mark: endMark))
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: endMark)
}
if case .flowEntry = cursor.current?.kind {
_ = try cursor.advance()
continue
}
endMark = try parseFlowMappingPair().endMark
if cursor.current?.kind.isFlowTerminator == false {
throw unexpectedToken(expected: "flow entry or flow mapping end")
}
}
throw unexpectedToken(expected: "flow mapping end")
}
/// Parses one flow-mapping entry. With a `mappingKey` (`?`) indicator the key
/// follows the explicit form; without one the entry is a `flow_node` that is
/// itself the key (carrying an empty value unless a `:` provides one).
mutating func parseFlowMappingPair() throws -> PureYAML.Parsing.NodeResult {
if cursor.current?.kind.isMappingKey == true {
let keyIndicator = try expect("mapping key") { $0.isMappingKey }
return try parseFlowSinglePairKeyValue(keyIndicator: keyIndicator)
}
try parseFlowMappingImplicitKey()
return try parseFlowMappingValue()
}
/// Parses the key and value of a flow pair introduced by an explicit `?`,
/// where each side may be absent (composing an empty scalar). Shared by flow
/// mappings and single-pair entries nested in a flow sequence.
@discardableResult
mutating func parseFlowSinglePairKeyValue(
keyIndicator: PureYAML.Parsing.Token,
) throws -> PureYAML.Parsing.NodeResult {
if flowKeyIsEmpty() {
try emitEmptyScalar(properties: .none, mark: keyIndicator.endMark)
} else {
// The key of a flow pair follows the same rules whether the pair was
// introduced by an explicit `?` or had a `mappingKey` spliced in by the
// simple-key mechanism: a bare tag/anchor before the `:` composes an empty
// tagged scalar key (`{ !!str : bar }`), and an alias before the `:` is the
// key itself. Routing through `parseNode` would instead treat the tag/`:`
// as a nested block mapping, so use the flow key parser shared with the
// implicit form.
try parseFlowMappingImplicitKey()
}
return try parseFlowMappingValue()
}
/// Parses the key of an implicit flow-mapping entry (`key : value` with no
/// explicit `?`). The key may be absent entirely (`{ : v }`, `{ , : v }`)
/// or be only a tag/anchor with no content, both composing to an empty
/// scalar key rather than routing into a block mapping.
mutating func parseFlowMappingImplicitKey() throws {
let properties = try consumeProperties()
if properties == .none, cursor.current?.kind.isMappingValue == true {
let mark = cursor.current?.mark ?? cursor.previous?.endMark ?? .start
try emitEmptyScalar(properties: .none, mark: mark)
return
}
if properties != .none, cursor.current?.kind.isMappingValue == true || cursor.current?.kind.isFlowTerminator == true {
let mark = cursor.current?.mark ?? cursor.previous?.endMark ?? .start
try emitEmptyScalar(properties: properties, mark: mark)
return
}
// An alias as a flow-mapping key (`{*a: b}`) is emitted directly so it is
// not re-routed into a block mapping by the alias-as-key check in
// `parseNode`, which only applies to block context.
if properties == .none, cursor.current?.kind.isAlias == true, try cursor.peek()?.kind.isMappingValue == true {
try emitAliasKey()
return
}
_ = try parseNode(properties: properties)
}
/// Parses the value half of a flow-mapping entry. A `:` consumes its node (or an
/// empty scalar when the value is absent); without a `:` the entry has an
/// implicit empty value (libyaml's `flow_mapping_value` with `empty == 1`).
mutating func parseFlowMappingValue() throws -> PureYAML.Parsing.NodeResult {
guard cursor.current?.kind.isMappingValue == true else {
let mark = cursor.current?.mark ?? cursor.previous?.endMark ?? .start
try emitEmptyScalar(properties: .none, mark: mark)
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: mark)
}
let valueIndicator = try expect("mapping value") { kind in
if case .mappingValue = kind {
return true
}
return false
}
let properties = try consumeProperties()
guard properties != .none || canBeginNode(cursor.current), cursor.current?.kind.isFlowTerminator == false else {
let mark = valueIndicator.endMark
try emitEmptyScalar(properties: properties, mark: mark)
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: mark)
}
return try parseNode(properties: properties)
}
/// True when the key position right after a `?` indicator is empty, i.e. the
/// next token is a `:` value indicator, a flow terminator, or the end of input,
/// so an empty scalar key must be composed (libyaml's `flow_mapping_key`).
func flowKeyIsEmpty() -> Bool {
guard let current = cursor.current else {
return true
}
return current.kind.isMappingValue || current.kind.isFlowTerminator
}
}