fix(parsing): bind an indented property to a zero-indent sequence
A node property written on its own indented line can sit above a block sequence whose entries return to the mapping key's own indentation (`seq:` then ` &anchor` then `- a` at the key's column). The grammar allows a block-in-block sequence at its parent's indent n while the property sits at n+1, so the indent's matching dedent falls between the property and the entries. The parser consumed the property as an empty anchored value and then rejected the entries as an unexpected block entry. It now recognises the property-then- dedent-then-entry shape, consumes that dedent, and binds the property to the block sequence that follows. Fixes yaml-test-suite SKE5; raises parse-level conformance to 311/333 (93.4%) and value-level to 204/204 (100.0%), with the should-error rate unchanged.
This commit is contained in:
parent
b45b7a4b7d
commit
5528ce9aa4
@ -84,6 +84,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A node property written on its own indented line above a zero-indent block sequence (`seq:` then
|
||||
` &anchor` then `- a` back at the key's column) now binds to that sequence. The grammar lets a
|
||||
block-in-block sequence sit at its parent's indentation while its property sits one level deeper,
|
||||
so the indent's matching dedent falls between the property and the entries. The parser had read
|
||||
the property as an empty anchored value and then rejected the entries as unexpected; it now
|
||||
consumes that dedent and attaches the property to the sequence. Fixes yaml-test-suite SKE5; raises
|
||||
parse-level conformance to 311/333 (93.4%) and value-level to 204/204 (100.0%), with the
|
||||
should-error rate unchanged.
|
||||
|
||||
- An alias node may now serve as an implicit mapping key (`*a : value`). The scanner emits the
|
||||
implicit-key indicator before a scalar key followed by a `:` but did not for an alias, so the
|
||||
parser, which already accepts an alias node in key position, never entered the pair and rejected
|
||||
|
||||
@ -63,17 +63,40 @@ extension PureYAML.Parsing.TokenEventParser {
|
||||
properties: PureYAML.Parsing.NodeProperties,
|
||||
) throws -> PureYAML.Parsing.NodeResult {
|
||||
_ = try expect("indent") { $0.isIndent }
|
||||
// A node property may sit alone on its own indented line above a block sequence whose entries
|
||||
// return to the mapping key's indentation (`seq:` then ` &anchor` then `- a` back at the key's
|
||||
// column). The grammar allows a block-in-block sequence at the parent indent n while the
|
||||
// property is at n+1, so the indent's matching dedent falls between the property and the
|
||||
// entries. Consume that dedent and bind the property to the sequence rather than emitting an
|
||||
// empty anchored value and orphaning the entries.
|
||||
if let kind = cursor.current?.kind, startsNodeProperties(kind) {
|
||||
var carried = properties
|
||||
try carried.mergeUnset(from: consumeProperties())
|
||||
if cursor.current?.kind.isDedent == true, try cursor.peek(offset: 1)?.kind.isBlockEntry == true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
return try parseBlockSequence(properties: carried, allowMappingKeyTerminator: true)
|
||||
}
|
||||
let node = try parseNode(properties: carried)
|
||||
try consumeIndentedMappingValueClose()
|
||||
return node
|
||||
}
|
||||
let result: PureYAML.Parsing.NodeResult = if cursor.current?.kind.isBlockEntry == true {
|
||||
try parseBlockSequence(properties: properties, allowMappingKeyTerminator: true)
|
||||
} else {
|
||||
try parseNode(properties: properties)
|
||||
}
|
||||
try consumeIndentedMappingValueClose()
|
||||
return result
|
||||
}
|
||||
|
||||
/// Consume the dedent that closes an indented mapping value, unless the value ended on a sibling
|
||||
/// mapping key, which the surrounding mapping continues from at the same indentation.
|
||||
mutating func consumeIndentedMappingValueClose() throws {
|
||||
if cursor.current?.kind.isDedent == true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
} else if cursor.current?.kind.isMappingKey != true {
|
||||
_ = try expect("dedent") { $0.isDedent }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// A value's anchor or tag on a line below the `:` must be indented past the key. A properly
|
||||
|
||||
33
Tests/SequencePropertyIndentTests.swift
Normal file
33
Tests/SequencePropertyIndentTests.swift
Normal file
@ -0,0 +1,33 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("A node property may sit indented above a zero-indent block sequence")
|
||||
struct SequencePropertyIndentTests {
|
||||
/// yaml-test-suite SKE5: a block sequence may sit at its parent mapping key's indentation while a
|
||||
/// property written above it is more indented (`seq:` / ` &anchor` / `- a`). The property binds to
|
||||
/// the sequence, not to an empty anchored value, so the entries are not orphaned.
|
||||
@Test("An anchor indented above a zero-indent block sequence binds to the sequence")
|
||||
func test_anchorAboveZeroIndentSequence() throws {
|
||||
let mapping = try requireMapping(PureYAML.parse("---\nseq:\n &anchor\n- a\n- b\n"))
|
||||
#expect(mapping?["seq"] == .sequence([.string("a"), .string("b")]))
|
||||
}
|
||||
|
||||
/// The plain zero-indent block sequence (no property) keeps working: a sequence whose entries are
|
||||
/// at the mapping key's own column is the value of that key.
|
||||
@Test("A zero-indent block sequence without a property is unaffected")
|
||||
func test_zeroIndentSequenceWithoutProperty() throws {
|
||||
let mapping = try requireMapping(PureYAML.parse("seq:\n- a\n- b\n"))
|
||||
#expect(mapping?["seq"] == .sequence([.string("a"), .string("b")]))
|
||||
}
|
||||
|
||||
/// A property on its own indented line above an indented nested mapping still binds to that
|
||||
/// mapping, so an alias resolves to the whole mapping rather than the sequence path above.
|
||||
@Test("An anchor above an indented nested mapping still binds to the mapping")
|
||||
func test_anchorAboveNestedMapping() throws {
|
||||
let mapping = try requireMapping(PureYAML.parse("key:\n &a\n nested: v\nx: *a\n"))
|
||||
let key = try requireMapping(#require(mapping?["key"]))
|
||||
let alias = try requireMapping(#require(mapping?["x"]))
|
||||
#expect(key?["nested"] == .string("v"))
|
||||
#expect(alias?["nested"] == .string("v"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user