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 if case .reservedDirective = token.type { // A reserved directive is ignored (YAML 1.2 §6.8.1) but still // consumed here so the explicit-document path requires a `---`. } 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) } // YAML 1.2 §6.8.1: a document whose major version is 1 is parsed even when // the minor version is newer than 1.2 (with a warning), rather than // libyaml's hard rejection of any minor other than 1 or 2. guard major == 1 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 } }