PureYAML/Sources/Parsing/LibYAML.Parser.Document.swift
Mihaela Mihaljevic 0588996506 feat(parsing): run the whole library on the ported engine
Routes the value and stream APIs through the ported scanner+parser (the
event API already was), so PureYAML now parses entirely on the libyaml
port. Adds the features needed to match and exceed the previous engine:
JSON UTF-16 surrogate-pair \u escapes, bare documents after an explicit
`...`, reserved-directive handling that still requires a following `---`,
and a 64-level nesting bound enforced on the event stream so deeply nested
input fails with nestingTooDeep instead of overflowing a recursive
consumer. Parser errors are translated to the diagnostic-aware ParseError,
recovering precise codes where identifiable and a readable malformed
fallback otherwise; error-reporting fixtures are rebased onto the port.

Canonical event conformance: 268 -> 271 of 308 (7Z25, HWV9, QT73).
393 tests pass.
2026-06-24 18:02:22 +02:00

94 lines
3.6 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()
}
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 isDocumentEnd(_ token: Token) -> Bool {
if case .documentEnd = token.type {
return true
}
return false
}
}