PureYAML/Sources/Parsing/LibYAML.Parser.Directives.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

65 lines
3.1 KiB
Swift

extension PureYAML.Parsing.LibYAML.Parser {
/// The implied tag handles, mirroring libyaml's `default_tag_directives`.
private var defaultTagDirectives: [(handle: String, prefix: String)] {
[("!", "!"), ("!!", "tag:yaml.org,2002:")]
}
/// Port of `yaml_parser_process_directives`: consume the `%YAML` and `%TAG`
/// directive tokens at the head of a document, validating the version and
/// recording the tag handles, then install the default tag handles. The
/// version is checked against a local so a duplicate `%YAML` is rejected.
mutating func processDirectives() throws {
var version: (major: Int, minor: Int)?
var token = peekToken()
while true {
if case let .versionDirective(major, minor) = token.type {
try recordVersion(major: major, minor: minor, into: &version, mark: token.startMark)
} else if case let .tagDirective(handle, prefix) = token.type {
try appendTagDirective(handle: handle, prefix: prefix, allowDuplicates: false, mark: token.startMark)
} else {
break
}
skipToken()
token = peekToken()
}
for directive in defaultTagDirectives {
try appendTagDirective(handle: directive.handle, prefix: directive.prefix, allowDuplicates: true, mark: token.startMark)
}
}
private mutating func recordVersion(major: Int, minor: Int, into version: inout (major: Int, minor: Int)?, mark: Mark) throws {
guard version == nil else {
throw ParserError(context: nil, problem: "found duplicate %YAML directive", mark: mark)
}
guard major == 1, minor == 1 || minor == 2 else {
throw ParserError(context: nil, problem: "found incompatible YAML document", mark: mark)
}
version = (major, minor)
}
/// Port of `yaml_parser_append_tag_directive`: append a handle/prefix mapping,
/// rejecting a redefinition unless duplicates are allowed (the defaults are).
mutating func appendTagDirective(handle: String, prefix: String, allowDuplicates: Bool, mark: Mark) throws {
if tagDirectives.contains(where: { $0.handle == handle }) {
if allowDuplicates {
return
}
throw ParserError(context: nil, problem: "found duplicate %TAG directive", mark: mark)
}
tagDirectives.append((handle, prefix))
}
/// Port of the tag-resolution block in `yaml_parser_parse_node`: an empty
/// handle means the suffix is a verbatim tag; otherwise substitute the handle's
/// registered prefix, failing on an undefined handle.
func resolveTag(handle: String, suffix: String, startMark _: Mark, tagMark: Mark) throws -> String {
guard !handle.isEmpty else {
return suffix
}
guard let directive = tagDirectives.first(where: { $0.handle == handle }) else {
throw ParserError(context: "while parsing a node", problem: "found undefined tag handle", mark: tagMark)
}
return directive.prefix + suffix
}
}