fix(parsing): treat colon-led flow sequence element as empty-key map
A flow sequence element that begins with `:` is a single-pair mapping with an
empty key, e.g. `[ : value ]` is `[{null: value}]`, but parseFlowSequence ran
the element through parseNode, which found the leading `:` and raised
"expected a YAML node".
Detect the leading mapping-value indicator and emit a single-pair flow mapping
with a null key and the following value (itself possibly empty), the sequence
counterpart of the scalar-key element `[ a: b ]` that already becomes a
mapping through its implicit-key marker. Plain elements and scalar-key mapping
elements are untouched, and the allowance is confined to flow sequence element
position.
Raises yaml-test-suite parse-level to 298/333 (89.5%) with the should-error
rejection rate unchanged. 426 tests pass.
This commit is contained in:
parent
956f42bbb7
commit
c1610f1eec
@ -84,6 +84,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A flow sequence element that begins with `:` is now parsed as a single-pair mapping with an empty
|
||||
(null) key, e.g. `[ : value ]` is `[{null: value}]`. The parser raised "expected a YAML node" for
|
||||
the leading `:`; it now emits the empty-key mapping, mirroring the way `[ a: b ]` (a scalar key
|
||||
followed by `:`) already becomes a mapping element. Plain elements and scalar-key mapping elements
|
||||
are unchanged. Raises yaml-test-suite parse-level conformance to 298/333 (89.5%), with the
|
||||
should-error rejection rate unchanged.
|
||||
|
||||
- A flow mapping entry may now begin with its `:` indicator, giving an empty (null) key, e.g.
|
||||
`{ : value }` or `{ a: b, : c }`, or carry only properties before the `:`, e.g. `{ !!str : value }`.
|
||||
The parser required a node before every flow mapping `:` and raised "expected a YAML node"; it now
|
||||
|
||||
@ -40,7 +40,14 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
_ = try cursor.advance()
|
||||
continue
|
||||
}
|
||||
endMark = try parseNode().endMark
|
||||
// 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")
|
||||
@ -49,6 +56,30 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
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 {
|
||||
|
||||
42
Tests/FlowSequenceEmptyKeyTests.swift
Normal file
42
Tests/FlowSequenceEmptyKeyTests.swift
Normal file
@ -0,0 +1,42 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// A flow sequence element that begins with `:` is a single-pair mapping with an empty (null) key,
|
||||
/// e.g. `[ : value ]` is `[{null: value}]`. An element that begins with a node followed by `:` already
|
||||
/// becomes a mapping through the scalar key's implicit-key marker (`[ a: b ]`), and a plain element is
|
||||
/// unaffected.
|
||||
@Suite("Flow sequence empty key")
|
||||
struct FlowSequenceEmptyKeyTests {
|
||||
@Test("An element beginning with a colon is a single-pair mapping with an empty key")
|
||||
func test_emptyKeyElement() throws {
|
||||
#expect(try PureYAML.parse("[ : x ]\n") == .sequence([
|
||||
.mapping(.init([.init(keyNode: .string(""), value: .string("x"))])),
|
||||
]))
|
||||
}
|
||||
|
||||
@Test("The empty key works with no space and an empty value")
|
||||
func test_noSpaceAndEmptyValue() throws {
|
||||
#expect(try PureYAML.parse("[: y]\n") == .sequence([
|
||||
.mapping(.init([.init(keyNode: .string(""), value: .string("y"))])),
|
||||
]))
|
||||
#expect(try PureYAML.parse("[ : ]\n") == .sequence([
|
||||
.mapping(.init([.init(keyNode: .string(""), value: .null)])),
|
||||
]))
|
||||
}
|
||||
|
||||
@Test("An empty-key element sits alongside ordinary mapping and scalar elements")
|
||||
func test_mixedElements() throws {
|
||||
#expect(try PureYAML.parse("[a: b, : c]\n") == .sequence([
|
||||
.mapping(.init([.init(key: "a", value: .string("b"))])),
|
||||
.mapping(.init([.init(keyNode: .string(""), value: .string("c"))])),
|
||||
]))
|
||||
}
|
||||
|
||||
@Test("Plain elements and scalar-key mapping elements are unchanged")
|
||||
func test_unaffected() throws {
|
||||
#expect(try PureYAML.parse("[a, b]\n") == .sequence([.string("a"), .string("b")]))
|
||||
#expect(try PureYAML.parse("[a: b]\n") == .sequence([
|
||||
.mapping(.init([.init(key: "a", value: .string("b"))])),
|
||||
]))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user