PureYAML/Sources/Parsing/LibYAML.Parser.Document.swift
Mihaela Mihaljevic 521062d974 feat(parsing): port libyaml parser state machine to Swift
Ports parser.c: the one-event-at-a-time state machine that turns the
scanner's token stream into the YAML event stream. Covers stream and
document framing, directive processing with %TAG handle resolution and
default handles, the node property/tag/anchor production, block and
indentless sequences, block mappings, and flow sequences and mappings
including the single-pair `?` mapping forms.

Output verified against the libyaml reference event dumper: the empty
mapping value, %TAG resolution, and shorthand tags all match byte for
byte. 331 tests, no regression.
2026-06-24 17:05:59 +02:00

92 lines
3.4 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`. Skips stray document-end markers
/// before an explicit document, opens an implicit document on a bare node,
/// opens an explicit document after directives and a `---`, or emits STREAM-END.
mutating func parseDocumentStart(implicit: Bool) throws -> Event {
var token = peekToken()
if !implicit {
while case .documentEnd = token.type {
skipToken()
token = peekToken()
}
}
if implicit, !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()
state = .documentStart
return .documentEnd(explicit: explicit, mark: token.startMark)
}
private func isDirectiveOrBoundary(_ token: Token) -> Bool {
switch token.type {
case .versionDirective, .tagDirective, .documentStart, .streamEnd:
true
default:
false
}
}
private func isDocumentEnd(_ token: Token) -> Bool {
if case .documentEnd = token.type {
return true
}
return false
}
}