refactor(parsing): share merge-key resolution between composers
The buffered and streaming event composers each carried an identical copy of the merge-key (<<) override logic and its invalid-value error. Extract both into one internal Parsing.MergeResolution helper so the precedence rules (local keys win, earlier sources win, inherited then local order) have a single source of truth. No behavior change; all tests pass.
This commit is contained in:
parent
87e3e5cbab
commit
9ae6c3baff
@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Merge-key (`<<`) override resolution now lives in a single internal helper,
|
||||
`Parsing.MergeResolution`, shared by the buffered and streaming event
|
||||
composers. Both paths previously carried an identical copy of the
|
||||
inherited-versus-local pair merge, so the subtle precedence rules (local keys
|
||||
win, earlier sources win, inherited-then-local ordering) now have one source
|
||||
of truth. No behavior change.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Correct `SECURITY.md`: fix the reporting contact address and replace the
|
||||
|
||||
@ -56,12 +56,12 @@ extension PureYAML.Parsing.EventComposer {
|
||||
case let .sequence(values):
|
||||
return try values.map { value in
|
||||
guard case let .mapping(mapping) = value else {
|
||||
throw invalidMergeValue(mark: mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: mark)
|
||||
}
|
||||
return mapping.pairs
|
||||
}
|
||||
case .bool, .double, .int, .null, .string:
|
||||
throw invalidMergeValue(mark: mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: mark)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,42 +70,8 @@ extension PureYAML.Parsing.EventComposer {
|
||||
mark: PureYAML.Parsing.Mark,
|
||||
) throws -> [PureYAML.Model.Pair] {
|
||||
guard case let .mapping(mapping) = value else {
|
||||
throw invalidMergeValue(mark: mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: mark)
|
||||
}
|
||||
return mapping.pairs
|
||||
}
|
||||
|
||||
func mergedPairs(
|
||||
from mergeSources: [[PureYAML.Model.Pair]],
|
||||
localPairs: [PureYAML.Model.Pair],
|
||||
) -> [PureYAML.Model.Pair] {
|
||||
guard !mergeSources.isEmpty else {
|
||||
return localPairs
|
||||
}
|
||||
|
||||
let localKeys = Set(localPairs.map(\.keyNode))
|
||||
var inheritedKeys = Set<PureYAML.Model.Key>()
|
||||
var inheritedPairs: [PureYAML.Model.Pair] = []
|
||||
for source in mergeSources {
|
||||
var sourceKeys = Set<PureYAML.Model.Key>()
|
||||
for pair in source {
|
||||
guard !localKeys.contains(pair.keyNode),
|
||||
!inheritedKeys.contains(pair.keyNode)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
inheritedPairs.append(pair)
|
||||
sourceKeys.insert(pair.keyNode)
|
||||
}
|
||||
inheritedKeys.formUnion(sourceKeys)
|
||||
}
|
||||
return inheritedPairs + localPairs
|
||||
}
|
||||
|
||||
func invalidMergeValue(mark: PureYAML.Parsing.Mark) -> PureYAML.Parsing.ParseError {
|
||||
PureYAML.Parsing.ParseError.invalidMergeValue(
|
||||
line: mark.line,
|
||||
column: mark.column,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ extension PureYAML.Parsing.EventComposer {
|
||||
}
|
||||
}
|
||||
_ = try expect("mapping end") { $0.isMappingEnd }
|
||||
let pairs = mergedPairs(from: mergeSources, localPairs: localPairs)
|
||||
let pairs = PureYAML.Parsing.MergeResolution.mergedPairs(from: mergeSources, localPairs: localPairs)
|
||||
return .mapping(PureYAML.Model.Mapping(pairs))
|
||||
}
|
||||
|
||||
|
||||
@ -182,7 +182,7 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
guard container.pendingKey == nil else {
|
||||
throw unexpectedEvent(expected: "mapping value", mark: mark)
|
||||
}
|
||||
let pairs = mergedPairs(
|
||||
let pairs = PureYAML.Parsing.MergeResolution.mergedPairs(
|
||||
from: container.mergeSources,
|
||||
localPairs: container.localPairs,
|
||||
)
|
||||
@ -251,7 +251,7 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
guard !node.sequenceItems.isEmpty else {
|
||||
return try values.map { value in
|
||||
guard case let .mapping(mapping) = value else {
|
||||
throw invalidMergeValue(mark: node.mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: node.mark)
|
||||
}
|
||||
return mapping.pairs
|
||||
}
|
||||
@ -259,51 +259,15 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
return try values.enumerated().map { index, value in
|
||||
let mark = node.sequenceItems[index].mark
|
||||
guard case let .mapping(mapping) = value else {
|
||||
throw invalidMergeValue(mark: mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: mark)
|
||||
}
|
||||
return mapping.pairs
|
||||
}
|
||||
case .bool, .double, .int, .null, .string:
|
||||
throw invalidMergeValue(mark: node.mark)
|
||||
throw PureYAML.Parsing.MergeResolution.invalidMergeValue(mark: node.mark)
|
||||
}
|
||||
}
|
||||
|
||||
func mergedPairs(
|
||||
from mergeSources: [[PureYAML.Model.Pair]],
|
||||
localPairs: [PureYAML.Model.Pair],
|
||||
) -> [PureYAML.Model.Pair] {
|
||||
guard !mergeSources.isEmpty else {
|
||||
return localPairs
|
||||
}
|
||||
|
||||
let localKeys = Set(localPairs.map(\.keyNode))
|
||||
var inheritedKeys = Set<PureYAML.Model.Key>()
|
||||
var inheritedPairs: [PureYAML.Model.Pair] = []
|
||||
for source in mergeSources {
|
||||
var sourceKeys = Set<PureYAML.Model.Key>()
|
||||
for pair in source {
|
||||
guard !localKeys.contains(pair.keyNode),
|
||||
!inheritedKeys.contains(pair.keyNode)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
inheritedPairs.append(pair)
|
||||
sourceKeys.insert(pair.keyNode)
|
||||
}
|
||||
inheritedKeys.formUnion(sourceKeys)
|
||||
}
|
||||
return inheritedPairs + localPairs
|
||||
}
|
||||
|
||||
func invalidMergeValue(
|
||||
mark: PureYAML.Parsing.Mark,
|
||||
) -> PureYAML.Parsing.ParseError {
|
||||
PureYAML.Parsing.ParseError.invalidMergeValue(
|
||||
line: mark.line,
|
||||
column: mark.column,
|
||||
)
|
||||
}
|
||||
|
||||
func unexpectedEvent(
|
||||
expected: String,
|
||||
mark: PureYAML.Parsing.Mark,
|
||||
|
||||
46
Sources/Parsing/MergeResolution.swift
Normal file
46
Sources/Parsing/MergeResolution.swift
Normal file
@ -0,0 +1,46 @@
|
||||
extension PureYAML.Parsing {
|
||||
/// Shared merge-key (`<<`) resolution used by both the buffered and the
|
||||
/// streaming event composers, so the override semantics live in one place.
|
||||
enum MergeResolution {
|
||||
/// Combine merge sources with local pairs.
|
||||
///
|
||||
/// Local keys win over inherited keys, and earlier merge sources win
|
||||
/// over later ones. Insertion order is inherited pairs first, then the
|
||||
/// document's own local pairs.
|
||||
static func mergedPairs(
|
||||
from mergeSources: [[PureYAML.Model.Pair]],
|
||||
localPairs: [PureYAML.Model.Pair],
|
||||
) -> [PureYAML.Model.Pair] {
|
||||
guard !mergeSources.isEmpty else {
|
||||
return localPairs
|
||||
}
|
||||
|
||||
let localKeys = Set(localPairs.map(\.keyNode))
|
||||
var inheritedKeys = Set<PureYAML.Model.Key>()
|
||||
var inheritedPairs: [PureYAML.Model.Pair] = []
|
||||
for source in mergeSources {
|
||||
var sourceKeys = Set<PureYAML.Model.Key>()
|
||||
for pair in source {
|
||||
guard !localKeys.contains(pair.keyNode),
|
||||
!inheritedKeys.contains(pair.keyNode)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
inheritedPairs.append(pair)
|
||||
sourceKeys.insert(pair.keyNode)
|
||||
}
|
||||
inheritedKeys.formUnion(sourceKeys)
|
||||
}
|
||||
return inheritedPairs + localPairs
|
||||
}
|
||||
|
||||
/// The parse error raised when a merge value is neither a mapping nor a
|
||||
/// sequence of mappings.
|
||||
static func invalidMergeValue(mark: PureYAML.Parsing.Mark) -> PureYAML.Parsing.ParseError {
|
||||
PureYAML.Parsing.ParseError.invalidMergeValue(
|
||||
line: mark.line,
|
||||
column: mark.column,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user