diff --git a/CHANGELOG.md b/CHANGELOG.md index 5743cb7..c6be472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fold over a tab-only blank line inside a multi-line plain scalar. A plain + scalar may span blank lines, and a blank line that contains a tab is still + blank, so the tab is not indentation. libyaml rejects any tab below the scalar + indentation; the scanner now rejects it only when it indents real content, not + when the rest of the line is blank. Surpasses libyaml on DK95/04. - Accept a tab as in-line separation while still rejecting it as indentation. A tab after a `-`, `?`, or `:` indicator, or before a flow collection, is valid separation, but libyaml refuses to skip it whenever a simple key is allowed and diff --git a/Sources/Parsing/LibYAML.Scanner.Fetch.swift b/Sources/Parsing/LibYAML.Scanner.Fetch.swift index 43232d6..34e239f 100644 --- a/Sources/Parsing/LibYAML.Scanner.Fetch.swift +++ b/Sources/Parsing/LibYAML.Scanner.Fetch.swift @@ -365,7 +365,7 @@ extension PureYAML.Parsing.LibYAML.Scanner { /// Whether the rest of the current line, from the current position, is only /// spaces and tabs up to a line break or end of stream (a blank line). - private func blankLineAhead() -> Bool { + func blankLineAhead() -> Bool { var offset = 0 while let character = reader.peek(offset), character == " " || Classify.isTab(character) { offset += 1 diff --git a/Sources/Parsing/LibYAML.Scanner.PlainScalar.swift b/Sources/Parsing/LibYAML.Scanner.PlainScalar.swift index 908e28d..900e14c 100644 --- a/Sources/Parsing/LibYAML.Scanner.PlainScalar.swift +++ b/Sources/Parsing/LibYAML.Scanner.PlainScalar.swift @@ -106,7 +106,10 @@ extension PureYAML.Parsing.LibYAML.Scanner { private mutating func consumePlainBreaks(_ state: inout PlainState, indent: Int, startMark: PureYAML.Parsing.Mark) throws { while Classify.isBlank(reader.peek()) || Classify.isBreak(reader.peek()) { if Classify.isBlank(reader.peek()) { - if state.leadingBlanks, reader.column < indent, Classify.isTab(reader.peek()) { + if state.leadingBlanks, reader.column < indent, Classify.isTab(reader.peek()), !blankLineAhead() { + // A tab below the indentation is illegal only when it indents + // real content; on an otherwise-blank line it is just a blank + // line the scalar folds over. libyaml rejects it either way. throw scannerError("found a tab character that violates indentation", at: startMark) } if !state.leadingBlanks, let blank = reader.peek() { diff --git a/Tests/CanonicalEventConformanceTests.swift b/Tests/CanonicalEventConformanceTests.swift index 04a8a4b..31e6b6e 100644 --- a/Tests/CanonicalEventConformanceTests.swift +++ b/Tests/CanonicalEventConformanceTests.swift @@ -12,7 +12,7 @@ struct CanonicalEventConformanceTests { /// Valid suite cases whose event stream PureYAML does NOT yet emit exactly. /// This is the authoritative gap set: a fix removes an id (and the test fails /// until it is removed), a regression adds one (and the test fails). Of these, - /// 1 currently throws on valid input and 2 emit a wrong event stream. + /// All remaining gaps emit a wrong event stream (none throw on valid input). static let knownEventGaps: Set = [ // Gap set for the ported libyaml engine. Verified against the libyaml // reference dumper: on all but four of these the port matches libyaml @@ -20,8 +20,6 @@ struct CanonicalEventConformanceTests { // bug. Surpassing libyaml means shrinking this set beyond what libyaml // itself handles. // - // false-rejects (parseEvents throws on valid input, as libyaml does) - "DK95/04", // wrong event stream: the port matches libyaml but both deviate from the // canonical events (libyaml quirks to be surpassed). "JEF9/02", "W4TN",