PureYAML/Sources/Parsing/TokenEventParser.Flow.swift
Mihaela Mihaljevic dfb498a89c fix(parsing): accept anchors, tags, and aliases as mapping keys
A mapping key is a full node: it may carry &anchor and !tag properties and may itself be an *alias. Fixes: (1) anchor/alias name scanning now stops at the first non-name character per yaml_parser_scan_anchor, so *a: reads as alias 'a' plus a mapping indicator instead of name 'a:'; (2) the block-mapping parser enters on a leading node property or alias, attaches same-line key properties to the key node, and emits alias keys directly; (3) an anchor or tag at value position with no same-line node composes an empty (null) value carrying the property, so 'a: &anchor' registers the anchor; (4) the composer registers an anchor's resolved model value (an empty anchored scalar registers null); (5) implicit keys with trailing spaces before the colon are recognized.

Flips 10 official YAML test suite cases to passing (280/402), including G9HC now correctly rejected. 2SXE no longer false-rejects but still value-mismatches on compact-mapping interpretation (a separate gap), so it is reclassified in the manifest rather than removed. Updates the manifest, fixtures, counts, and roadmap.
2026-06-23 21:07:47 +02:00

136 lines
5.4 KiB
Swift

extension PureYAML.Parsing.TokenEventParser {
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 parseNode().endMark
if cursor.current?.kind.isFlowTerminator == false {
throw unexpectedToken(expected: "flow entry or flow sequence end")
}
}
throw unexpectedToken(expected: "flow sequence end")
}
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")
}
mutating func parseFlowMappingPair() throws -> PureYAML.Parsing.NodeResult {
if cursor.current?.kind.isMappingKey == true {
return try parseMappingPair()
}
try parseFlowMappingPairKey()
guard cursor.current?.kind.isMappingValue == true else {
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: cursor.previous?.endMark ?? .start)
}
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)
}
/// 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 parseFlowMappingPairKey() 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)
}
}