A directive the processor does not recognize (e.g. %FOO) before a document is now ignored, matching the YAML spec (emit a warning, otherwise no effect), rather than failing the whole document with an unsupportedDirective error. %YAML and %TAG handling is unchanged, and a directive after content, where a directive cannot legally appear, is still rejected via the existing guard. Two tests and a fixture that pinned the old reject-everything behavior are updated: the unknown-directive case moves from the unsupported-gaps list, and the scanner-error test now exercises a directive-after-content case (still an error) rather than an unknown directive before content. Measured against the official YAML test-suite, parse-level conformance rises from 227/333 (68.2%) to 229/333 (68.8%), with the two newly-parsed documents also value-correct and no regression.
99 lines
3.8 KiB
Swift
99 lines
3.8 KiB
Swift
extension PureYAML.Parsing.Scanner {
|
|
func scanDirectiveOrDocumentMarker(_ state: inout State) throws -> Bool {
|
|
guard state.indentation.last == 0 else {
|
|
return false
|
|
}
|
|
if state.reader.peek() == "%" {
|
|
try scanDirective(&state)
|
|
return true
|
|
}
|
|
if state.reader.peek() == "#" {
|
|
return false
|
|
}
|
|
if startsWith("---", state.reader), isIndicatorBoundary(state.reader.peek(offset: 3)) {
|
|
scanDocumentMarker(.documentStart, state: &state)
|
|
resetForDocumentStart(&state)
|
|
return true
|
|
}
|
|
if startsWith("...", state.reader), isIndicatorBoundary(state.reader.peek(offset: 3)) {
|
|
guard !state.isDocumentClosed else {
|
|
throw PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: state.reader.mark.line)
|
|
}
|
|
scanDocumentMarker(.documentEnd, state: &state)
|
|
resetForDocumentEnd(&state)
|
|
return true
|
|
}
|
|
guard !state.isDocumentClosed else {
|
|
throw PureYAML.Parsing.ParseError.unsupportedMultiDocumentStream(line: state.reader.mark.line)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func scanDocumentMarker(
|
|
_ kind: PureYAML.Parsing.TokenKind,
|
|
state: inout State,
|
|
) {
|
|
let start = state.reader.mark
|
|
state.reader.advance()
|
|
state.reader.advance()
|
|
state.reader.advance()
|
|
state.append(kind, mark: start, endMark: state.reader.mark)
|
|
// The document's root node may begin on the marker's own line (e.g. `--- >`, `--- foo`,
|
|
// `--- {a: 1}`, `--- !!str x`). After a document start, skip only the spaces and leave any
|
|
// real content for the main loop to scan; an empty rest-of-line or a comment is consumed.
|
|
// A document end (`...`) takes no same-line node, so its line is always skipped.
|
|
if case .documentStart = kind {
|
|
while state.reader.peek() == " " {
|
|
state.reader.advance()
|
|
}
|
|
if let next = state.reader.peek(), !isLineBreak(next), next != "#" {
|
|
state.isAtLineStart = false
|
|
return
|
|
}
|
|
}
|
|
skipLine(&state)
|
|
}
|
|
|
|
func resetForDocumentStart(_ state: inout State) {
|
|
state.hasDocumentContent = false
|
|
state.hasDocumentStartMarker = true
|
|
state.isDocumentClosed = false
|
|
}
|
|
|
|
func resetForDocumentEnd(_ state: inout State) {
|
|
state.hasDocumentContent = false
|
|
state.hasDocumentStartMarker = false
|
|
state.isDocumentClosed = true
|
|
}
|
|
|
|
func scanDirective(_ state: inout State) throws {
|
|
let start = state.reader.mark
|
|
let line = state.reader.consume { character in
|
|
!isLineBreak(character)
|
|
}
|
|
let parts = line.split(separator: " ").map(String.init)
|
|
guard let name = parts.first else {
|
|
return
|
|
}
|
|
guard !state.hasDocumentContent, !state.hasDocumentStartMarker, !state.isDocumentClosed else {
|
|
throw PureYAML.Parsing.ParseError.unsupportedDirective(name: name, line: start.line)
|
|
}
|
|
switch name {
|
|
case "%YAML":
|
|
guard parts.indices.contains(1), parts[1] == "1.1" || parts[1] == "1.2" else {
|
|
throw PureYAML.Parsing.ParseError.incompatibleYAMLDirective(line: start.line)
|
|
}
|
|
case "%TAG":
|
|
guard parts.count >= 3 else {
|
|
return
|
|
}
|
|
state.tagHandles[parts[1]] = parts[2]
|
|
default:
|
|
// A YAML processor ignores a directive it does not recognize (the spec calls for a
|
|
// warning and otherwise no effect), rather than rejecting the document. The line has
|
|
// already been consumed, so there is nothing more to do.
|
|
return
|
|
}
|
|
}
|
|
}
|