fix(parsing): allow an anchor or tag on a non-first mapping key
A block mapping pair after the first could not carry an anchor or tag on its key (k: 1 then &b key2: 2): the first pair's key properties are consumed when the mapping is entered, but subsequent pairs were not, so an anchored key past the first failed with 'expected mapping key, found anchor'. The pair parser now consumes leading key properties for every pair, attaching them to the key node, and a block mapping continues across an anchor or tag token that begins a key. Normal mappings, anchored values, sequences, and explicit keys are unaffected. Measured against the official YAML test-suite, parse-level conformance rises from 235/333 (70.6%) to 238/333 (71.5%), with no regression.
This commit is contained in:
parent
58f30b4343
commit
0ae88855da
@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A block mapping pair after the first may now carry an anchor or tag on its key
|
||||
(`k: 1` / `&b key2: 2`). The first pair's key properties were consumed when the mapping was
|
||||
entered, but later pairs were not, so an anchored key past the first failed with
|
||||
"expected mapping key". The pair parser now consumes leading key properties for every pair,
|
||||
and the mapping continues across an anchor or tag that begins a key. Lifts official test-suite
|
||||
parse-level conformance from 235/333 (70.6%) to 238/333 (71.5%).
|
||||
- An explicit mapping key (`? key`) with no following `:` now maps to null instead of failing to
|
||||
parse, so several `? key` entries in a row (a set, for instance) each map to null. The parser
|
||||
required a `:` after every explicit key; it now treats a missing one as an implicit null value.
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
extension PureYAML.Parsing.TokenEventParser {
|
||||
mutating func parseMappingPair() throws -> PureYAML.Parsing.NodeResult {
|
||||
// A subsequent pair's key may carry an anchor or tag (`&a key: …`). The first pair's
|
||||
// properties are consumed when the mapping is entered; consuming here covers the rest, and
|
||||
// is a no-op (`.none`) when there are none.
|
||||
let keyProperties = try consumeProperties()
|
||||
_ = try expect("mapping key") { $0.isMappingKey }
|
||||
let key = try parseNode()
|
||||
let key = try parseNode(properties: keyProperties)
|
||||
// An explicit key (`? key`) with no following `:` has an implicit null value, so several
|
||||
// `? key` entries in a row (a set, for instance) each map to null rather than being an error.
|
||||
guard case .mappingValue = cursor.current?.kind else {
|
||||
@ -246,7 +250,7 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
}
|
||||
|
||||
func canContinueBlockMapping(minimumColumn: Int?) -> Bool {
|
||||
guard let current = cursor.current, current.kind.isMappingKey else {
|
||||
guard let current = cursor.current, current.kind.isMappingKey || startsNodeProperties(current.kind) else {
|
||||
return false
|
||||
}
|
||||
guard let minimumColumn else {
|
||||
@ -255,6 +259,17 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
return current.mark.column >= minimumColumn
|
||||
}
|
||||
|
||||
/// Whether a token kind begins a node's properties (an anchor or a tag), which a mapping key may
|
||||
/// carry, so the next pair's key is `&a key: …` rather than a bare key.
|
||||
func startsNodeProperties(_ kind: PureYAML.Parsing.TokenKind) -> Bool {
|
||||
switch kind {
|
||||
case .anchor, .tag:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
func canEndBlockMapping(
|
||||
at token: PureYAML.Parsing.Token?,
|
||||
minimumColumn: Int? = nil,
|
||||
|
||||
19
Tests/AnchoredMappingKeyTests.swift
Normal file
19
Tests/AnchoredMappingKeyTests.swift
Normal file
@ -0,0 +1,19 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Anchored mapping keys")
|
||||
struct AnchoredMappingKeyTests {
|
||||
@Test("A subsequent pair's key may carry an anchor")
|
||||
func test_anchoredKeyOnSubsequentPair() throws {
|
||||
let value = try PureYAML.parse("k: 1\n&b key2: 2\n")
|
||||
#expect(value == .mapping(.init([
|
||||
.init(key: "k", value: .int(1)),
|
||||
.init(key: "key2", value: .int(2)),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("An anchored key on the first pair still parses")
|
||||
func test_anchoredKeyOnFirstPair() throws {
|
||||
#expect(try PureYAML.parse("&a c: 3\n") == .mapping(.init([.init(key: "c", value: .int(3))])))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user