feat(parsing): exact scalar end positions in the located parse
The scalar event now carries the scanner token's end mark, so a located scalar's range spans the whole token (end exclusive, one past the last character) instead of being start-anchored. This gives consumers exact highlight widths and is the prerequisite for surgical, position-based text edits. Only the scalar event case gained a field; the value tree, its equality, and the event dump (which compares value and style, not marks) are unchanged, so the canonical-event and whole-suite gates stay green (343 tests).
This commit is contained in:
parent
3f3ce310b1
commit
f7500400ee
@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- The located parse now reports an exact end position for scalars, not just a start: the
|
||||
scalar event carries the scanner token's end mark, so a `LocatedValue.scalar`'s range covers
|
||||
the whole token (end exclusive, one past the last character) instead of being start-anchored.
|
||||
Collections already spanned open to close. The event dump compares value and style, not marks,
|
||||
so conformance is unaffected; the whole-suite gates stay green.
|
||||
- Add an opt-in located parse, `PureYAML.parseLocated(_:)` and
|
||||
`PureYAML.parseStreamLocated(_:)`, returning a `Model.LocatedValue` tree parallel
|
||||
in shape to `Value` where every node carries its source span (1-based line and
|
||||
|
||||
@ -8,7 +8,7 @@ extension PureYAML.Parsing {
|
||||
case sequenceEnd(mark: Mark)
|
||||
case mappingStart(anchor: String?, tag: String?, style: CollectionStyle, mark: Mark)
|
||||
case mappingEnd(mark: Mark)
|
||||
case scalar(value: String, anchor: String?, tag: String?, style: ScalarStyle, mark: Mark)
|
||||
case scalar(value: String, anchor: String?, tag: String?, style: ScalarStyle, mark: Mark, endMark: Mark)
|
||||
case alias(anchor: String, mark: Mark)
|
||||
|
||||
var description: String {
|
||||
@ -29,7 +29,7 @@ extension PureYAML.Parsing {
|
||||
"mappingStart anchor=\(describe(anchor)) tag=\(describe(tag)) style=\(style) @\(mark)"
|
||||
case let .mappingEnd(mark):
|
||||
"mappingEnd @\(mark)"
|
||||
case let .scalar(value, anchor, tag, style, mark):
|
||||
case let .scalar(value, anchor, tag, style, mark, _):
|
||||
"scalar value=\"\(escape(value))\" anchor=\(describe(anchor)) tag=\(describe(tag)) style=\(style) @\(mark)"
|
||||
case let .alias(anchor, mark):
|
||||
"alias anchor=\(anchor) @\(mark)"
|
||||
@ -61,8 +61,9 @@ extension PureYAML.Parsing.Event {
|
||||
return .mappingStart(anchor: anchor, tag: tag, style: style, mark: bumped)
|
||||
case .mappingEnd:
|
||||
return .mappingEnd(mark: bumped)
|
||||
case let .scalar(value, anchor, tag, style, _):
|
||||
return .scalar(value: value, anchor: anchor, tag: tag, style: style, mark: bumped)
|
||||
case let .scalar(value, anchor, tag, style, _, endMark):
|
||||
let bumpedEnd = PureYAML.Parsing.Mark(line: endMark.line + 1, column: endMark.column + 1, index: endMark.index)
|
||||
return .scalar(value: value, anchor: anchor, tag: tag, style: style, mark: bumped, endMark: bumpedEnd)
|
||||
case let .alias(anchor, _):
|
||||
return .alias(anchor: anchor, mark: bumped)
|
||||
}
|
||||
@ -94,7 +95,7 @@ extension PureYAML.Parsing.Event {
|
||||
case let .alias(_, mark),
|
||||
let .mappingEnd(mark),
|
||||
let .mappingStart(_, _, _, mark),
|
||||
let .scalar(_, _, _, _, mark),
|
||||
let .scalar(_, _, _, _, mark, _),
|
||||
let .sequenceEnd(mark),
|
||||
let .sequenceStart(_, _, _, mark),
|
||||
let .streamEnd(mark),
|
||||
|
||||
@ -102,7 +102,7 @@ extension PureYAML.Parsing.EventComposer {
|
||||
let value = try composeMapping()
|
||||
store(value, anchor: anchor)
|
||||
return value
|
||||
case let .scalar(value, anchor, tag, style, mark):
|
||||
case let .scalar(value, anchor, tag, style, mark, _):
|
||||
index += 1
|
||||
let model = try composeScalar(
|
||||
value,
|
||||
@ -163,7 +163,7 @@ extension PureYAML.Parsing.EventComposer {
|
||||
guard let event = current else {
|
||||
throw unexpectedEvent(expected: "mapping key")
|
||||
}
|
||||
guard case let .scalar(value, anchor, tag, style, _) = event else {
|
||||
guard case let .scalar(value, anchor, tag, style, _, _) = event else {
|
||||
let value = try composeNode()
|
||||
return ComposedMappingKey(
|
||||
keyNode: PureYAML.Model.Key(value: value),
|
||||
|
||||
@ -68,8 +68,8 @@ extension PureYAML.Parsing {
|
||||
try startDocument(mark: mark)
|
||||
case let .documentEnd(_, mark):
|
||||
try endDocument(mark: mark)
|
||||
case let .scalar(value, anchor, tag, style, mark):
|
||||
try consumeScalar(value, anchor: anchor, tag: tag, style: style, mark: mark)
|
||||
case let .scalar(value, anchor, tag, style, mark, endMark):
|
||||
try consumeScalar(value, anchor: anchor, tag: tag, style: style, marks: (mark, endMark))
|
||||
case let .alias(anchor, mark):
|
||||
try consumeAlias(anchor, mark: mark)
|
||||
case let .sequenceStart(anchor, _, _, mark):
|
||||
@ -157,9 +157,9 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
anchor: String?,
|
||||
tag: String?,
|
||||
style: PureYAML.Parsing.ScalarStyle,
|
||||
mark: PureYAML.Parsing.Mark,
|
||||
marks: (start: PureYAML.Parsing.Mark, end: PureYAML.Parsing.Mark),
|
||||
) throws {
|
||||
let model = try scalarParser.composeScalarValue(value, tag: tag, style: style, mark: mark)
|
||||
let model = try scalarParser.composeScalarValue(value, tag: tag, style: style, mark: marks.start)
|
||||
if let anchor {
|
||||
anchors[anchor] = model
|
||||
}
|
||||
@ -171,8 +171,8 @@ private extension PureYAML.Parsing.EventStreamComposer {
|
||||
tag: scalarParser.normalizedTag(tag),
|
||||
style: style,
|
||||
),
|
||||
mark: mark,
|
||||
located: tracksLocations ? .scalar(model, range(at: mark)) : nil,
|
||||
mark: marks.start,
|
||||
located: tracksLocations ? .scalar(model, range(start: marks.start, end: marks.end)) : nil,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ extension PureYAML.Parsing.LibYAML.Parser {
|
||||
case let .scalar(value, style):
|
||||
state = states.removeLast()
|
||||
skipToken()
|
||||
return .scalar(value: value, anchor: props.anchor, tag: tag, style: style, mark: props.startMark)
|
||||
return .scalar(value: value, anchor: props.anchor, tag: tag, style: style, mark: props.startMark, endMark: token.endMark)
|
||||
case .flowSequenceStart:
|
||||
state = .flowSequenceFirstEntry
|
||||
return .sequenceStart(anchor: props.anchor, tag: tag, style: .flow, mark: props.startMark)
|
||||
@ -88,7 +88,7 @@ extension PureYAML.Parsing.LibYAML.Parser {
|
||||
private mutating func emptyNodeOrError(token: Token, block: Bool, props: NodeProperties, tag: String?) throws -> Event {
|
||||
if props.anchor != nil || tag != nil {
|
||||
state = states.removeLast()
|
||||
return .scalar(value: "", anchor: props.anchor, tag: tag, style: .plain, mark: props.startMark)
|
||||
return .scalar(value: "", anchor: props.anchor, tag: tag, style: .plain, mark: props.startMark, endMark: props.startMark)
|
||||
}
|
||||
throw ParserError(
|
||||
context: block ? "while parsing a block node" : "while parsing a flow node",
|
||||
|
||||
@ -134,7 +134,7 @@ extension PureYAML.Parsing.LibYAML {
|
||||
/// Port of `yaml_parser_process_empty_scalar`: an empty plain scalar event
|
||||
/// stands in for an absent node.
|
||||
func processEmptyScalar(at mark: Mark) -> Event {
|
||||
.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark)
|
||||
.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark, endMark: mark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ extension PureYAML.Tagged.EventComposer {
|
||||
let value = try composeMapping(tag: tag)
|
||||
store(value, anchor: anchor)
|
||||
return value
|
||||
case let .scalar(value, anchor, tag, style, mark):
|
||||
case let .scalar(value, anchor, tag, style, mark, _):
|
||||
index += 1
|
||||
let scalar = try PureYAML.Tagged.Scalar(
|
||||
rawValue: value,
|
||||
@ -166,7 +166,7 @@ extension PureYAML.Tagged.EventComposer {
|
||||
guard let event = current else {
|
||||
throw unexpectedEvent(expected: "mapping key")
|
||||
}
|
||||
guard case let .scalar(value, anchor, tag, style, mark) = event else {
|
||||
guard case let .scalar(value, anchor, tag, style, mark, _) = event else {
|
||||
let mark = event.mark
|
||||
throw PureYAML.Parsing.ParseError.expectedScalarKey(line: mark.line, column: mark.column)
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ enum CanonicalEventCorpus {
|
||||
case .sequenceEnd: "-SEQ"
|
||||
case let .mappingStart(anchor, tag, style, _): "+MAP" + (style == .flow ? " {}" : "") + props(anchor, tag)
|
||||
case .mappingEnd: "-MAP"
|
||||
case let .scalar(value, anchor, tag, style, _): "=VAL" + props(anchor, tag) + " " + styleChar(style) + escape(value)
|
||||
case let .scalar(value, anchor, tag, style, _, _): "=VAL" + props(anchor, tag) + " " + styleChar(style) + escape(value)
|
||||
case let .alias(anchor, _): "=ALI *" + anchor
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ struct LibYAMLParserTests {
|
||||
"+MAP" + (style == .flow ? " {}" : "") + properties(anchor, tag)
|
||||
case .mappingEnd:
|
||||
"-MAP"
|
||||
case let .scalar(value, anchor, tag, style, _):
|
||||
case let .scalar(value, anchor, tag, style, _, _):
|
||||
"=VAL" + properties(anchor, tag) + " " + styleChar(style) + escape(value)
|
||||
case let .alias(anchor, _):
|
||||
"=ALI *" + anchor
|
||||
|
||||
@ -42,11 +42,12 @@ struct LocatedValueConformanceTests {
|
||||
#expect(mappingRange.start.line == 1)
|
||||
#expect(mappingRange.start.column == 1)
|
||||
|
||||
// name: web -> key at 1:1, value at 1:7
|
||||
// name: web -> key at 1:1, value spanning columns 7..10 (end exclusive, one past 'web')
|
||||
let name = pairs[0]
|
||||
#expect(name.keyNode.stringValue == "name")
|
||||
#expect(name.keyRange.start == .init(line: 1, column: 1))
|
||||
#expect(name.value.range.start == .init(line: 1, column: 7))
|
||||
#expect(name.value.range.end == .init(line: 1, column: 10))
|
||||
|
||||
// port: 8080 -> key at 2:1, value at 2:7
|
||||
let port = pairs[1]
|
||||
|
||||
@ -7,7 +7,7 @@ struct ParsingEventTests {
|
||||
func test_eventModelRepresentsAliasesAnchorsTagsAndFlowStyle() {
|
||||
let mark = PureYAML.Parsing.Mark(line: 2, column: 3, index: 12)
|
||||
let events: [PureYAML.Parsing.Event] = [
|
||||
.scalar(value: "value", anchor: "root", tag: "!Thing", style: .singleQuoted, mark: mark),
|
||||
.scalar(value: "value", anchor: "root", tag: "!Thing", style: .singleQuoted, mark: mark, endMark: mark),
|
||||
.sequenceStart(anchor: "items", tag: "!seq", style: .flow, mark: mark),
|
||||
.alias(anchor: "root", mark: mark),
|
||||
]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user