From 8092897612483189dbb54eec5b86fed7ac4d1185 Mon Sep 17 00:00:00 2001 From: Mihaela Mihaljevic Date: Wed, 24 Jun 2026 19:07:04 +0200 Subject: [PATCH] fix(parsing): parse a leading ':' flow-sequence entry as an empty-key pair A flow sequence entry that begins with ':' (e.g. '[ : v ]') is a single-pair mapping with an empty key, which YAML 1.2 permits but libyaml rejects by handing the leading VALUE token to its node parser. Open the implicit single-pair mapping on a leading ':' in the flow-sequence-entry parser, and leave the VALUE token for the mapping value state to consume so the value is parsed rather than dropped. Surpasses libyaml on CFD4. Port score 298 -> 299. --- CHANGELOG.md | 5 +++++ Sources/Parsing/LibYAML.Parser.Flow.swift | 10 +++++++++- Tests/CanonicalEventConformanceTests.swift | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0908c..cbc8053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Parse a flow sequence entry that begins with `:` as a single-pair mapping with + an empty key (`[ : v ]`). libyaml hands the leading VALUE token to its node + parser and fails. The flow-sequence-entry parser now opens the implicit mapping + on a leading `:` and leaves the indicator for the value state, so the value is + parsed rather than dropped. Surpasses libyaml on CFD4. - Apply the YAML 1.2 flow `:` rule instead of libyaml's "always a value indicator in flow". In a flow collection a `:` only ends a node when it is followed by whitespace, the end of input, or a flow indicator, or when it is diff --git a/Sources/Parsing/LibYAML.Parser.Flow.swift b/Sources/Parsing/LibYAML.Parser.Flow.swift index 28638b6..db65a2d 100644 --- a/Sources/Parsing/LibYAML.Parser.Flow.swift +++ b/Sources/Parsing/LibYAML.Parser.Flow.swift @@ -24,6 +24,12 @@ extension PureYAML.Parsing.LibYAML.Parser { skipToken() return .mappingStart(anchor: nil, tag: nil, style: .flow, mark: token.startMark) } + if isValue(token) { + // A leading `:` opens a single-pair mapping with an empty key + // (`[ : v ]`). Keep the VALUE token for the mapping-key state. + state = .flowSequenceEntryMappingKey + return .mappingStart(anchor: nil, tag: nil, style: .flow, mark: token.startMark) + } if !isFlowSequenceEnd(token) { states.append(.flowSequenceEntry) return try parseNode(block: false, indentlessSequence: false) @@ -42,7 +48,9 @@ extension PureYAML.Parsing.LibYAML.Parser { states.append(.flowSequenceEntryMappingValue) return try parseNode(block: false, indentlessSequence: false) } - if isFlowSequenceEnd(token) { + // An empty key: leave a VALUE token for the value state to consume (so the + // value is parsed); a closing `]` ends the pair with an empty value too. + if isValue(token) || isFlowSequenceEnd(token) { state = .flowSequenceEntryMappingValue return processEmptyScalar(at: token.startMark) } diff --git a/Tests/CanonicalEventConformanceTests.swift b/Tests/CanonicalEventConformanceTests.swift index 3fb51a4..263d50a 100644 --- a/Tests/CanonicalEventConformanceTests.swift +++ b/Tests/CanonicalEventConformanceTests.swift @@ -12,7 +12,7 @@ struct CanonicalEventConformanceTests { /// Valid suite cases whose event stream PureYAML does NOT yet emit exactly. /// This is the authoritative gap set: a fix removes an id (and the test fails /// until it is removed), a regression adds one (and the test fails). Of these, - /// 8 currently throw on valid input and 2 emit a wrong event stream. + /// 7 currently throw on valid input and 2 emit a wrong event stream. static let knownEventGaps: Set = [ // Gap set for the ported libyaml engine. Verified against the libyaml // reference dumper: on all but four of these the port matches libyaml @@ -22,7 +22,7 @@ struct CanonicalEventConformanceTests { // // false-rejects (parseEvents throws on valid input, as libyaml does) "6BCT", "6CA3", - "A2M4", "CFD4", "DK95/00", + "A2M4", "DK95/00", "DK95/04", "Q5MG", "Y79Y/010",