PureYAML/Sources/Parsing/LibYAML.Parser.Node.swift
Mihaela Mihaljevic f7500400ee 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).
2026-06-26 02:25:01 +02:00

100 lines
4.7 KiB
Swift

extension PureYAML.Parsing.LibYAML.Parser {
typealias NodeProperties = PureYAML.Parsing.LibYAML.NodeProperties
/// Port of `yaml_parser_parse_node`. An ALIAS is emitted directly; otherwise
/// the node's anchor/tag properties are consumed, the tag is resolved, and the
/// content token selects a scalar, a flow/block collection start, or an empty
/// scalar standing in for a property-only node.
mutating func parseNode(block: Bool, indentlessSequence: Bool) throws -> Event {
let token = peekToken()
if case let .alias(name) = token.type {
state = states.removeLast()
skipToken()
return .alias(anchor: name, mark: token.startMark)
}
let props = consumeNodeProperties()
let tag = try props.tagHandle.map {
try resolveTag(handle: $0, suffix: props.tagSuffix ?? "", startMark: props.startMark, tagMark: props.tagMark)
}
return try dispatchNodeContent(block: block, indentlessSequence: indentlessSequence, props: props, tag: tag)
}
/// Consume `properties ::= TAG ANCHOR? | ANCHOR TAG?` ahead of the content.
private mutating func consumeNodeProperties() -> NodeProperties {
var token = peekToken()
var props = NodeProperties(anchor: nil, tagHandle: nil, tagSuffix: nil, startMark: token.startMark, tagMark: token.startMark)
if case let .anchor(name) = token.type {
props.anchor = name
skipToken()
token = peekToken()
if case let .tag(handle, suffix) = token.type {
props.tagHandle = handle
props.tagSuffix = suffix
props.tagMark = token.startMark
skipToken()
}
} else if case let .tag(handle, suffix) = token.type {
props.tagHandle = handle
props.tagSuffix = suffix
props.tagMark = token.startMark
skipToken()
token = peekToken()
if case let .anchor(name) = token.type {
props.anchor = name
skipToken()
}
}
return props
}
/// Select the event for the content token following a node's properties.
private mutating func dispatchNodeContent(block: Bool, indentlessSequence: Bool, props: NodeProperties, tag: String?) throws -> Event {
let token = peekToken()
if indentlessSequence, case .blockEntry = token.type {
state = .indentlessSequenceEntry
return .sequenceStart(anchor: props.anchor, tag: tag, style: .block, mark: props.startMark)
}
if let event = contentEvent(token: token, block: block, props: props, tag: tag) {
return event
}
return try emptyNodeOrError(token: token, block: block, props: props, tag: tag)
}
/// A scalar or a flow/block collection start, or nil if the token is neither.
private mutating func contentEvent(token: Token, block: Bool, props: NodeProperties, tag: String?) -> Event? {
switch token.type {
case let .scalar(value, style):
state = states.removeLast()
skipToken()
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)
case .flowMappingStart:
state = .flowMappingFirstKey
return .mappingStart(anchor: props.anchor, tag: tag, style: .flow, mark: props.startMark)
case .blockSequenceStart where block:
state = .blockSequenceFirstEntry
return .sequenceStart(anchor: props.anchor, tag: tag, style: .block, mark: props.startMark)
case .blockMappingStart where block:
state = .blockMappingFirstKey
return .mappingStart(anchor: props.anchor, tag: tag, style: .block, mark: props.startMark)
default:
return nil
}
}
/// An empty scalar when the node carried only properties, else a parse error.
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, endMark: props.startMark)
}
throw ParserError(
context: block ? "while parsing a block node" : "while parsing a flow node",
problem: "did not find expected node content",
mark: token.startMark,
)
}
}