swiftformat --lint failed on these pre-existing files and the pre-push hook checks the whole tree, blocking every push until they conform. The wrapped if bodies pushed YAMLTestSuiteGapsTests over the 400-line lint limit, so its private JSONStreamScanner helper moves to its own support file.
164 lines
6.6 KiB
Swift
164 lines
6.6 KiB
Swift
extension PureYAML.Parsing.LibYAML.Scanner {
|
|
private typealias Classify = PureYAML.Parsing.LibYAML.CharacterClass
|
|
|
|
/// Port of `yaml_parser_scan_tag`. Scans a tag in verbatim `!<uri>`, shorthand
|
|
/// `!handle!suffix`, or `!suffix` form, producing the handle and suffix; the
|
|
/// parser resolves them against the tag directives.
|
|
mutating func scanTag() throws -> PureYAML.Parsing.LibYAML.Token {
|
|
let startMark = reader.mark
|
|
var handle = ""
|
|
var suffix = ""
|
|
if reader.peek(1) == "<" {
|
|
reader.skip()
|
|
reader.skip()
|
|
suffix = try scanTagURI(uriChar: true, directive: false, head: "", startMark: startMark)
|
|
guard reader.peek() == ">" else {
|
|
throw tagError("did not find the expected '>'", startMark)
|
|
}
|
|
reader.skip()
|
|
} else {
|
|
handle = try scanTagHandle(directive: false, startMark: startMark)
|
|
if handle.first == "!", handle.count > 1, handle.last == "!" {
|
|
suffix = try scanTagURI(uriChar: false, directive: false, head: "", startMark: startMark)
|
|
} else {
|
|
suffix = try scanTagURI(uriChar: false, directive: false, head: handle, startMark: startMark)
|
|
handle = "!"
|
|
if suffix.isEmpty {
|
|
swap(&handle, &suffix)
|
|
}
|
|
}
|
|
}
|
|
guard Classify.isBlankZ(reader.peek()) || (flowLevel > 0 && reader.peek() == ",") else {
|
|
throw tagError("did not find expected whitespace or line break", startMark)
|
|
}
|
|
return PureYAML.Parsing.LibYAML.Token(type: .tag(handle: handle, suffix: suffix), startMark: startMark, endMark: reader.mark)
|
|
}
|
|
|
|
/// Port of `yaml_parser_scan_tag_handle`. A handle is `!`, optionally followed
|
|
/// by alphanumeric characters and a closing `!`.
|
|
mutating func scanTagHandle(directive: Bool, startMark: PureYAML.Parsing.Mark) throws -> String {
|
|
guard reader.peek() == "!" else {
|
|
throw tagError("did not find expected '!'", startMark)
|
|
}
|
|
var handle = "!"
|
|
reader.skip()
|
|
while let character = reader.peek(), Classify.isAlpha(character) {
|
|
handle.unicodeScalars.append(character)
|
|
reader.skip()
|
|
}
|
|
if reader.peek() == "!" {
|
|
handle.unicodeScalars.append("!")
|
|
reader.skip()
|
|
} else if directive, handle != "!" {
|
|
throw tagError("did not find expected '!'", startMark)
|
|
}
|
|
return handle
|
|
}
|
|
|
|
/// Port of `yaml_parser_scan_tag_uri`. Consumes the URI character set (plus the
|
|
/// flow indicators inside a verbatim tag), decoding `%`-escapes.
|
|
mutating func scanTagURI(uriChar: Bool, directive: Bool, head: String, startMark: PureYAML.Parsing.Mark) throws -> String {
|
|
var string = head.count > 1 ? String(head.dropFirst()) : ""
|
|
var length = head.isEmpty ? 0 : head.count
|
|
while let character = reader.peek(), isURICharacter(character, uriChar: uriChar) {
|
|
if character == "%" {
|
|
try scanURIEscapes(directive: directive, startMark: startMark, into: &string)
|
|
} else {
|
|
string.unicodeScalars.append(character)
|
|
reader.skip()
|
|
}
|
|
length += 1
|
|
}
|
|
guard length > 0 else {
|
|
throw tagError("did not find expected tag URI", startMark)
|
|
}
|
|
return string
|
|
}
|
|
|
|
/// Port of `yaml_parser_scan_uri_escapes`. Decodes one UTF-8 character from a
|
|
/// run of `%XX` octets.
|
|
private mutating func scanURIEscapes(directive _: Bool, startMark: PureYAML.Parsing.Mark, into string: inout String) throws {
|
|
var bytes: [UInt8] = []
|
|
var width = 0
|
|
repeat {
|
|
guard reader.peek() == "%", let high = reader.peek(1), Classify.isHex(high), let low = reader.peek(2), Classify.isHex(low) else {
|
|
throw tagError("did not find URI escaped octet", startMark)
|
|
}
|
|
let octet = UInt8((hexDigit(high) << 4) + hexDigit(low))
|
|
if width == 0 {
|
|
width = utf8Width(of: octet)
|
|
guard width > 0 else {
|
|
throw tagError("found an incorrect leading UTF-8 octet", startMark)
|
|
}
|
|
} else if octet & 0xC0 != 0x80 {
|
|
throw tagError("found an incorrect trailing UTF-8 octet", startMark)
|
|
}
|
|
bytes.append(octet)
|
|
reader.skip()
|
|
reader.skip()
|
|
reader.skip()
|
|
width -= 1
|
|
} while width > 0
|
|
guard let scalar = Unicode.Scalar(decodeUTF8(bytes)) else {
|
|
throw tagError("found invalid UTF-8 in a URI escape", startMark)
|
|
}
|
|
string.unicodeScalars.append(scalar)
|
|
}
|
|
|
|
/// Assemble a code point from the validated 1-to-4 UTF-8 octets of one escape.
|
|
private func decodeUTF8(_ bytes: [UInt8]) -> UInt32 {
|
|
switch bytes.count {
|
|
case 1:
|
|
UInt32(bytes[0])
|
|
case 2:
|
|
(UInt32(bytes[0] & 0x1F) << 6) | UInt32(bytes[1] & 0x3F)
|
|
case 3:
|
|
(UInt32(bytes[0] & 0x0F) << 12) | (UInt32(bytes[1] & 0x3F) << 6) | UInt32(bytes[2] & 0x3F)
|
|
default:
|
|
(UInt32(bytes[0] & 0x07) << 18) | (UInt32(bytes[1] & 0x3F) << 12) | (UInt32(bytes[2] & 0x3F) << 6) | UInt32(bytes[3] & 0x3F)
|
|
}
|
|
}
|
|
|
|
private func utf8Width(of octet: UInt8) -> Int {
|
|
if octet & 0x80 == 0x00 {
|
|
return 1
|
|
}
|
|
if octet & 0xE0 == 0xC0 {
|
|
return 2
|
|
}
|
|
if octet & 0xF0 == 0xE0 {
|
|
return 3
|
|
}
|
|
if octet & 0xF8 == 0xF0 {
|
|
return 4
|
|
}
|
|
return 0
|
|
}
|
|
|
|
private func isURICharacter(_ scalar: Unicode.Scalar, uriChar: Bool) -> Bool {
|
|
if Classify.isAlpha(scalar) {
|
|
return true
|
|
}
|
|
switch scalar {
|
|
case ";", "/", "?", ":", "@", "&", "=", "+", "$", ".", "%", "!", "~", "*", "'", "(", ")":
|
|
return true
|
|
case ",", "[", "]":
|
|
return uriChar
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
private func hexDigit(_ scalar: Unicode.Scalar) -> Int {
|
|
switch scalar {
|
|
case "0" ... "9": Int(scalar.value - Unicode.Scalar("0").value)
|
|
case "A" ... "F": Int(scalar.value - Unicode.Scalar("A").value) + 10
|
|
default: Int(scalar.value - Unicode.Scalar("a").value) + 10
|
|
}
|
|
}
|
|
|
|
private func tagError(_ problem: String, _ mark: PureYAML.Parsing.Mark) -> PureYAML.Parsing.LibYAML.ScannerError {
|
|
PureYAML.Parsing.LibYAML.ScannerError(context: "while scanning a tag", problem: problem, mark: mark)
|
|
}
|
|
}
|