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.
232 lines
9.6 KiB
Swift
232 lines
9.6 KiB
Swift
extension PureYAML.Parsing.TokenEventParser {
|
|
mutating func parseBlockSequence(
|
|
properties: PureYAML.Parsing.NodeProperties,
|
|
allowMappingKeyTerminator: Bool = false,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let start = try expect("block sequence entry") { $0.isBlockEntry }
|
|
let startMark = properties.mark ?? start.mark
|
|
var endMark = start.endMark
|
|
try emit(.sequenceStart(
|
|
anchor: properties.anchor,
|
|
tag: properties.tag,
|
|
style: .block,
|
|
mark: startMark,
|
|
))
|
|
|
|
try parseBlockSequenceItem(
|
|
after: start,
|
|
endMark: &endMark,
|
|
allowMappingKeyTerminator: allowMappingKeyTerminator,
|
|
)
|
|
try consumeSequenceItemBoundaryDedent(sequenceIndentWidth: start.mark.column - 1)
|
|
try consumeSequenceItemBoundaryIndent(sequenceIndentWidth: start.mark.column - 1)
|
|
while cursor.current?.kind.isBlockEntry == true {
|
|
let entry = try expect("block sequence entry") { $0.isBlockEntry }
|
|
try parseBlockSequenceItem(
|
|
after: entry,
|
|
endMark: &endMark,
|
|
allowMappingKeyTerminator: allowMappingKeyTerminator,
|
|
)
|
|
try consumeSequenceItemBoundaryDedent(sequenceIndentWidth: entry.mark.column - 1)
|
|
try consumeSequenceItemBoundaryIndent(sequenceIndentWidth: entry.mark.column - 1)
|
|
}
|
|
if let current = cursor.current, current.kind.isMappingKey, !allowMappingKeyTerminator {
|
|
throw PureYAML.Parsing.ParseError.mixedCollectionStyles(line: current.mark.line)
|
|
}
|
|
|
|
try emit(.sequenceEnd(mark: endMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .sequence, endMark: endMark)
|
|
}
|
|
|
|
mutating func consumeSequenceItemBoundaryDedent(sequenceIndentWidth: Int) throws {
|
|
guard dedentWidth(cursor.current) == sequenceIndentWidth,
|
|
try cursor.peek()?.kind.isBlockEntry == true
|
|
else {
|
|
return
|
|
}
|
|
_ = try expect("dedent") { $0.isDedent }
|
|
}
|
|
|
|
mutating func consumeSequenceItemBoundaryIndent(sequenceIndentWidth: Int) throws {
|
|
guard indentWidth(cursor.current) == sequenceIndentWidth,
|
|
try cursor.peek()?.kind.isBlockEntry == true
|
|
else {
|
|
return
|
|
}
|
|
_ = try expect("indent") { $0.isIndent }
|
|
}
|
|
|
|
mutating func parseBlockSequenceItem(
|
|
after entry: PureYAML.Parsing.Token,
|
|
endMark: inout PureYAML.Parsing.Mark,
|
|
allowMappingKeyTerminator: Bool,
|
|
) throws {
|
|
if hasNodeOnSameLine(after: entry) {
|
|
let properties = try consumeProperties()
|
|
if cursor.current?.kind.isIndent == true {
|
|
_ = try expect("indent") { $0.isIndent }
|
|
endMark = try parseNode(properties: properties).endMark
|
|
_ = try expect("dedent") { $0.isDedent }
|
|
return
|
|
}
|
|
// After consuming anchor/tag properties the only content remaining on
|
|
// this entry's line may be gone: a `mappingKey` or sibling `-` now on
|
|
// the same line opens a nested collection, but one on a later line is
|
|
// the next sibling, leaving this entry an empty scalar that carries
|
|
// the properties.
|
|
if cursor.current?.kind.isMappingKey == true, hasNodeOnSameLine(after: entry) {
|
|
endMark = try parseBlockMapping(
|
|
properties: properties,
|
|
includeIndentedContinuation: true,
|
|
minimumColumn: entry.mark.column + 1,
|
|
).endMark
|
|
} else if cursor.current?.kind.isBlockEntry == true, hasNodeOnSameLine(after: entry) {
|
|
endMark = try parseBlockSequence(
|
|
properties: properties,
|
|
allowMappingKeyTerminator: allowMappingKeyTerminator,
|
|
).endMark
|
|
} else if canBeginNode(cursor.current), hasNodeOnSameLine(after: entry) {
|
|
endMark = try parseNode(properties: properties).endMark
|
|
} else {
|
|
let mark = properties.mark ?? entry.endMark
|
|
try emitEmptyScalar(properties: properties, mark: mark)
|
|
endMark = mark
|
|
}
|
|
} else if cursor.current?.kind.isIndent == true {
|
|
_ = try expect("indent") { $0.isIndent }
|
|
endMark = try parseNode().endMark
|
|
_ = try expect("dedent") { $0.isDedent }
|
|
} else {
|
|
let mark = entry.endMark
|
|
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark))
|
|
endMark = mark
|
|
}
|
|
}
|
|
|
|
mutating func parseBlockMapping(
|
|
properties: PureYAML.Parsing.NodeProperties,
|
|
includeIndentedContinuation: Bool,
|
|
minimumColumn: Int? = nil,
|
|
firstKeyProperties: PureYAML.Parsing.NodeProperties = .none,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let startMark = firstKeyProperties.mark ?? properties.mark ?? cursor.current?.mark ?? .start
|
|
let tokenColumn = cursor.current?.mark.column ?? startMark.column
|
|
let contentColumn = min(startMark.column, tokenColumn)
|
|
let boundaryColumn = minimumColumn ?? contentColumn
|
|
var endMark = startMark
|
|
try emit(.mappingStart(
|
|
anchor: properties.anchor,
|
|
tag: properties.tag,
|
|
style: .block,
|
|
mark: startMark,
|
|
))
|
|
|
|
try parseBlockMappingPairs(
|
|
boundaryColumn: boundaryColumn,
|
|
includeIndentedContinuation: includeIndentedContinuation,
|
|
endMark: &endMark,
|
|
firstKeyProperties: firstKeyProperties,
|
|
)
|
|
if let current = cursor.current, !canEndBlockMapping(at: current, minimumColumn: boundaryColumn) {
|
|
throw PureYAML.Parsing.ParseError.unexpectedToken(
|
|
expected: "mapping key",
|
|
actual: current.kind.description,
|
|
line: current.mark.line,
|
|
column: current.mark.column,
|
|
)
|
|
}
|
|
try emit(.mappingEnd(mark: endMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: endMark)
|
|
}
|
|
|
|
mutating func parseBlockMappingPairs(
|
|
boundaryColumn: Int,
|
|
includeIndentedContinuation: Bool,
|
|
endMark: inout PureYAML.Parsing.Mark,
|
|
firstKeyProperties: PureYAML.Parsing.NodeProperties = .none,
|
|
) throws {
|
|
var keyProperties = firstKeyProperties
|
|
while canContinueBlockMapping(minimumColumn: boundaryColumn) {
|
|
endMark = try parseMappingPairAndBoundaries(
|
|
boundaryColumn: boundaryColumn,
|
|
includeIndentedContinuation: includeIndentedContinuation,
|
|
keyProperties: keyProperties,
|
|
).endMark
|
|
keyProperties = .none
|
|
}
|
|
while includeIndentedContinuation, cursor.current?.kind.isIndent == true {
|
|
endMark = try parseIndentedBlockMappingPairs(boundaryColumn: boundaryColumn).endMark
|
|
}
|
|
}
|
|
|
|
mutating func parseIndentedBlockMappingPairs(
|
|
boundaryColumn: Int,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
_ = try expect("indent") { $0.isIndent }
|
|
guard cursor.current?.kind.isMappingKey == true else {
|
|
throw unexpectedToken(expected: "mapping key")
|
|
}
|
|
var endMark = cursor.current?.mark ?? .start
|
|
while canContinueBlockMapping(minimumColumn: boundaryColumn) {
|
|
endMark = try parseMappingPairAndBoundaries(
|
|
boundaryColumn: boundaryColumn,
|
|
includeIndentedContinuation: true,
|
|
).endMark
|
|
}
|
|
if !canEndBlockMapping(at: cursor.current, minimumColumn: boundaryColumn) {
|
|
_ = try expect("dedent") { $0.isDedent }
|
|
}
|
|
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: endMark)
|
|
}
|
|
|
|
mutating func parseMappingPairAndBoundaries(
|
|
boundaryColumn: Int,
|
|
includeIndentedContinuation: Bool,
|
|
keyProperties: PureYAML.Parsing.NodeProperties = .none,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let result = try parseMappingPair(keyProperties: keyProperties)
|
|
try consumeMappingSiblingBoundaryDedents(minimumColumn: boundaryColumn)
|
|
try consumeMappingSiblingBoundaryReindent(minimumColumn: boundaryColumn)
|
|
if includeIndentedContinuation {
|
|
try consumeDedents(atOrAbove: boundaryColumn)
|
|
try consumeMappingSiblingBoundaryDedents(minimumColumn: boundaryColumn)
|
|
try consumeMappingSiblingBoundaryReindent(minimumColumn: boundaryColumn)
|
|
}
|
|
return result
|
|
}
|
|
|
|
mutating func parseBlockScalar(
|
|
properties: PureYAML.Parsing.NodeProperties,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let header = try expect("block scalar header") { kind in
|
|
if case .blockScalarHeader = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
guard case let .blockScalarHeader(_, _, value) = header.kind else {
|
|
throw unexpectedToken(expected: "block scalar header")
|
|
}
|
|
let mark = properties.mark ?? header.mark
|
|
try emit(.scalar(
|
|
value: value,
|
|
anchor: properties.anchor,
|
|
tag: properties.tag,
|
|
style: scalarStyle(for: header.kind),
|
|
mark: mark,
|
|
))
|
|
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: header.endMark)
|
|
}
|
|
|
|
func scalarStyle(for kind: PureYAML.Parsing.TokenKind) -> PureYAML.Parsing.ScalarStyle {
|
|
switch kind {
|
|
case .blockScalarHeader(.folded, _, _):
|
|
.folded
|
|
case .blockScalarHeader(.literal, _, _):
|
|
.literal
|
|
default:
|
|
.plain
|
|
}
|
|
}
|
|
}
|