diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd2721..3d958ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/Parsing/Event.swift b/Sources/Parsing/Event.swift index 043aaff..1d7d005 100644 --- a/Sources/Parsing/Event.swift +++ b/Sources/Parsing/Event.swift @@ -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), diff --git a/Sources/Parsing/EventComposer.swift b/Sources/Parsing/EventComposer.swift index 1c2f4af..602bcc4 100644 --- a/Sources/Parsing/EventComposer.swift +++ b/Sources/Parsing/EventComposer.swift @@ -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), diff --git a/Sources/Parsing/EventStreamComposer.swift b/Sources/Parsing/EventStreamComposer.swift index 4bf7515..8e3d34d 100644 --- a/Sources/Parsing/EventStreamComposer.swift +++ b/Sources/Parsing/EventStreamComposer.swift @@ -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, )) } diff --git a/Sources/Parsing/LibYAML.Parser.Node.swift b/Sources/Parsing/LibYAML.Parser.Node.swift index a00b790..edf9e74 100644 --- a/Sources/Parsing/LibYAML.Parser.Node.swift +++ b/Sources/Parsing/LibYAML.Parser.Node.swift @@ -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", diff --git a/Sources/Parsing/LibYAML.Parser.swift b/Sources/Parsing/LibYAML.Parser.swift index 92a3033..a84caa4 100644 --- a/Sources/Parsing/LibYAML.Parser.swift +++ b/Sources/Parsing/LibYAML.Parser.swift @@ -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) } } } diff --git a/Sources/Tagged/Tagged.EventComposer.swift b/Sources/Tagged/Tagged.EventComposer.swift index 6bd25c9..e012886 100644 --- a/Sources/Tagged/Tagged.EventComposer.swift +++ b/Sources/Tagged/Tagged.EventComposer.swift @@ -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) } diff --git a/Tests/CanonicalEventConformanceTests.swift b/Tests/CanonicalEventConformanceTests.swift index e7b919c..57e038e 100644 --- a/Tests/CanonicalEventConformanceTests.swift +++ b/Tests/CanonicalEventConformanceTests.swift @@ -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 } } diff --git a/Tests/LibYAMLParserTests.swift b/Tests/LibYAMLParserTests.swift index 343b89c..87bb659 100644 --- a/Tests/LibYAMLParserTests.swift +++ b/Tests/LibYAMLParserTests.swift @@ -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 diff --git a/Tests/LocatedValueConformanceTests.swift b/Tests/LocatedValueConformanceTests.swift index 6b59320..ab580f0 100644 --- a/Tests/LocatedValueConformanceTests.swift +++ b/Tests/LocatedValueConformanceTests.swift @@ -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] diff --git a/Tests/ParsingEventTests.swift b/Tests/ParsingEventTests.swift index 34aa49a..6d7c7b9 100644 --- a/Tests/ParsingEventTests.swift +++ b/Tests/ParsingEventTests.swift @@ -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), ]