fix(parsing): bound a block sequence to its own column for siblings

A compact nested block sequence (`- - x`) carries no indent or dedent tokens,
only block entries at their source columns. The sibling-entry loop in
parseBlockSequence collected every following block entry by kind alone, so the
inner sequence absorbed an entry that belonged to an enclosing one: `- - x`
then `- - y` parsed as a single outer item `[[x, [y]]]` instead of two items
`[[x], [y]]`.

Record the first entry's column as the sequence's column and continue the
sibling loop only while the next entry sits at it. An entry at a smaller
column ends this sequence and is handled by the enclosing one; entries at the
same column still group, and flat sequences are unchanged.

Raises yaml-test-suite value-level conformance to 190/194 (97.9%) with the
parse-level and should-error rejection rates unchanged. 460 tests pass.
This commit is contained in:
Mihaela M 2026-06-28 22:37:36 +02:00
parent 2b3d95d214
commit a33be7c37a
3 changed files with 49 additions and 1 deletions

View File

@ -84,6 +84,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Compact nested block sequences (`- - x`) now separate their outer items correctly. Such a sequence
carries no indent/dedent tokens, only block entries at their columns, but the sibling-entry loop
collected every following block entry regardless of column, so `- - x` then `- - y` became one
outer item holding both rather than two. A sequence now collects siblings only at its own column, so
an entry at a smaller column ends it and belongs to the enclosing sequence. Raises yaml-test-suite
value-level conformance to 190/194 (97.9%), with the parse and should-error rates unchanged.
- A block scalar whose only lines are blank now carries those blank lines through chomping at stream
end. Such a scalar never establishes a content indentation, so its blank lines stayed buffered and
were dropped, making `keep: |+` over a blank line resolve to the empty string instead of a line

View File

@ -5,6 +5,10 @@ extension PureYAML.Parsing.TokenEventParser {
) throws -> PureYAML.Parsing.NodeResult {
let start = try expect("block sequence entry") { $0.isBlockEntry }
try enforceBlockSequenceNotInlineAfterProperty(properties, firstEntry: start)
// A compact nested sequence (`- - x`) carries no indent/dedent tokens, only block entries at
// their columns, so the sibling-entry loop must stay at this sequence's own column: a later
// entry at a smaller column belongs to an enclosing sequence and ends this one.
let sequenceColumn = start.mark.column
let startMark = properties.mark ?? start.mark
var endMark = start.endMark
try emit(.sequenceStart(
@ -21,7 +25,7 @@ extension PureYAML.Parsing.TokenEventParser {
)
try consumeSequenceItemBoundaryDedent(sequenceIndentWidth: start.mark.column - 1)
try consumeSequenceItemBoundaryIndent(sequenceIndentWidth: start.mark.column - 1)
while cursor.current?.kind.isBlockEntry == true {
while cursor.current?.kind.isBlockEntry == true, cursor.current?.mark.column == sequenceColumn {
let entry = try expect("block sequence entry") { $0.isBlockEntry }
try parseBlockSequenceItem(
after: entry,

View File

@ -0,0 +1,37 @@
@testable import PureYAML
import Testing
/// A compact nested block sequence (`- - x`) carries no indent/dedent tokens, only block entries at
/// their columns. A sequence collects sibling items only at its own column, so a later entry at a
/// smaller column ends it and belongs to the enclosing sequence: `- - x` then `- - y` is `[[x], [y]]`,
/// two outer items, not one outer item holding both.
@Suite("Compact nested sequence")
struct CompactNestedSequenceTests {
@Test("Two compact nested entries are two outer items")
func test_twoNestedItems() throws {
#expect(try PureYAML.parse("- - x\n- - y\n") == .sequence([
.sequence([.string("x")]),
.sequence([.string("y")]),
]))
}
@Test("Three levels of compact nesting separate at the outer column")
func test_threeLevels() throws {
#expect(try PureYAML.parse("- - - []\n- - - {}\n") == .sequence([
.sequence([.sequence([.sequence([])])]),
.sequence([.sequence([.mapping(.init([]))])]),
]))
}
@Test("Entries at the same column stay one sequence")
func test_sameColumnSiblings() throws {
#expect(try PureYAML.parse("- - a\n - b\n") == .sequence([
.sequence([.string("a"), .string("b")]),
]))
}
@Test("A flat sequence is unchanged")
func test_flatSequence() throws {
#expect(try PureYAML.parse("- a\n- b\n") == .sequence([.string("a"), .string("b")]))
}
}