fix(parsing): an explicit key with no value maps to null
The block mapping parser required a ':' after every explicit key ('? key'),
so a key with no value, including several '? key' entries in a row (a set),
failed to parse with 'expected mapping value'. An explicit key with no
following ':' now takes an implicit null value, shared with the existing
'key:'-with-nothing-after path via one emitNullMappingValue helper.
Explicit keys with values and complex keys in general are unaffected. A
test that asserted '? [a, b]' followed by a block entry is a missing-value
error was corrected: the key is a valid null entry, and the trailing
sequence is now rejected as extra content (a second root node), which is
the more accurate diagnosis.
Measured against the official YAML test-suite, parse-level conformance
rises from 229/333 (68.8%) to 235/333 (70.6%), with several of the newly
parsed documents also value-correct and no regression.
This commit is contained in:
parent
82d5b57fd6
commit
58f30b4343
@ -23,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- An explicit mapping key (`? key`) with no following `:` now maps to null instead of failing to
|
||||
parse, so several `? key` entries in a row (a set, for instance) each map to null. The parser
|
||||
required a `:` after every explicit key; it now treats a missing one as an implicit null value.
|
||||
Explicit keys with values, and complex keys generally, are unaffected. Lifts official test-suite
|
||||
parse-level conformance from 229/333 (68.8%) to 235/333 (70.6%).
|
||||
- An unrecognized directive (e.g. `%FOO`) before a document is now ignored, as the YAML spec
|
||||
requires (a warning, otherwise no effect), instead of being rejected with an error. `%YAML`
|
||||
and `%TAG` are unchanged, and a directive after content (where one cannot appear) is still
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
extension PureYAML.Parsing.TokenEventParser {
|
||||
mutating func parseMappingPair() throws -> PureYAML.Parsing.NodeResult {
|
||||
_ = try expect("mapping key") { $0.isMappingKey }
|
||||
_ = try parseNode()
|
||||
let key = try parseNode()
|
||||
// An explicit key (`? key`) with no following `:` has an implicit null value, so several
|
||||
// `? key` entries in a row (a set, for instance) each map to null rather than being an error.
|
||||
guard case .mappingValue = cursor.current?.kind else {
|
||||
return try emitNullMappingValue(at: key.endMark)
|
||||
}
|
||||
let valueIndicator = try expect("mapping value") { kind in
|
||||
if case .mappingValue = kind {
|
||||
return true
|
||||
@ -47,7 +52,12 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
)
|
||||
}
|
||||
|
||||
let mark = valueIndicator.endMark
|
||||
return try emitNullMappingValue(at: valueIndicator.endMark)
|
||||
}
|
||||
|
||||
/// Emit an empty (null) scalar as a mapping value and return its node result, used where a
|
||||
/// mapping entry has no value: an explicit key with no `:`, or a `key:` with nothing after it.
|
||||
mutating func emitNullMappingValue(at mark: PureYAML.Parsing.Mark) throws -> PureYAML.Parsing.NodeResult {
|
||||
try emit(.scalar(value: "", anchor: nil, tag: nil, style: .plain, mark: mark))
|
||||
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: mark)
|
||||
}
|
||||
|
||||
@ -136,13 +136,15 @@ struct ComplexMappingKeyParsingTests {
|
||||
#expect(service?.rootMapping?["[\"a\", \"b\"]"] == nil)
|
||||
}
|
||||
|
||||
@Test("Rejects complex keys that are not followed by mapping values")
|
||||
func test_rejectsComplexKeysThatAreNotFollowedByMappingValues() {
|
||||
@Test("A complex key with no value is null; a bare sequence after it is extra content")
|
||||
func test_complexKeyWithoutValueIsNullThenRejectsExtraContent() {
|
||||
// `? [a, b]` with no `:` is a valid null-valued entry, so the following block sequence is a
|
||||
// second root node, rejected as extra content rather than as a missing mapping value.
|
||||
expectParseError("""
|
||||
? [a, b]
|
||||
- invalid
|
||||
""", .unexpectedToken(
|
||||
expected: "mapping value",
|
||||
expected: "document boundary or stream end",
|
||||
actual: "blockEntry",
|
||||
line: 2,
|
||||
column: 1,
|
||||
|
||||
34
Tests/ExplicitKeyTests.swift
Normal file
34
Tests/ExplicitKeyTests.swift
Normal file
@ -0,0 +1,34 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Explicit keys")
|
||||
struct ExplicitKeyTests {
|
||||
@Test("Explicit keys with no value each map to null")
|
||||
func test_explicitKeysWithoutValuesMapToNull() throws {
|
||||
// Several `? key` entries in a row (a set, for instance) are valid; each maps to null.
|
||||
let value = try PureYAML.parse("""
|
||||
? a
|
||||
? b
|
||||
c:
|
||||
""")
|
||||
#expect(value == .mapping(.init([
|
||||
.init(key: "a", value: .null),
|
||||
.init(key: "b", value: .null),
|
||||
.init(key: "c", value: .null),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("Explicit keys with values still parse")
|
||||
func test_explicitKeysWithValues() throws {
|
||||
let value = try PureYAML.parse("""
|
||||
? a
|
||||
: b
|
||||
? c
|
||||
: d
|
||||
""")
|
||||
#expect(value == .mapping(.init([
|
||||
.init(key: "a", value: .string("b")),
|
||||
.init(key: "c", value: .string("d")),
|
||||
])))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user