fix(parsing): base an inline-mapping indent indicator on the mapping
An explicit block scalar indentation indicator is the content indentation relative to the block scalar's parent node. For an inline mapping value (`- aaa: |2`) the parent is the mapping, but a compact inline mapping never pushes its indentation onto the main stack, so the indicator was added to the stack level (0) rather than the mapping's (2). The content indentation then came out too small and the next mapping key was pulled into the scalar. Capture at the header whether the block scalar is an inline mapping value (a mapping value was seen on its line). When it is, fold the deepest applicable implicit indentation into the indicator's base; a direct sequence item (`- |1`), which has no mapping value on its line, keeps the sequence's own level, so the explicit indicators on sequence items stay correct. Raises yaml-test-suite value-level conformance to 191/194 (98.5%) with the parse-level and should-error rejection rates unchanged. 463 tests pass.
This commit is contained in:
parent
a33be7c37a
commit
8bc855d7a0
@ -84,6 +84,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- An explicit block scalar indentation indicator (`|2`, `>1`) on an inline mapping value now measures
|
||||
from the mapping's indentation. A value like `- aaa: |2` has the inline mapping as its parent, but
|
||||
that mapping sits at an implicit indentation off the main stack, so the indicator was added to the
|
||||
stack's level (0) instead of the mapping's (2), making the content indentation too small and pulling
|
||||
the next key into the scalar. The deepest applicable implicit indentation is now folded in for an
|
||||
inline mapping value, while a direct sequence item (`- |1`) keeps the sequence's own level. Raises
|
||||
yaml-test-suite value-level conformance to 191/194 (98.5%), with the parse and should-error rates
|
||||
unchanged.
|
||||
|
||||
- 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
|
||||
|
||||
@ -170,7 +170,15 @@ extension PureYAML.Parsing.Scanner {
|
||||
marks: (start: PureYAML.Parsing.Mark, end: PureYAML.Parsing.Mark),
|
||||
state: inout State,
|
||||
) throws {
|
||||
let blockScalarIndentation = current + indicator
|
||||
// The indicator is the content indentation relative to the block scalar's parent node. For an
|
||||
// inline mapping value (`- aaa: |2`), the parent mapping sits at an implicit indentation that
|
||||
// never reached the main stack, so take the deepest applicable implicit level as the base:
|
||||
// `|2` under a mapping at column 2 makes the content indentation 4. A direct sequence item
|
||||
// (`- |2`) has no such parent and keeps the stack's current level.
|
||||
let parentIndentation = state.pendingBlockScalarInlineMappingValue
|
||||
? max(current, state.validImplicitIndentation.filter { $0 <= width }.max() ?? current)
|
||||
: current
|
||||
let blockScalarIndentation = parentIndentation + indicator
|
||||
guard width >= blockScalarIndentation else {
|
||||
throw PureYAML.Parsing.ParseError.unexpectedIndentation(line: marks.end.line)
|
||||
}
|
||||
@ -252,6 +260,7 @@ extension PureYAML.Parsing.Scanner {
|
||||
state.pendingBlockScalarLeadingBlankLines = 0
|
||||
state.pendingBlockScalarMaxBlankIndent = 0
|
||||
state.pendingBlockScalarIsRootNode = false
|
||||
state.pendingBlockScalarInlineMappingValue = false
|
||||
}
|
||||
|
||||
func closeIndentation(_ state: inout State) {
|
||||
|
||||
@ -98,6 +98,11 @@ extension PureYAML.Parsing.Scanner {
|
||||
&& !state.pendingExplicitKey
|
||||
&& state.validImplicitIndentation.isEmpty
|
||||
&& state.flowDepth == 0
|
||||
// Whether this block scalar is an inline mapping value (`- key: |2`). Such a value's parent is
|
||||
// the mapping, which sits at an implicit indentation off the main stack, so an explicit
|
||||
// indicator is measured relative to it. A direct sequence item (`- |2`) has no mapping value
|
||||
// on its line and keeps the sequence's own indentation as the base.
|
||||
state.pendingBlockScalarInlineMappingValue = state.sawMappingValueOnLine && state.flowDepth == 0
|
||||
}
|
||||
|
||||
func scanBlockScalarContent(_ state: inout State) {
|
||||
|
||||
@ -64,6 +64,10 @@ extension PureYAML.Parsing.Scanner {
|
||||
/// Whether the pending block scalar is the document's own root node (no enclosing collection),
|
||||
/// so its content may begin at column 0 (the document parent's indentation being -1).
|
||||
var pendingBlockScalarIsRootNode = false
|
||||
/// Whether the pending block scalar is an inline mapping value (`- key: |2`), whose parent
|
||||
/// mapping sits at an implicit indentation off the main stack, so an explicit indentation
|
||||
/// indicator is measured relative to that level rather than the stack's.
|
||||
var pendingBlockScalarInlineMappingValue = false
|
||||
var pendingBlockScalarLeadingBlankLines = 0
|
||||
/// The widest indentation seen on a block scalar's leading blank lines (before its first
|
||||
/// content line). The spec forbids a leading empty line from being more indented than the
|
||||
|
||||
32
Tests/BlockScalarIndentIndicatorTests.swift
Normal file
32
Tests/BlockScalarIndentIndicatorTests.swift
Normal file
@ -0,0 +1,32 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// An explicit block scalar indentation indicator (`|2`, `>1`) is the content indentation relative to
|
||||
/// the block scalar's parent node. For an inline mapping value (`- aaa: |2`) the parent is the
|
||||
/// mapping, which sits at an implicit indentation off the main stack, so the indicator is measured
|
||||
/// from there. A direct sequence item (`- |1`) has no such parent and keeps the sequence's own level.
|
||||
@Suite("Block scalar indent indicator")
|
||||
struct BlockScalarIndentIndicatorTests {
|
||||
@Test("An indicator on an inline mapping value is relative to the mapping indent")
|
||||
func test_inlineMappingValue() throws {
|
||||
#expect(try PureYAML.parse("- aaa: |2\n xxx\n bbb: |\n xxx\n") == .sequence([
|
||||
.mapping(.init([
|
||||
.init(key: "aaa", value: .string("xxx\n")),
|
||||
.init(key: "bbb", value: .string("xxx\n")),
|
||||
])),
|
||||
]))
|
||||
}
|
||||
|
||||
@Test("An indicator on a root mapping value is relative to the root")
|
||||
func test_rootMappingValue() throws {
|
||||
#expect(try PureYAML.parse("aaa: |2\n xxx\n") == .mapping(.init([
|
||||
.init(key: "aaa", value: .string("xxx\n")),
|
||||
])))
|
||||
}
|
||||
|
||||
@Test("An indicator on a direct sequence item keeps the sequence base")
|
||||
func test_sequenceItem() throws {
|
||||
// `|1` over ` explicit` (indent 2) strips one space at the sequence's own base, not three.
|
||||
#expect(try PureYAML.parse("- |1\n explicit\n") == .sequence([.string(" explicit\n")]))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user