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).
59 lines
2.7 KiB
Swift
59 lines
2.7 KiB
Swift
public extension PureYAML.Model {
|
|
/// A value tree annotated with source spans, parallel in shape to ``Value``: every node
|
|
/// carries its own ``SourceRange`` and its located children. It is produced by the opt-in
|
|
/// ``PureYAML/parseLocated(_:)`` and ``PureYAML/parseStreamLocated(_:)`` entry points; the
|
|
/// plain ``Value`` API is unchanged, so source positions never affect value equality, the
|
|
/// conformance oracle, or any existing consumer.
|
|
///
|
|
/// `value` projects the position-free ``Value`` (identical to what a plain parse returns),
|
|
/// and `range` is the node's own span, so a consumer can keep operating on ``Value`` and
|
|
/// consult the located tree only when it needs a location.
|
|
indirect enum LocatedValue: Equatable, Sendable {
|
|
/// A leaf scalar (`null`, `bool`, `int`, `double`, or `string`) with its start-anchored span.
|
|
case scalar(Value, SourceRange)
|
|
/// A sequence: its located items and the span from its opening to its closing.
|
|
case sequence([LocatedValue], SourceRange)
|
|
/// A mapping: its located pairs and the span from its opening to its closing.
|
|
case mapping([LocatedPair], SourceRange)
|
|
}
|
|
}
|
|
|
|
public extension PureYAML.Model.LocatedValue {
|
|
/// The span of this node.
|
|
var range: PureYAML.Model.SourceRange {
|
|
switch self {
|
|
case let .scalar(_, range), let .sequence(_, range), let .mapping(_, range):
|
|
range
|
|
}
|
|
}
|
|
|
|
/// The position-free value this located node projects to, identical to a plain parse.
|
|
var value: PureYAML.Model.Value {
|
|
switch self {
|
|
case let .scalar(value, _):
|
|
value
|
|
case let .sequence(items, _):
|
|
.sequence(items.map(\.value))
|
|
case let .mapping(pairs, _):
|
|
.mapping(PureYAML.Model.Mapping(pairs.map(\.pair)))
|
|
}
|
|
}
|
|
|
|
/// Wrap a plain ``Value`` so every node shares one span. Used where a real per-node span is
|
|
/// not available (an alias expansion or a merge-inherited pair, whose content is defined
|
|
/// elsewhere): the whole subtree honestly points at its single reference site.
|
|
static func synthesized(from value: PureYAML.Model.Value, at range: PureYAML.Model.SourceRange) -> Self {
|
|
switch value {
|
|
case .null, .bool, .int, .double, .string:
|
|
return .scalar(value, range)
|
|
case let .sequence(items):
|
|
return .sequence(items.map { synthesized(from: $0, at: range) }, range)
|
|
case let .mapping(mapping):
|
|
let pairs = mapping.pairs.map {
|
|
PureYAML.Model.LocatedPair(keyNode: $0.keyNode, keyRange: range, value: synthesized(from: $0.value, at: range))
|
|
}
|
|
return .mapping(pairs, range)
|
|
}
|
|
}
|
|
}
|