PureYAML/Sources/Parsing/LibYAML.Parser.Flow.swift
Mihaela Mihaljevic 8092897612 fix(parsing): parse a leading ':' flow-sequence entry as an empty-key pair
A flow sequence entry that begins with ':' (e.g. '[ : v ]') is a
single-pair mapping with an empty key, which YAML 1.2 permits but libyaml
rejects by handing the leading VALUE token to its node parser.

Open the implicit single-pair mapping on a leading ':' in the
flow-sequence-entry parser, and leave the VALUE token for the mapping
value state to consume so the value is parsed rather than dropped.
Surpasses libyaml on CFD4. Port score 298 -> 299.
2026-06-24 19:07:04 +02:00

158 lines
6.2 KiB
Swift

extension PureYAML.Parsing.LibYAML.Parser {
/// Port of `yaml_parser_parse_flow_sequence_entry`. A `?` key inside a flow
/// sequence opens a single-pair flow mapping.
mutating func parseFlowSequenceEntry(first: Bool) throws -> Event {
if first {
marks.append(peekToken().startMark)
skipToken()
}
var token = peekToken()
if !isFlowSequenceEnd(token) {
if !first {
guard isFlowEntry(token) else {
throw ParserError(
context: "while parsing a flow sequence",
problem: "did not find expected ',' or ']'",
mark: marks.popLast() ?? token.startMark,
)
}
skipToken()
token = peekToken()
}
if isKey(token) {
state = .flowSequenceEntryMappingKey
skipToken()
return .mappingStart(anchor: nil, tag: nil, style: .flow, mark: token.startMark)
}
if isValue(token) {
// A leading `:` opens a single-pair mapping with an empty key
// (`[ : v ]`). Keep the VALUE token for the mapping-key state.
state = .flowSequenceEntryMappingKey
return .mappingStart(anchor: nil, tag: nil, style: .flow, mark: token.startMark)
}
if !isFlowSequenceEnd(token) {
states.append(.flowSequenceEntry)
return try parseNode(block: false, indentlessSequence: false)
}
}
state = states.removeLast()
_ = marks.popLast()
skipToken()
return .sequenceEnd(mark: token.startMark)
}
/// Port of `yaml_parser_parse_flow_sequence_entry_mapping_key`.
mutating func parseFlowSequenceEntryMappingKey() throws -> Event {
let token = peekToken()
if !isValue(token), !isFlowEntry(token), !isFlowSequenceEnd(token) {
states.append(.flowSequenceEntryMappingValue)
return try parseNode(block: false, indentlessSequence: false)
}
// An empty key: leave a VALUE token for the value state to consume (so the
// value is parsed); a closing `]` ends the pair with an empty value too.
if isValue(token) || isFlowSequenceEnd(token) {
state = .flowSequenceEntryMappingValue
return processEmptyScalar(at: token.startMark)
}
let mark = token.endMark
skipToken()
state = .flowSequenceEntryMappingValue
return processEmptyScalar(at: mark)
}
/// Port of `yaml_parser_parse_flow_sequence_entry_mapping_value`.
mutating func parseFlowSequenceEntryMappingValue() throws -> Event {
var token = peekToken()
if isValue(token) {
skipToken()
token = peekToken()
if !isFlowEntry(token), !isFlowSequenceEnd(token) {
states.append(.flowSequenceEntryMappingEnd)
return try parseNode(block: false, indentlessSequence: false)
}
}
state = .flowSequenceEntryMappingEnd
return processEmptyScalar(at: token.startMark)
}
/// Port of `yaml_parser_parse_flow_sequence_entry_mapping_end`.
mutating func parseFlowSequenceEntryMappingEnd() throws -> Event {
let token = peekToken()
state = .flowSequenceEntry
return .mappingEnd(mark: token.startMark)
}
/// Port of `yaml_parser_parse_flow_mapping_key`.
mutating func parseFlowMappingKey(first: Bool) throws -> Event {
if first {
marks.append(peekToken().startMark)
skipToken()
}
var token = peekToken()
if !isFlowMappingEnd(token) {
if !first {
guard isFlowEntry(token) else {
throw ParserError(
context: "while parsing a flow mapping",
problem: "did not find expected ',' or '}'",
mark: marks.popLast() ?? token.startMark,
)
}
skipToken()
token = peekToken()
}
if isKey(token) {
return try flowMappingKeyEvent()
}
// YAML 1.2 permits an empty (null) key in a flow mapping: a `:` with
// no preceding key denotes an entry whose key is empty. libyaml hands
// the VALUE token to `parse_node` and fails; instead emit the empty
// scalar key and let `parseFlowMappingValue` consume the indicator.
if isValue(token) {
state = .flowMappingValue
return processEmptyScalar(at: token.startMark)
}
if !isFlowMappingEnd(token) {
states.append(.flowMappingEmptyValue)
return try parseNode(block: false, indentlessSequence: false)
}
}
state = states.removeLast()
_ = marks.popLast()
skipToken()
return .mappingEnd(mark: token.startMark)
}
/// The KEY branch of `parse_flow_mapping_key`: parse the key node, or an empty
/// scalar when a value/entry/end follows the `?` immediately.
private mutating func flowMappingKeyEvent() throws -> Event {
skipToken()
let token = peekToken()
if !isValue(token), !isFlowEntry(token), !isFlowMappingEnd(token) {
states.append(.flowMappingValue)
return try parseNode(block: false, indentlessSequence: false)
}
state = .flowMappingValue
return processEmptyScalar(at: token.startMark)
}
/// Port of `yaml_parser_parse_flow_mapping_value`.
mutating func parseFlowMappingValue(empty: Bool) throws -> Event {
var token = peekToken()
if empty {
state = .flowMappingKey
return processEmptyScalar(at: token.startMark)
}
if isValue(token) {
skipToken()
token = peekToken()
if !isFlowEntry(token), !isFlowMappingEnd(token) {
states.append(.flowMappingKey)
return try parseNode(block: false, indentlessSequence: false)
}
}
state = .flowMappingKey
return processEmptyScalar(at: token.startMark)
}
}