A flow collection followed by a same-line `: ` indicator is an implicit block
mapping key, e.g. `[a, b]: value` or `{a: 1}: value`, but the parser took the
flow node as the document's root value and then rejected the trailing `:` with
"expected document boundary".
The marker that tells the parser a node is a key is emitted before the node,
so for a flow key it must precede the flow tokens, which are already drained
by the time the `:` is seen (the streaming cursor drains after each token).
Resolve it in the scanner with a character lookahead from the flow opener to
its matching close, skipping quoted scalars so their brackets and colons do
not miscount the nesting; when a same-line `:` boundary follows, emit the same
implicit-key marker a scalar key gets, so the flow node parses as the key
through the ordinary mapping path with no parser change.
A block implicit key must lie on one line, so a flow collection spanning a
line break before its `:` (`[23 / ]: 42`) is rejected, which a first cut
wrongly accepted until the lookahead was made to stop at a line break. A flow
node that no `:` follows, or that is a mapping value, is unaffected.
Raises yaml-test-suite parse-level to 294/333 (88.3%) with the should-error
rejection rate unchanged. 416 tests pass.
55 lines
2.5 KiB
Swift
55 lines
2.5 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
/// A flow collection followed by a `: ` indicator on the same line is an implicit block mapping key,
|
|
/// e.g. `[a, b]: value` or `{a: 1}: value`. The scanner detects this by looking ahead from the flow
|
|
/// opener to the matching close and emits the same implicit-key marker a scalar key gets, so the flow
|
|
/// node is parsed as the key through the ordinary mapping path. A block implicit key must lie on one
|
|
/// line, so a flow collection spanning a line break before its `:` is not a key and stays an error.
|
|
@Suite("Flow collection as mapping key")
|
|
struct FlowCollectionAsMappingKeyTests {
|
|
@Test("A flow sequence before a colon is a mapping key")
|
|
func test_flowSequenceKey() throws {
|
|
#expect(try PureYAML.parse("[a, b]: c\n") == .mapping(.init([
|
|
.init(keyNode: .sequence([.string("a"), .string("b")]), value: .string("c")),
|
|
])))
|
|
}
|
|
|
|
@Test("A flow mapping before a colon is a mapping key")
|
|
func test_flowMappingKey() throws {
|
|
#expect(try PureYAML.parse("{a: 1}: b\n") == .mapping(.init([
|
|
.init(keyNode: .mapping(.init([.init(key: "a", value: .int(1))])), value: .string("b")),
|
|
])))
|
|
}
|
|
|
|
@Test("A colon inside a quoted scalar in the flow key does not end the lookahead early")
|
|
func test_quotedColonInsideKey() throws {
|
|
#expect(try PureYAML.parse("['x: y']: z\n") == .mapping(.init([
|
|
.init(keyNode: .sequence([.string("x: y")]), value: .string("z")),
|
|
])))
|
|
}
|
|
|
|
@Test("A flow node not followed by a colon stays an ordinary node, not a key")
|
|
func test_flowNodeWithoutColonIsNotAKey() throws {
|
|
#expect(try PureYAML.parse("[a, b, c]\n") == .sequence([.string("a"), .string("b"), .string("c")]))
|
|
#expect(try PureYAML.parse("key: [a, b]\n") == .mapping(.init([
|
|
.init(key: "key", value: .sequence([.string("a"), .string("b")])),
|
|
])))
|
|
}
|
|
|
|
@Test("A flow collection spanning a line break before its colon is not a valid key")
|
|
func test_multilineFlowKeyRejected() {
|
|
// The flow sequence runs across two lines, so it cannot be a single-line implicit key.
|
|
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
|
try PureYAML.parse("[23\n]: 42\n")
|
|
}
|
|
}
|
|
|
|
@Test("A colon without a following space is not a mapping value, so the flow node is not a key")
|
|
func test_colonWithoutSpaceIsNotAKey() {
|
|
#expect(throws: PureYAML.Parsing.ParseError.self) {
|
|
try PureYAML.parse("[a]:b\n")
|
|
}
|
|
}
|
|
}
|