diff --git a/Sources/Parsing/LibYAML.Parser.Directives.swift b/Sources/Parsing/LibYAML.Parser.Directives.swift index 6e11f65..8332a4c 100644 --- a/Sources/Parsing/LibYAML.Parser.Directives.swift +++ b/Sources/Parsing/LibYAML.Parser.Directives.swift @@ -31,7 +31,10 @@ extension PureYAML.Parsing.LibYAML.Parser { guard version == nil else { throw ParserError(context: nil, problem: "found duplicate %YAML directive", mark: mark) } - guard major == 1, minor == 1 || minor == 2 else { + // 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) diff --git a/Sources/Parsing/LibYAML.Scanner.Directive.swift b/Sources/Parsing/LibYAML.Scanner.Directive.swift index 9ff5d2f..f890b42 100644 --- a/Sources/Parsing/LibYAML.Scanner.Directive.swift +++ b/Sources/Parsing/LibYAML.Scanner.Directive.swift @@ -14,29 +14,44 @@ extension PureYAML.Parsing.LibYAML.Scanner { indentEngine.unrollIndent(column: -1, mark: reader.mark) try removeSimpleKey() simpleKeyAllowed = false - let token = try scanDirective() - indentEngine.enqueue(token) + if let token = try scanDirective() { + indentEngine.enqueue(token) + } } - /// Port of `yaml_parser_scan_directive`. Eats the `%`, reads the directive name, - /// dispatches to the `%YAML` version value or the `%TAG` handle/prefix value, - /// then consumes the rest of the line including any comment. - private mutating func scanDirective() throws -> Token { + /// Port of `yaml_parser_scan_directive`, extended to YAML 1.2 §6.8.1: a `%YAML` + /// or `%TAG` directive produces its token; any other (reserved) directive is + /// ignored with a warning, producing no token, rather than libyaml's hard + /// rejection. Returns nil for the ignored case. + private mutating func scanDirective() throws -> Token? { let startMark = reader.mark reader.skip() let name = try scanDirectiveName(startMark: startMark) - let token: Token if name == "YAML" { let version = try scanVersionDirectiveValue(startMark: startMark) - token = Token(type: .versionDirective(major: version.major, minor: version.minor), startMark: startMark, endMark: reader.mark) - } else if name == "TAG" { - let value = try scanTagDirectiveValue(startMark: startMark) - token = Token(type: .tagDirective(handle: value.handle, prefix: value.prefix), startMark: startMark, endMark: reader.mark) - } else { - throw directiveError("while scanning a directive", "found unknown directive name", startMark) + let token = Token(type: .versionDirective(major: version.major, minor: version.minor), startMark: startMark, endMark: reader.mark) + try consumeDirectiveTail(startMark: startMark) + return token + } + if name == "TAG" { + let value = try scanTagDirectiveValue(startMark: startMark) + let token = Token(type: .tagDirective(handle: value.handle, prefix: value.prefix), startMark: startMark, endMark: reader.mark) + try consumeDirectiveTail(startMark: startMark) + return token + } + skipReservedDirectiveLine() + return nil + } + + /// Consume a reserved directive's parameters through the end of the line; the + /// whole line is ignored per YAML 1.2 §6.8.1. + private mutating func skipReservedDirectiveLine() { + while !Classify.isBreakZ(reader.peek()) { + reader.skip() + } + if Classify.isBreak(reader.peek()) { + reader.skipLine() } - try consumeDirectiveTail(startMark: startMark) - return token } /// Eat trailing blanks and an optional comment, require a line break, eat it. diff --git a/Tests/CanonicalEventConformanceTests.swift b/Tests/CanonicalEventConformanceTests.swift index 994aefa..e2b5e22 100644 --- a/Tests/CanonicalEventConformanceTests.swift +++ b/Tests/CanonicalEventConformanceTests.swift @@ -21,11 +21,11 @@ struct CanonicalEventConformanceTests { // itself handles. // // false-rejects (parseEvents throws on valid input, as libyaml does) - "2JQS", "2LFX", "2SXE", "4ABK", "4MUZ/00", "4MUZ/01", "4MUZ/02", "58MP", - "5MUD", "5T43", "6BCT", "6CA3", "6LVF", "6M2F", "7Z25", "8XYN", "96NN/00", - "96NN/01", "9SA2", "A2M4", "BEC7", "CFD4", "DBG4", "DK3J", "DK95/00", + "2JQS", "2SXE", "4ABK", "4MUZ/00", "4MUZ/01", "4MUZ/02", "58MP", + "5MUD", "5T43", "6BCT", "6CA3", "6M2F", "7Z25", "8XYN", "96NN/00", + "96NN/01", "9SA2", "A2M4", "CFD4", "DBG4", "DK3J", "DK95/00", "DK95/03", "DK95/04", "FP8R", "FRK4", "HM87/00", "HWV9", "K3WX", "M2N8/00", - "M7A3", "MUS6/05", "MUS6/06", "NHX8", "NJ66", "NKF9", "Q5MG", "QT73", + "M7A3", "NHX8", "NJ66", "NKF9", "Q5MG", "QT73", "R4YG", "S3PD", "SM9W/01", "UKK6/00", "UT92", "VJP3/01", "W4TN", "W5VH", "Y79Y/001", "Y79Y/010", // wrong event stream: the port matches libyaml but both deviate from the