fix(parsing): parse a flow mapping value on the next line
In a flow mapping the value is a flow node and may sit on any line; flow is line insensitive. parseMappingPair gated value parsing on hasNodeOnSameLine, a block-only rule, so a value after `:` on the next line was treated as null and left unconsumed, failing the whole flow mapping. Thread an inFlow flag and, in flow, parse the value as a flow node line-insensitively (only a flow terminator leaves it null), bypassing the block-only same-line and indentation branches. Pretty-printed JSON (a YAML 1.2 superset) relies on this. Against the JSON Schema Test Suite corpus this fixes all 27 files that previously failed to parse (432/432 now); the YAML test-suite (318/333, value-level 206/206) and the full suite stay green.
This commit is contained in:
parent
5e5e568e2c
commit
d1d5b1a37c
@ -131,7 +131,7 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
|
||||
mutating func parseFlowMappingPair() throws -> PureYAML.Parsing.NodeResult {
|
||||
if cursor.current?.kind.isMappingKey == true {
|
||||
return try parseMappingPair()
|
||||
return try parseMappingPair(inFlow: true)
|
||||
}
|
||||
|
||||
// A flow mapping key may be empty (`{ : v }`) or carry only properties (`{ !!str : v }`):
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
extension PureYAML.Parsing.TokenEventParser {
|
||||
mutating func parseMappingPair(
|
||||
firstKeyProperties: PureYAML.Parsing.NodeProperties = .none,
|
||||
inFlow: Bool = false,
|
||||
) throws -> PureYAML.Parsing.NodeResult {
|
||||
// A subsequent pair's key may carry an anchor or tag (`&a key: …`). The first pair's inline
|
||||
// properties are consumed when the mapping is entered and passed back in here; consuming
|
||||
@ -22,6 +23,16 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
}
|
||||
|
||||
let properties = try consumeProperties()
|
||||
if inFlow {
|
||||
// In a flow mapping the value is a flow node and may sit on any line: flow is line
|
||||
// insensitive, so the block-only same-line and indentation rules below do not apply.
|
||||
// Only a flow terminator (`,`, `}`, `]`) or the stream end leaves the value empty (null),
|
||||
// mirroring the empty-value rule in `parseFlowMappingPair` for the non-`?` key path.
|
||||
guard properties != .none || cursor.current?.kind.isFlowTerminator == false else {
|
||||
return try emitNullMappingValue(at: valueIndicator.endMark)
|
||||
}
|
||||
return try parseNode(properties: properties)
|
||||
}
|
||||
try enforceValuePropertyIndented(properties, valueIndicator: valueIndicator)
|
||||
if cursor.current?.kind.isIndent == true {
|
||||
return try parseIndentedMappingValue(properties: properties)
|
||||
|
||||
49
Tests/FlowMultilineValueTests.swift
Normal file
49
Tests/FlowMultilineValueTests.swift
Normal file
@ -0,0 +1,49 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// In a flow mapping the value is a flow node and may sit on any line: flow is line insensitive,
|
||||
/// unlike a block mapping whose value must be on the key's line or an indented block below it.
|
||||
/// A value after `:` on the next line is the value, not a null. Pretty-printed JSON (a YAML 1.2
|
||||
/// superset) relies on this throughout, which is why it must hold. Only a flow terminator leaves
|
||||
/// the value empty.
|
||||
@Suite("Flow multi-line value")
|
||||
struct FlowMultilineValueTests {
|
||||
@Test("A flow mapping value on the line after the colon is the value")
|
||||
func test_valueOnNextLine() throws {
|
||||
#expect(try PureYAML.parse("{\"a\":\n\"b\"}") == .mapping(.init([
|
||||
.init(key: "a", value: .string("b")),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("Several flow values, each on its own line, each bind to their key")
|
||||
func test_severalNextLineValues() throws {
|
||||
let yaml = "{\n\"a\":\n \"x\",\n\"b\":\n \"y\"\n}"
|
||||
#expect(try PureYAML.parse(yaml) == .mapping(.init([
|
||||
.init(key: "a", value: .string("x")),
|
||||
.init(key: "b", value: .string("y")),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("A flow value that is itself a flow node spanning lines is parsed")
|
||||
func test_nextLineValueIsFlowNode() throws {
|
||||
#expect(try PureYAML.parse("{\"a\":\n[1, 2]}") == .mapping(.init([
|
||||
.init(key: "a", value: .sequence([.int(1), .int(2)])),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("Near miss: a flow value omitted before a terminator is null, not the next entry")
|
||||
func test_emptyFlowValueStaysNull() throws {
|
||||
#expect(try PureYAML.parse("{\"a\": , \"b\": \"c\"}") == .mapping(.init([
|
||||
.init(key: "a", value: .null),
|
||||
.init(key: "b", value: .string("c")),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("Pretty-printed JSON (an array of objects with values on their own lines) parses")
|
||||
func test_prettyPrintedJSON() throws {
|
||||
let yaml = "[\n {\n \"k\":\n \"v\"\n }\n]"
|
||||
#expect(try PureYAML.parse(yaml) == .sequence([
|
||||
.mapping(.init([.init(key: "k", value: .string("v"))])),
|
||||
]))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user