A flow mapping entry may omit its `:` value indicator, in which case its key
maps to null: `{ a }` and `{ a, b }` are flow sets. parseFlowMappingPair
required a `:` after every key, so it rejected these entries, and rejected a
valueless key that spanned lines (`{ multi\n line, a: b}`) with them.
Parse a value only when a `:` follows the key; otherwise emit a null value for
the key. A unit test that asserted `{ a, }` was an error is corrected to expect
the set `{a: null}`, the behaviour the yaml-test-suite cases 8KB6 and 9BXH
require.
Raises yaml-test-suite parse-level conformance to 304/333 (91.3%) and
value-level to 195/197, with the should-error rejection rate unchanged.
480 tests pass.
171 lines
7.6 KiB
Swift
171 lines
7.6 KiB
Swift
extension PureYAML.Parsing.TokenEventParser {
|
|
mutating func parseFlowSequence(
|
|
properties: PureYAML.Parsing.NodeProperties,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let start = try expect("flow sequence start") { kind in
|
|
if case .flowSequenceStart = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
let startMark = properties.mark ?? start.mark
|
|
var endMark = start.endMark
|
|
try emit(.sequenceStart(
|
|
anchor: properties.anchor,
|
|
tag: properties.tag,
|
|
style: .flow,
|
|
mark: startMark,
|
|
))
|
|
var hasEntry = false
|
|
while cursor.current != nil {
|
|
if case .flowSequenceEnd = cursor.current?.kind {
|
|
let end = try expect("flow sequence end") { kind in
|
|
if case .flowSequenceEnd = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
endMark = end.endMark
|
|
try emit(.sequenceEnd(mark: endMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .sequence, endMark: endMark)
|
|
}
|
|
if case .flowEntry = cursor.current?.kind {
|
|
// A comma separates entries, so one must precede it: a leading or repeated comma
|
|
// is an empty entry, which is not allowed. A single trailing comma before the
|
|
// closing bracket is fine, since the close is handled above regardless of `hasEntry`.
|
|
guard hasEntry else {
|
|
throw unexpectedToken(expected: "flow sequence entry or flow sequence end")
|
|
}
|
|
hasEntry = false
|
|
_ = try cursor.advance()
|
|
continue
|
|
}
|
|
// A flow sequence element that begins with `:` is a single-pair mapping with an empty
|
|
// key, e.g. `[ : value ]`; an element that begins with a node and is then followed by `:`
|
|
// already becomes a mapping through the scalar key's implicit-key marker (`[ a: b ]`).
|
|
if case .mappingValue = cursor.current?.kind {
|
|
endMark = try parseEmptyKeyFlowMappingElement().endMark
|
|
} else {
|
|
endMark = try parseNode().endMark
|
|
}
|
|
hasEntry = true
|
|
if cursor.current?.kind.isFlowTerminator == false {
|
|
throw unexpectedToken(expected: "flow entry or flow sequence end")
|
|
}
|
|
}
|
|
throw unexpectedToken(expected: "flow sequence end")
|
|
}
|
|
|
|
/// Emits a single-pair flow mapping with an empty (null) key for a flow sequence element that
|
|
/// begins with `:`. The value follows the `:` and may itself be empty before the next separator.
|
|
mutating func parseEmptyKeyFlowMappingElement() throws -> PureYAML.Parsing.NodeResult {
|
|
let mark = cursor.current?.mark ?? .start
|
|
try emit(.mappingStart(anchor: nil, tag: nil, style: .flow, mark: mark))
|
|
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark))
|
|
let valueIndicator = try expect("mapping value") { kind in
|
|
if case .mappingValue = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
let properties = try consumeProperties()
|
|
let endMark: PureYAML.Parsing.Mark
|
|
if properties == .none, cursor.current?.kind.isFlowTerminator != false {
|
|
endMark = valueIndicator.endMark
|
|
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: endMark))
|
|
} else {
|
|
endMark = try parseNode(properties: properties).endMark
|
|
}
|
|
try emit(.mappingEnd(mark: endMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: endMark)
|
|
}
|
|
|
|
mutating func parseFlowMapping(
|
|
properties: PureYAML.Parsing.NodeProperties,
|
|
) throws -> PureYAML.Parsing.NodeResult {
|
|
let start = try expect("flow mapping start") { kind in
|
|
if case .flowMappingStart = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
let startMark = properties.mark ?? start.mark
|
|
var endMark = start.endMark
|
|
try emit(.mappingStart(
|
|
anchor: properties.anchor,
|
|
tag: properties.tag,
|
|
style: .flow,
|
|
mark: startMark,
|
|
))
|
|
var hasEntry = false
|
|
while cursor.current != nil {
|
|
if case .flowMappingEnd = cursor.current?.kind {
|
|
let end = try expect("flow mapping end") { kind in
|
|
if case .flowMappingEnd = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
endMark = end.endMark
|
|
try emit(.mappingEnd(mark: endMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .mapping, endMark: endMark)
|
|
}
|
|
if case .flowEntry = cursor.current?.kind {
|
|
// As in a flow sequence, a comma separates entries and so requires one before it;
|
|
// a leading or repeated comma is an empty entry. A trailing comma is fine.
|
|
guard hasEntry else {
|
|
throw unexpectedToken(expected: "flow mapping entry or flow mapping end")
|
|
}
|
|
hasEntry = false
|
|
_ = try cursor.advance()
|
|
continue
|
|
}
|
|
endMark = try parseFlowMappingPair().endMark
|
|
hasEntry = true
|
|
if cursor.current?.kind.isFlowTerminator == false {
|
|
throw unexpectedToken(expected: "flow entry or flow mapping end")
|
|
}
|
|
}
|
|
throw unexpectedToken(expected: "flow mapping end")
|
|
}
|
|
|
|
mutating func parseFlowMappingPair() throws -> PureYAML.Parsing.NodeResult {
|
|
if cursor.current?.kind.isMappingKey == true {
|
|
return try parseMappingPair()
|
|
}
|
|
|
|
// A flow mapping key may be empty (`{ : v }`) or carry only properties (`{ !!str : v }`):
|
|
// both resolve to a null key, so emit one rather than requiring a node before the `:`. This is
|
|
// valid only in flow mapping key position, so the empty-node allowance stays local here.
|
|
let keyProperties = try consumeProperties()
|
|
let keyEndMark: PureYAML.Parsing.Mark
|
|
if case .mappingValue = cursor.current?.kind {
|
|
let mark = keyProperties.mark ?? cursor.current?.mark ?? .start
|
|
try emit(.scalar(value: "", anchor: keyProperties.anchor, tag: keyProperties.tag, style: .plain, mark: mark))
|
|
keyEndMark = mark
|
|
} else {
|
|
keyEndMark = try parseNode(properties: keyProperties).endMark
|
|
}
|
|
// A flow mapping entry may omit its `:` value indicator: `{ a }` and `{ a, b }` are sets whose
|
|
// keys map to null. A value parses only when a `:` follows the key.
|
|
guard case .mappingValue = cursor.current?.kind else {
|
|
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: keyEndMark))
|
|
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: keyEndMark)
|
|
}
|
|
let valueIndicator = try expect("mapping value") { kind in
|
|
if case .mappingValue = kind {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
let properties = try consumeProperties()
|
|
guard properties != .none || cursor.current?.kind.isFlowTerminator == false else {
|
|
let mark = valueIndicator.endMark
|
|
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark))
|
|
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: mark)
|
|
}
|
|
return try parseNode(properties: properties)
|
|
}
|
|
}
|