fix(parsing): reject a tab where a token must start, add port differential
The scanner now errors with "found character that cannot start any token" when a tab leads a token in block context, matching libyaml's final guard in fetch_next_token (a plain scalar may not begin with a blank). Adds a differential harness (env-gated) that dumps the ported pipeline's yeast events for the whole suite. Verified against the libyaml reference dumper across all 308 vendored valid cases: the port is byte-for-byte identical to libyaml on 307, and the single difference (L24T/01) is the port matching the canonical test.event where libyaml drops a trailing line break. 333 tests.
This commit is contained in:
parent
521062d974
commit
6eddb64dcd
@ -102,6 +102,16 @@ extension PureYAML.Parsing.LibYAML.Scanner {
|
||||
case ":" where flowLevel > 0 || Classify.isBlankZ(reader.peek(1)):
|
||||
try fetchValue()
|
||||
default:
|
||||
// A plain scalar may begin with any non-blank character; a blank (a
|
||||
// tab here, since spaces and breaks were already consumed) cannot
|
||||
// start a token, matching libyaml's final guard in fetch_next_token.
|
||||
guard !Classify.isBlankZ(character) else {
|
||||
throw ScannerError(
|
||||
context: "while scanning for the next token",
|
||||
problem: "found character that cannot start any token",
|
||||
mark: reader.mark,
|
||||
)
|
||||
}
|
||||
try fetchPlainScalar()
|
||||
}
|
||||
}
|
||||
|
||||
66
Tests/LibYAMLPortConformanceTests.swift
Normal file
66
Tests/LibYAMLPortConformanceTests.swift
Normal file
@ -0,0 +1,66 @@
|
||||
import Foundation
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// Differential measurement of the ported libyaml scanner+parser against the full
|
||||
/// vendored YAML test suite, using the same yeast serializer as the canonical
|
||||
/// gate. Gated by an environment flag so it only runs when measuring; it prints a
|
||||
/// score and the failing ids split into throws vs wrong-stream.
|
||||
@Suite("LibYAML Port Conformance")
|
||||
struct LibYAMLPortConformanceTests {
|
||||
@Test("measure the ported pipeline against the suite")
|
||||
func test_measure() throws {
|
||||
guard ProcessInfo.processInfo.environment["PUREYAML_PORT_MEASURE"] == "1" else { return }
|
||||
var pass = 0
|
||||
var throwing: [String] = []
|
||||
var wrong: [String] = []
|
||||
let cases = CanonicalEventCorpus.validCases()
|
||||
for testCase in cases {
|
||||
guard let yaml = testCase.input, let expected = testCase.expectedEvents else { continue }
|
||||
do {
|
||||
var parser = try PureYAML.Parsing.LibYAML.Parser(yaml)
|
||||
let actual = try parser.parse().map(CanonicalEventCorpus.serialize).joined(separator: "\n")
|
||||
if actual == expected.trimmedTrailing {
|
||||
pass += 1
|
||||
} else {
|
||||
wrong.append(testCase.id)
|
||||
}
|
||||
} catch {
|
||||
throwing.append(testCase.id)
|
||||
}
|
||||
}
|
||||
print("PORT-SCORE pass=\(pass) wrong=\(wrong.count) throws=\(throwing.count) total=\(cases.count)")
|
||||
print("PORT-WRONG \(wrong.sorted().joined(separator: " "))")
|
||||
print("PORT-THROWS \(throwing.sorted().joined(separator: " "))")
|
||||
}
|
||||
|
||||
@Test("dump port yeast for ids in PUREYAML_PORT_DUMP_IDS")
|
||||
func test_dump() throws {
|
||||
guard let ids = ProcessInfo.processInfo.environment["PUREYAML_PORT_DUMP_IDS"] else { return }
|
||||
let cases = ids == "ALL"
|
||||
? CanonicalEventCorpus.validCases().map(\.id)
|
||||
: ids.split(separator: ",").map(String.init)
|
||||
for id in cases {
|
||||
let testCase = CanonicalEventCorpus.Case(id: id)
|
||||
guard let yaml = testCase.input else { continue }
|
||||
print("@@@ID@@@ \(id)")
|
||||
do {
|
||||
var parser = try PureYAML.Parsing.LibYAML.Parser(yaml)
|
||||
try print(parser.parse().map(CanonicalEventCorpus.serialize).joined(separator: "\n"))
|
||||
} catch {
|
||||
print("@@@ERROR@@@ \(error)")
|
||||
}
|
||||
print("@@@END@@@")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
var trimmedTrailing: String {
|
||||
var copy = self
|
||||
while copy.hasSuffix("\n") {
|
||||
copy.removeLast()
|
||||
}
|
||||
return copy
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user