fix(parsing): comments and over-indented blanks ending block scalars
A block scalar terminated by a less-indented comment line failed with a spurious 'unexpected indentation': once the smaller indentation ended the scalar, the comment's own column was processed as a dedent, and because no indentation level held that width the dedent raised an error. A comment's indentation does not participate in block structure, so a comment that ends a block scalar is now left for the main loop to scan, with the block scalar's own first content line (which may itself begin with '#') still treated as content. Separately, a whitespace-only line indented past a block scalar's content level lost its surplus spaces because the width was consumed before the line was recognized as blank; the surplus is now preserved as the line's content, so an over-indented blank contributes the correct value. Measured against the official YAML test-suite, parse-level conformance rises from 240/333 (72.1%) to 243/333 (73.0%) with matching value-level gains, and no regression.
This commit is contained in:
parent
ceec6cca84
commit
b80e9c1c63
@ -23,6 +23,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A block scalar followed by a less-indented comment line no longer fails with a spurious
|
||||
"unexpected indentation". When a comment ends a block scalar, its own column was processed as
|
||||
a dedent and, landing on a width no indentation level held, raised an error; a comment's
|
||||
indentation is now left out of block structure. Relatedly, a whitespace-only line indented
|
||||
past a block scalar's content level now keeps its surplus spaces as content instead of
|
||||
collapsing to empty, so such a line contributes the correct value. Lifts official test-suite
|
||||
parse-level conformance from 240/333 (72.1%) to 243/333 (73.0%) and value-level matches.
|
||||
- A quoted mapping key may now have a space before its `:` (`"key" : value`). A plain key
|
||||
consumes the space before the colon into the scalar, but a quoted key stops at its closing
|
||||
quote, leaving the space, and the implicit-key check looked only at the immediate next
|
||||
|
||||
@ -11,7 +11,10 @@ extension PureYAML.Parsing.Scanner {
|
||||
return
|
||||
}
|
||||
if state.blockScalarIndentation != nil {
|
||||
state.append(.scalar(value: "", style: .plain))
|
||||
// The prefix holds any surplus indentation past the block's content level for an
|
||||
// otherwise-empty line (see `preserveBlockScalarSurplusOnBlankLine`); empty otherwise.
|
||||
state.append(.scalar(value: state.blockScalarContentPrefix, style: .plain))
|
||||
state.blockScalarContentPrefix = ""
|
||||
} else if state.pendingBlockScalarIndentation {
|
||||
state.pendingBlockScalarLeadingBlankLines += 1
|
||||
}
|
||||
|
||||
@ -3,15 +3,27 @@ extension PureYAML.Parsing.Scanner {
|
||||
let start = state.reader.mark
|
||||
let width = try scanIndentationWidth(&state)
|
||||
guard shouldProcessIndentation(state) else {
|
||||
preserveBlockScalarSurplusOnBlankLine(width: width, state: &state)
|
||||
return
|
||||
}
|
||||
|
||||
let end = state.reader.mark
|
||||
let current = state.indentation.last ?? 0
|
||||
let wasInBlockScalar = state.blockScalarIndentation != nil
|
||||
if consumeBlockScalarContentIndentation(width: width, state: &state) {
|
||||
return
|
||||
}
|
||||
|
||||
// An active block scalar that just ended (its content was less indented this line) onto a
|
||||
// line beginning with '#' is leaving an ordinary comment. A comment's own indentation does
|
||||
// not participate in block structure, so leave it for the main loop to scan rather than
|
||||
// forcing its column to match an indentation level, which would dedent onto a width no stack
|
||||
// level holds and raise a spurious error. The guard on `wasInBlockScalar` keeps a block
|
||||
// scalar's own first content line that begins with '#' (e.g. `|-` then `# text`) as content.
|
||||
if wasInBlockScalar, state.reader.peek() == "#" {
|
||||
return
|
||||
}
|
||||
|
||||
if let indicator = state.pendingBlockScalarIndentationIndicator {
|
||||
try applyExplicitBlockScalarIndentation(
|
||||
indicator: indicator,
|
||||
@ -30,6 +42,18 @@ extension PureYAML.Parsing.Scanner {
|
||||
clearPendingBlockScalarIndentation(&state)
|
||||
}
|
||||
|
||||
/// A whitespace-only line inside an active block scalar that is indented past the block's
|
||||
/// content indentation carries the surplus spaces as content (e.g. a 3-space line in a 2-space
|
||||
/// block contributes `" "`). The width is consumed before the line is recognized as blank, so
|
||||
/// stash the surplus in the content prefix for the blank-line emission to pick up; otherwise the
|
||||
/// line collapses to empty and a space is dropped from the scalar's value.
|
||||
func preserveBlockScalarSurplusOnBlankLine(width: Int, state: inout State) {
|
||||
guard let blockScalarIndentation = state.blockScalarIndentation, width > blockScalarIndentation else {
|
||||
return
|
||||
}
|
||||
state.blockScalarContentPrefix = String(repeating: " ", count: width - blockScalarIndentation)
|
||||
}
|
||||
|
||||
func scanIndentationWidth(_ state: inout State) throws -> Int {
|
||||
var width = 0
|
||||
while let character = state.reader.peek() {
|
||||
|
||||
27
Tests/BlockScalarTrailingCommentTests.swift
Normal file
27
Tests/BlockScalarTrailingCommentTests.swift
Normal file
@ -0,0 +1,27 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
@Suite("Block scalar indentation edges")
|
||||
struct BlockScalarTrailingCommentTests {
|
||||
@Test("A comment less indented than block scalar content ends it without error")
|
||||
func test_lessIndentedTrailingCommentEndsBlockScalar() throws {
|
||||
// The `# comment` sits at column 2, below the content indent (column 3), so it ends the
|
||||
// block scalar and is an ordinary comment rather than driving a dedent onto an invalid
|
||||
// indentation level.
|
||||
#expect(try PureYAML.parse("|\n text\n # comment\n") == .string("text\n"))
|
||||
}
|
||||
|
||||
@Test("An over-indented blank line keeps its surplus spaces as content")
|
||||
func test_overIndentedBlankLineKeepsSurplusSpaces() throws {
|
||||
// The middle line carries three spaces against a two-space content indent, so one surplus
|
||||
// space is preserved as the line's content instead of collapsing to empty.
|
||||
#expect(try PureYAML.parse("|\n a\n \n b\n") == .string("a\n \nb\n"))
|
||||
}
|
||||
|
||||
@Test("A block scalar whose own content starts with '#' keeps it as literal text")
|
||||
func test_blockScalarContentStartingWithHash() throws {
|
||||
#expect(try PureYAML.parse("strip: |-\n # text\n") == .mapping(.init([
|
||||
.init(key: "strip", value: .string("# text")),
|
||||
])))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user