Add parseLocated / parseStreamLocated, returning a Model.LocatedValue tree parallel in shape to Value, where every node carries its source span (1-based line/column) and located children. Collections span from their opening to closing event; scalars carry a start-anchored span; aliases and merge-inherited pairs honestly point at their reference site. The plain Value, its equality, and the conformance oracle are untouched: locations are built only when the streaming composer is asked for them (tracksLocations), behind new entry points, so the default value path is byte-identical. A whole-suite test proves the located projection equals a plain parse for every passing case (402/402), including through merge keys and aliases, and the canonical-event and gap gates stay green.
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)
|
|
}
|
|
}
|
|
}
|