fix(parsing): parse a property-only explicit mapping key as an empty key
An explicit key that is only a node property (`? &e` then `: &a`) parsed to an `expectedNode` error. Unlike the implicit form (`&a : v`), the scanner emits no synthesized empty scalar for an explicit key, so the block parser read the key via parseNode, consumed the property, then hit the `:` value indicator with no node and threw. parseNode now treats a `:` in node position as an empty key carrying any properties, reusing emitEmptyNodeOrThrow (the node it already produced for a property at end of input) without consuming the `:`, so the mapping machinery reads the `:` as the value indicator. To stay within the cyclomatic-complexity limit, the scalar case is extracted to parseScalarNode. yaml-test-suite PW8X now passes (parse-level 314/333 to 315/333). should-error rejections and value-level matches are unchanged, and a value-level test pins the produced tree for the anchored and tagged property-only explicit keys.
This commit is contained in:
parent
8d245ae335
commit
ae29059530
@ -84,6 +84,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- An explicit mapping key that is only a node property (`? &e` then `: &a`) now parses as an empty
|
||||
key carrying that property, completing the empty-property-key work for the explicit-key form. The
|
||||
block parser rejected the `:` after a property-only explicit key with an `expectedNode` error;
|
||||
`parseNode` now treats a `:` in node position as an empty property-carrying key, the same node
|
||||
`emitEmptyNodeOrThrow` already produced for a property at end of input. yaml-test-suite PW8X now
|
||||
passes (parse-level 314/333 to 315/333); should-error rejections and value-level matches are
|
||||
unchanged.
|
||||
- A block mapping key consisting only of a node property (`&a : v`, `!!str : v`) is now parsed as an
|
||||
empty implicit key carrying that property, the block analogue of the flow `{ !!str : v }` case. The
|
||||
scanner synthesized an empty mapping key only for a bare `: v` line; a line whose sole content
|
||||
|
||||
@ -269,22 +269,40 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
includeIndentedContinuation: false,
|
||||
)
|
||||
case let .scalar(value, style):
|
||||
if try isCommentInterruptedScalar() {
|
||||
return try rejectCommentInterruptedScalar()
|
||||
}
|
||||
_ = try cursor.advance()
|
||||
let decoded = try decodeScalar(value, style: style, line: token.mark.line)
|
||||
let mark = properties.mark ?? token.mark
|
||||
try emit(.scalar(
|
||||
value: decoded,
|
||||
anchor: properties.anchor,
|
||||
tag: properties.tag,
|
||||
style: style,
|
||||
mark: mark,
|
||||
))
|
||||
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: token.endMark)
|
||||
return try parseScalarNode(value: value, style: style, token: token, properties: properties)
|
||||
case .mappingValue:
|
||||
// An explicit key that is only properties before its `:` (`? &e` then `: v`) is an empty
|
||||
// key carrying those properties. Emit the empty scalar without consuming the `:`, so the
|
||||
// mapping machinery reads it as the value indicator. With no properties this is a misplaced
|
||||
// `:`, which `emitEmptyNodeOrThrow` keeps as an error. (The implicit form `&e : v` never
|
||||
// reaches here: the scanner synthesizes its empty key as an explicit scalar token.)
|
||||
return try emitEmptyNodeOrThrow(properties: properties)
|
||||
default:
|
||||
throw PureYAML.Parsing.ParseError.expectedNode(line: token.mark.line, column: token.mark.column)
|
||||
}
|
||||
}
|
||||
|
||||
/// Emits a scalar node from a scalar token, applying any node properties. A plain scalar that the
|
||||
/// scanner split around a comment (the comment-interrupted shape) is rejected rather than emitted.
|
||||
mutating func parseScalarNode(
|
||||
value: String,
|
||||
style: PureYAML.Parsing.ScalarStyle,
|
||||
token: PureYAML.Parsing.Token,
|
||||
properties: PureYAML.Parsing.NodeProperties,
|
||||
) throws -> PureYAML.Parsing.NodeResult {
|
||||
if try isCommentInterruptedScalar() {
|
||||
return try rejectCommentInterruptedScalar()
|
||||
}
|
||||
_ = try cursor.advance()
|
||||
let decoded = try decodeScalar(value, style: style, line: token.mark.line)
|
||||
let mark = properties.mark ?? token.mark
|
||||
try emit(.scalar(
|
||||
value: decoded,
|
||||
anchor: properties.anchor,
|
||||
tag: properties.tag,
|
||||
style: style,
|
||||
mark: mark,
|
||||
))
|
||||
return PureYAML.Parsing.NodeResult(kind: .scalar, endMark: token.endMark)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,4 +31,17 @@ struct ExplicitKeyTests {
|
||||
.init(key: "c", value: .string("d")),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("An explicit key that is only a property is an empty key carrying it")
|
||||
func test_explicitPropertyOnlyKey() throws {
|
||||
// `? &e` is an anchored empty key, `: &a` an anchored empty value: an empty plain scalar
|
||||
// resolves to the empty string as a key and to null as a value, the property binding to each
|
||||
// during construction. The key is no longer rejected for having no scalar before its `:`.
|
||||
#expect(try PureYAML.parse("? &e\n: &a\n") == .mapping(.init([
|
||||
.init(keyNode: .string(""), value: .null),
|
||||
])))
|
||||
#expect(try PureYAML.parse("? !!str\n: !!null\n") == .mapping(.init([
|
||||
.init(keyNode: .string(""), value: .null),
|
||||
])))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user