Directives apply to the document that follows, so the previous document
must be closed with an explicit '...' end marker before them. After an
implicit end, a '%YAML'/'%TAG'/reserved directive is now rejected instead
of being silently accepted. Also reject a '%YAML' version trailed directly
by '#' with no separating space ('%YAML 1.1#...'), since a comment needs
leading separation; libyaml accepts both.
This restores reject-side conformance the suite manifest recorded as
passing but the libyaml port had dropped (9HCY, EB22, MUS6/00, MUS6/01,
RHX7), and updates the two PureYAML gap-error fixtures whose rejection now
fires earlier and more precisely at the misplaced directive.
Refresh yaml-test-suite-manifest.yaml to version 3: whole-suite
conformance is 384/402 (up from the recorded 331); 53 cases still marked
as gaps now pass, none regress.
113 lines
4.3 KiB
Swift
113 lines
4.3 KiB
Swift
extension PureYAML.Parsing.LibYAML.Parser {
|
|
/// Port of `yaml_parser_parse_stream_start`.
|
|
mutating func parseStreamStart() throws -> Event {
|
|
let token = peekToken()
|
|
guard case .streamStart = token.type else {
|
|
throw ParserError(context: nil, problem: "did not find expected <stream-start>", mark: token.startMark)
|
|
}
|
|
state = .implicitDocumentStart
|
|
skipToken()
|
|
return .streamStart(mark: token.startMark)
|
|
}
|
|
|
|
/// Port of `yaml_parser_parse_document_start`, extended for YAML 1.2: stray
|
|
/// `...` markers are skipped, then a bare document is opened when one is allowed
|
|
/// (the first document or after an explicit `...`), an explicit document is
|
|
/// opened after directives and a `---`, or STREAM-END is emitted. libyaml only
|
|
/// allows a bare document as the very first one.
|
|
mutating func parseDocumentStart() throws -> Event {
|
|
var token = peekToken()
|
|
while case .documentEnd = token.type {
|
|
skipToken()
|
|
token = peekToken()
|
|
}
|
|
// Directives apply to the document that follows, so the previous document
|
|
// must be closed with an explicit `...` end marker before them. After an
|
|
// implicit end (`allowBareDocument` is false) a directive is an error.
|
|
if isDirective(token), !allowBareDocument {
|
|
throw ParserError(
|
|
context: nil,
|
|
problem: "directives require a preceding document end marker",
|
|
mark: token.startMark,
|
|
)
|
|
}
|
|
if allowBareDocument, !isDirectiveOrBoundary(token) {
|
|
try processDirectives()
|
|
states.append(.documentEnd)
|
|
state = .blockNode
|
|
return .documentStart(explicit: false, mark: token.startMark)
|
|
}
|
|
if case .streamEnd = token.type {
|
|
state = .end
|
|
skipToken()
|
|
return .streamEnd(mark: token.startMark)
|
|
}
|
|
return try openExplicitDocument(startMark: token.startMark)
|
|
}
|
|
|
|
/// The explicit-document branch: directives then a required `---`.
|
|
private mutating func openExplicitDocument(startMark: Mark) throws -> Event {
|
|
try processDirectives()
|
|
let token = peekToken()
|
|
guard case .documentStart = token.type else {
|
|
throw ParserError(context: nil, problem: "did not find expected <document start>", mark: token.startMark)
|
|
}
|
|
states.append(.documentEnd)
|
|
state = .documentContent
|
|
skipToken()
|
|
return .documentStart(explicit: true, mark: startMark)
|
|
}
|
|
|
|
/// Port of `yaml_parser_parse_document_content`: an empty scalar stands in for
|
|
/// a document with no node, otherwise parse the root node.
|
|
mutating func parseDocumentContent() throws -> Event {
|
|
let token = peekToken()
|
|
if isDirectiveOrBoundary(token) || isDocumentEnd(token) {
|
|
state = states.removeLast()
|
|
return processEmptyScalar(at: token.startMark)
|
|
}
|
|
return try parseNode(block: true, indentlessSequence: false)
|
|
}
|
|
|
|
/// Port of `yaml_parser_parse_document_end`: reset the per-document tag
|
|
/// directives and emit DOCUMENT-END, explicit only when `...` was present.
|
|
mutating func parseDocumentEnd() throws -> Event {
|
|
let token = peekToken()
|
|
var explicit = false
|
|
if case .documentEnd = token.type {
|
|
skipToken()
|
|
explicit = true
|
|
}
|
|
tagDirectives.removeAll()
|
|
// A bare document may follow only an explicit `...` end marker.
|
|
allowBareDocument = explicit
|
|
state = .documentStart
|
|
return .documentEnd(explicit: explicit, mark: token.startMark)
|
|
}
|
|
|
|
private func isDirectiveOrBoundary(_ token: Token) -> Bool {
|
|
switch token.type {
|
|
case .versionDirective, .tagDirective, .reservedDirective, .documentStart, .streamEnd:
|
|
true
|
|
default:
|
|
false
|
|
}
|
|
}
|
|
|
|
private func isDirective(_ token: Token) -> Bool {
|
|
switch token.type {
|
|
case .versionDirective, .tagDirective, .reservedDirective:
|
|
true
|
|
default:
|
|
false
|
|
}
|
|
}
|
|
|
|
private func isDocumentEnd(_ token: Token) -> Bool {
|
|
if case .documentEnd = token.type {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|