Port the located value tree onto the hand-rolled engine, reusing the
engine-agnostic located layer (Model.LocatedValue, LocatedPair,
SourcePosition, SourceRange, and the located composer helper) authored
on the conformance branch. The streaming composer now builds a parallel
located tree behind an opt-in tracksLocations flag, exposed as
parseLocated / parseStreamLocated; the plain Value path stays
byte-identical, gated behind the flag.
The scalar event carries the scanner token's end mark, so a located
scalar spans the whole token (end exclusive, one past the last
character) rather than being start-anchored. The hand-rolled scanner
already tracked token end marks, so this surfaces information the engine
was already computing.
Also stop emitting a spurious null document for a comment-only or empty
stream: YAML 1.2 gives such a stream zero documents, while the
single-document value API still maps it to null, so parse("") is
unchanged.
A whole-suite test proves the located projection equals a plain parse
for every passing case; the value, event-dump, and conformance suites
stay green (507 tests).
64 lines
2.6 KiB
Swift
64 lines
2.6 KiB
Swift
extension PureYAML.Parsing.EventStreamComposer {
|
|
func range(at mark: PureYAML.Parsing.Mark) -> PureYAML.Model.SourceRange {
|
|
.init(at: .init(line: mark.line, column: mark.column))
|
|
}
|
|
|
|
func range(start: PureYAML.Parsing.Mark, end: PureYAML.Parsing.Mark) -> PureYAML.Model.SourceRange {
|
|
.init(start: .init(line: start.line, column: start.column), end: .init(line: end.line, column: end.column))
|
|
}
|
|
|
|
/// The node's located projection, falling back to a synthesized one at the node's mark if
|
|
/// the located field was somehow not populated (never expected while tracking locations).
|
|
func locatedNode(_ node: PureYAML.Parsing.EventStreamComposedNode) -> PureYAML.Model.LocatedValue {
|
|
node.located ?? .synthesized(from: node.value, at: range(at: node.mark))
|
|
}
|
|
|
|
/// The located pairs a merge value (`<<`) contributes, mirroring ``mergeSources(from:)``.
|
|
/// The value-side merge validation has already run, so the located shape is a mapping or a
|
|
/// sequence of mappings; anything else contributes nothing.
|
|
func locatedMergeSources(from node: PureYAML.Parsing.EventStreamComposedNode) -> [[PureYAML.Model.LocatedPair]] {
|
|
switch locatedNode(node) {
|
|
case let .mapping(pairs, _):
|
|
[pairs]
|
|
case let .sequence(items, _):
|
|
items.compactMap { item in
|
|
guard case let .mapping(pairs, _) = item else {
|
|
return nil
|
|
}
|
|
return pairs
|
|
}
|
|
case .scalar:
|
|
[]
|
|
}
|
|
}
|
|
|
|
/// The final located pairs, mirroring ``mergedPairs(from:localPairs:)`` exactly (same dedup,
|
|
/// same order) so the located mapping has the identical shape to the value mapping.
|
|
func mergedLocatedPairs(
|
|
from mergeSources: [[PureYAML.Model.LocatedPair]],
|
|
localPairs: [PureYAML.Model.LocatedPair],
|
|
) -> [PureYAML.Model.LocatedPair] {
|
|
guard !mergeSources.isEmpty else {
|
|
return localPairs
|
|
}
|
|
|
|
let localKeys = Set(localPairs.map(\.keyNode))
|
|
var inheritedKeys = Set<PureYAML.Model.Key>()
|
|
var inheritedPairs: [PureYAML.Model.LocatedPair] = []
|
|
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
|
|
}
|
|
}
|