|
|
@@ -64,20 +64,20 @@ private let asciiLowerZ = UInt8(ascii: "z")
|
|
|
private let asciiUpperZ = UInt8(ascii: "Z")
|
|
|
|
|
|
private func fromHexDigit(_ c: UnicodeScalar) -> UInt32? {
|
|
|
- let n = c.value
|
|
|
- if n >= 48 && n <= 57 {
|
|
|
- return UInt32(n - 48)
|
|
|
- }
|
|
|
- switch n {
|
|
|
- case 65, 97: return 10
|
|
|
- case 66, 98: return 11
|
|
|
- case 67, 99: return 12
|
|
|
- case 68, 100: return 13
|
|
|
- case 69, 101: return 14
|
|
|
- case 70, 102: return 15
|
|
|
- default:
|
|
|
- return nil
|
|
|
- }
|
|
|
+ let n = c.value
|
|
|
+ if n >= 48 && n <= 57 {
|
|
|
+ return UInt32(n - 48)
|
|
|
+ }
|
|
|
+ switch n {
|
|
|
+ case 65, 97: return 10
|
|
|
+ case 66, 98: return 11
|
|
|
+ case 67, 99: return 12
|
|
|
+ case 68, 100: return 13
|
|
|
+ case 69, 101: return 14
|
|
|
+ case 70, 102: return 15
|
|
|
+ default:
|
|
|
+ return nil
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Decode both the RFC 4648 section 4 Base 64 encoding and the RFC
|
|
|
@@ -86,22 +86,22 @@ private func fromHexDigit(_ c: UnicodeScalar) -> UInt32? {
|
|
|
// Note that both "-" and "+" decode to 62 and "/" and "_" both
|
|
|
// decode as 63.
|
|
|
let base64Values: [Int] = [
|
|
|
-/* 0x00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0x10 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0x20 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63,
|
|
|
-/* 0x30 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0x40 */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
|
-/* 0x50 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
|
|
|
-/* 0x60 */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
|
-/* 0x70 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
|
|
-/* 0x80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0x90 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xa0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xb0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xc0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xd0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xe0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
-/* 0xf0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x10 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x20 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63,
|
|
|
+ /* 0x30 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x40 */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
|
+ /* 0x50 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
|
|
|
+ /* 0x60 */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
|
+ /* 0x70 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0x90 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xa0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xb0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xc0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xd0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xe0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
+ /* 0xf0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
]
|
|
|
|
|
|
/// Returns a `Data` value containing bytes equivalent to the given
|
|
|
@@ -116,9 +116,9 @@ let base64Values: [Int] = [
|
|
|
/// Base 64 encoding and the "URL and Filename Safe Alphabet" variant.
|
|
|
///
|
|
|
private func parseBytes(
|
|
|
- source: UnsafeRawBufferPointer,
|
|
|
- index: inout UnsafeRawBufferPointer.Index,
|
|
|
- end: UnsafeRawBufferPointer.Index
|
|
|
+ source: UnsafeRawBufferPointer,
|
|
|
+ index: inout UnsafeRawBufferPointer.Index,
|
|
|
+ end: UnsafeRawBufferPointer.Index
|
|
|
) throws -> Data {
|
|
|
let c = source[index]
|
|
|
if c != asciiDoubleQuote {
|
|
|
@@ -191,83 +191,83 @@ private func parseBytes(
|
|
|
index = digitsStart
|
|
|
try value.withUnsafeMutableBytes {
|
|
|
(body: UnsafeMutableRawBufferPointer) in
|
|
|
- if var p = body.baseAddress, body.count > 0 {
|
|
|
- var n = 0
|
|
|
- var chars = 0 // # chars in current group
|
|
|
- var padding = 0 // # padding '=' chars
|
|
|
- digits: while true {
|
|
|
- let digit = source[index]
|
|
|
- var k = base64Values[Int(digit)]
|
|
|
- if k < 0 {
|
|
|
- switch digit {
|
|
|
- case asciiDoubleQuote:
|
|
|
- break digits
|
|
|
- case asciiBackslash:
|
|
|
- source.formIndex(after: &index)
|
|
|
- let escaped = source[index]
|
|
|
- switch escaped {
|
|
|
- case asciiForwardSlash:
|
|
|
- k = base64Values[Int(escaped)]
|
|
|
- default:
|
|
|
- // Note: Invalid backslash escapes were caught
|
|
|
- // above; we should never get here.
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
- }
|
|
|
- case asciiSpace:
|
|
|
- source.formIndex(after: &index)
|
|
|
- continue digits
|
|
|
- case asciiEqualSign: // Count padding
|
|
|
- while true {
|
|
|
- switch source[index] {
|
|
|
- case asciiDoubleQuote:
|
|
|
- break digits
|
|
|
- case asciiSpace:
|
|
|
- break
|
|
|
- case 61:
|
|
|
- padding += 1
|
|
|
- default: // Only '=' and whitespace permitted
|
|
|
+ if var p = body.baseAddress, body.count > 0 {
|
|
|
+ var n = 0
|
|
|
+ var chars = 0 // # chars in current group
|
|
|
+ var padding = 0 // # padding '=' chars
|
|
|
+ digits: while true {
|
|
|
+ let digit = source[index]
|
|
|
+ var k = base64Values[Int(digit)]
|
|
|
+ if k < 0 {
|
|
|
+ switch digit {
|
|
|
+ case asciiDoubleQuote:
|
|
|
+ break digits
|
|
|
+ case asciiBackslash:
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ let escaped = source[index]
|
|
|
+ switch escaped {
|
|
|
+ case asciiForwardSlash:
|
|
|
+ k = base64Values[Int(escaped)]
|
|
|
+ default:
|
|
|
+ // Note: Invalid backslash escapes were caught
|
|
|
+ // above; we should never get here.
|
|
|
throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
+ case asciiSpace:
|
|
|
source.formIndex(after: &index)
|
|
|
+ continue digits
|
|
|
+ case asciiEqualSign: // Count padding
|
|
|
+ while true {
|
|
|
+ switch source[index] {
|
|
|
+ case asciiDoubleQuote:
|
|
|
+ break digits
|
|
|
+ case asciiSpace:
|
|
|
+ break
|
|
|
+ case 61:
|
|
|
+ padding += 1
|
|
|
+ default: // Only '=' and whitespace permitted
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
+ }
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
- default:
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
+ n <<= 6
|
|
|
+ n |= k
|
|
|
+ chars += 1
|
|
|
+ if chars == 4 {
|
|
|
+ p[0] = UInt8(truncatingIfNeeded: n >> 16)
|
|
|
+ p[1] = UInt8(truncatingIfNeeded: n >> 8)
|
|
|
+ p[2] = UInt8(truncatingIfNeeded: n)
|
|
|
+ p += 3
|
|
|
+ chars = 0
|
|
|
+ n = 0
|
|
|
+ }
|
|
|
+ source.formIndex(after: &index)
|
|
|
}
|
|
|
- n <<= 6
|
|
|
- n |= k
|
|
|
- chars += 1
|
|
|
- if chars == 4 {
|
|
|
- p[0] = UInt8(truncatingIfNeeded: n >> 16)
|
|
|
- p[1] = UInt8(truncatingIfNeeded: n >> 8)
|
|
|
- p[2] = UInt8(truncatingIfNeeded: n)
|
|
|
- p += 3
|
|
|
- chars = 0
|
|
|
- n = 0
|
|
|
- }
|
|
|
- source.formIndex(after: &index)
|
|
|
- }
|
|
|
- switch chars {
|
|
|
- case 3:
|
|
|
- p[0] = UInt8(truncatingIfNeeded: n >> 10)
|
|
|
- p[1] = UInt8(truncatingIfNeeded: n >> 2)
|
|
|
- if padding == 1 || padding == 0 {
|
|
|
- return
|
|
|
- }
|
|
|
- case 2:
|
|
|
- p[0] = UInt8(truncatingIfNeeded: n >> 4)
|
|
|
- if padding == 2 || padding == 0 {
|
|
|
- return
|
|
|
- }
|
|
|
- case 0:
|
|
|
- if padding == 0 {
|
|
|
- return
|
|
|
+ switch chars {
|
|
|
+ case 3:
|
|
|
+ p[0] = UInt8(truncatingIfNeeded: n >> 10)
|
|
|
+ p[1] = UInt8(truncatingIfNeeded: n >> 2)
|
|
|
+ if padding == 1 || padding == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ case 2:
|
|
|
+ p[0] = UInt8(truncatingIfNeeded: n >> 4)
|
|
|
+ if padding == 2 || padding == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ case 0:
|
|
|
+ if padding == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ break
|
|
|
}
|
|
|
- default:
|
|
|
- break
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
- }
|
|
|
}
|
|
|
source.formIndex(after: &index)
|
|
|
return value
|
|
|
@@ -276,93 +276,95 @@ private func parseBytes(
|
|
|
// JSON encoding allows a variety of \-escapes, including
|
|
|
// escaping UTF-16 code points (which may be surrogate pairs).
|
|
|
private func decodeString(_ s: String) -> String? {
|
|
|
- var out = String.UnicodeScalarView()
|
|
|
- var chars = s.unicodeScalars.makeIterator()
|
|
|
- while let c = chars.next() {
|
|
|
- switch c.value {
|
|
|
- case UInt32(asciiBackslash): // backslash
|
|
|
- if let escaped = chars.next() {
|
|
|
- switch escaped.value {
|
|
|
- case UInt32(asciiLowerU): // "\u"
|
|
|
- // Exactly 4 hex digits:
|
|
|
- if let digit1 = chars.next(),
|
|
|
- let d1 = fromHexDigit(digit1),
|
|
|
- let digit2 = chars.next(),
|
|
|
- let d2 = fromHexDigit(digit2),
|
|
|
- let digit3 = chars.next(),
|
|
|
- let d3 = fromHexDigit(digit3),
|
|
|
- let digit4 = chars.next(),
|
|
|
- let d4 = fromHexDigit(digit4) {
|
|
|
- let codePoint = ((d1 * 16 + d2) * 16 + d3) * 16 + d4
|
|
|
- if let scalar = UnicodeScalar(codePoint) {
|
|
|
- out.append(scalar)
|
|
|
- } else if codePoint < 0xD800 || codePoint >= 0xE000 {
|
|
|
- // Not a valid Unicode scalar.
|
|
|
- return nil
|
|
|
- } else if codePoint >= 0xDC00 {
|
|
|
- // Low surrogate without a preceding high surrogate.
|
|
|
- return nil
|
|
|
- } else {
|
|
|
- // We have a high surrogate (in the range 0xD800..<0xDC00), so
|
|
|
- // verify that it is followed by a low surrogate.
|
|
|
- guard chars.next() == "\\", chars.next() == "u" else {
|
|
|
- // High surrogate was not followed by a Unicode escape sequence.
|
|
|
- return nil
|
|
|
- }
|
|
|
- if let digit1 = chars.next(),
|
|
|
- let d1 = fromHexDigit(digit1),
|
|
|
- let digit2 = chars.next(),
|
|
|
- let d2 = fromHexDigit(digit2),
|
|
|
- let digit3 = chars.next(),
|
|
|
- let d3 = fromHexDigit(digit3),
|
|
|
- let digit4 = chars.next(),
|
|
|
- let d4 = fromHexDigit(digit4) {
|
|
|
- let follower = ((d1 * 16 + d2) * 16 + d3) * 16 + d4
|
|
|
- guard 0xDC00 <= follower && follower < 0xE000 else {
|
|
|
- // High surrogate was not followed by a low surrogate.
|
|
|
- return nil
|
|
|
- }
|
|
|
- let high = codePoint - 0xD800
|
|
|
- let low = follower - 0xDC00
|
|
|
- let composed = 0x10000 | high << 10 | low
|
|
|
- guard let composedScalar = UnicodeScalar(composed) else {
|
|
|
- // Composed value is not a valid Unicode scalar.
|
|
|
- return nil
|
|
|
+ var out = String.UnicodeScalarView()
|
|
|
+ var chars = s.unicodeScalars.makeIterator()
|
|
|
+ while let c = chars.next() {
|
|
|
+ switch c.value {
|
|
|
+ case UInt32(asciiBackslash): // backslash
|
|
|
+ if let escaped = chars.next() {
|
|
|
+ switch escaped.value {
|
|
|
+ case UInt32(asciiLowerU): // "\u"
|
|
|
+ // Exactly 4 hex digits:
|
|
|
+ if let digit1 = chars.next(),
|
|
|
+ let d1 = fromHexDigit(digit1),
|
|
|
+ let digit2 = chars.next(),
|
|
|
+ let d2 = fromHexDigit(digit2),
|
|
|
+ let digit3 = chars.next(),
|
|
|
+ let d3 = fromHexDigit(digit3),
|
|
|
+ let digit4 = chars.next(),
|
|
|
+ let d4 = fromHexDigit(digit4)
|
|
|
+ {
|
|
|
+ let codePoint = ((d1 * 16 + d2) * 16 + d3) * 16 + d4
|
|
|
+ if let scalar = UnicodeScalar(codePoint) {
|
|
|
+ out.append(scalar)
|
|
|
+ } else if codePoint < 0xD800 || codePoint >= 0xE000 {
|
|
|
+ // Not a valid Unicode scalar.
|
|
|
+ return nil
|
|
|
+ } else if codePoint >= 0xDC00 {
|
|
|
+ // Low surrogate without a preceding high surrogate.
|
|
|
+ return nil
|
|
|
+ } else {
|
|
|
+ // We have a high surrogate (in the range 0xD800..<0xDC00), so
|
|
|
+ // verify that it is followed by a low surrogate.
|
|
|
+ guard chars.next() == "\\", chars.next() == "u" else {
|
|
|
+ // High surrogate was not followed by a Unicode escape sequence.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if let digit1 = chars.next(),
|
|
|
+ let d1 = fromHexDigit(digit1),
|
|
|
+ let digit2 = chars.next(),
|
|
|
+ let d2 = fromHexDigit(digit2),
|
|
|
+ let digit3 = chars.next(),
|
|
|
+ let d3 = fromHexDigit(digit3),
|
|
|
+ let digit4 = chars.next(),
|
|
|
+ let d4 = fromHexDigit(digit4)
|
|
|
+ {
|
|
|
+ let follower = ((d1 * 16 + d2) * 16 + d3) * 16 + d4
|
|
|
+ guard 0xDC00 <= follower && follower < 0xE000 else {
|
|
|
+ // High surrogate was not followed by a low surrogate.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ let high = codePoint - 0xD800
|
|
|
+ let low = follower - 0xDC00
|
|
|
+ let composed = 0x10000 | high << 10 | low
|
|
|
+ guard let composedScalar = UnicodeScalar(composed) else {
|
|
|
+ // Composed value is not a valid Unicode scalar.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ out.append(composedScalar)
|
|
|
+ } else {
|
|
|
+ // Malformed \u escape for low surrogate
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Malformed \u escape
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case UInt32(asciiLowerB): // \b
|
|
|
+ out.append("\u{08}")
|
|
|
+ case UInt32(asciiLowerF): // \f
|
|
|
+ out.append("\u{0c}")
|
|
|
+ case UInt32(asciiLowerN): // \n
|
|
|
+ out.append("\u{0a}")
|
|
|
+ case UInt32(asciiLowerR): // \r
|
|
|
+ out.append("\u{0d}")
|
|
|
+ case UInt32(asciiLowerT): // \t
|
|
|
+ out.append("\u{09}")
|
|
|
+ case UInt32(asciiDoubleQuote), UInt32(asciiBackslash),
|
|
|
+ UInt32(asciiForwardSlash): // " \ /
|
|
|
+ out.append(escaped)
|
|
|
+ default:
|
|
|
+ return nil // Unrecognized escape
|
|
|
}
|
|
|
- out.append(composedScalar)
|
|
|
- } else {
|
|
|
- // Malformed \u escape for low surrogate
|
|
|
- return nil
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ return nil // Input ends with backslash
|
|
|
}
|
|
|
- } else {
|
|
|
- // Malformed \u escape
|
|
|
- return nil
|
|
|
- }
|
|
|
- case UInt32(asciiLowerB): // \b
|
|
|
- out.append("\u{08}")
|
|
|
- case UInt32(asciiLowerF): // \f
|
|
|
- out.append("\u{0c}")
|
|
|
- case UInt32(asciiLowerN): // \n
|
|
|
- out.append("\u{0a}")
|
|
|
- case UInt32(asciiLowerR): // \r
|
|
|
- out.append("\u{0d}")
|
|
|
- case UInt32(asciiLowerT): // \t
|
|
|
- out.append("\u{09}")
|
|
|
- case UInt32(asciiDoubleQuote), UInt32(asciiBackslash),
|
|
|
- UInt32(asciiForwardSlash): // " \ /
|
|
|
- out.append(escaped)
|
|
|
default:
|
|
|
- return nil // Unrecognized escape
|
|
|
+ out.append(c)
|
|
|
}
|
|
|
- } else {
|
|
|
- return nil // Input ends with backslash
|
|
|
- }
|
|
|
- default:
|
|
|
- out.append(c)
|
|
|
}
|
|
|
- }
|
|
|
- return String(out)
|
|
|
+ return String(out)
|
|
|
}
|
|
|
|
|
|
///
|
|
|
@@ -370,1174 +372,1203 @@ private func decodeString(_ s: String) -> String? {
|
|
|
///
|
|
|
/// For performance, it works directly against UTF-8 bytes in memory.
|
|
|
internal struct JSONScanner {
|
|
|
- private let source: UnsafeRawBufferPointer
|
|
|
- private var index: UnsafeRawBufferPointer.Index
|
|
|
- private var numberParser = DoubleParser()
|
|
|
- internal let options: JSONDecodingOptions
|
|
|
- internal let extensions: any ExtensionMap
|
|
|
- internal var recursionBudget: Int
|
|
|
+ private let source: UnsafeRawBufferPointer
|
|
|
+ private var index: UnsafeRawBufferPointer.Index
|
|
|
+ private var numberParser = DoubleParser()
|
|
|
+ internal let options: JSONDecodingOptions
|
|
|
+ internal let extensions: any ExtensionMap
|
|
|
+ internal var recursionBudget: Int
|
|
|
|
|
|
- /// True if the scanner has read all of the data from the source, with the
|
|
|
- /// exception of any trailing whitespace (which is consumed by reading this
|
|
|
- /// property).
|
|
|
- internal var complete: Bool {
|
|
|
- mutating get {
|
|
|
- skipWhitespace()
|
|
|
- return !hasMoreContent
|
|
|
+ /// True if the scanner has read all of the data from the source, with the
|
|
|
+ /// exception of any trailing whitespace (which is consumed by reading this
|
|
|
+ /// property).
|
|
|
+ internal var complete: Bool {
|
|
|
+ mutating get {
|
|
|
+ skipWhitespace()
|
|
|
+ return !hasMoreContent
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /// True if the scanner has not yet reached the end of the source.
|
|
|
- private var hasMoreContent: Bool {
|
|
|
- return index != source.endIndex
|
|
|
- }
|
|
|
|
|
|
- /// The byte (UTF-8 code unit) at the scanner's current position.
|
|
|
- private var currentByte: UInt8 {
|
|
|
- return source[index]
|
|
|
- }
|
|
|
+ /// True if the scanner has not yet reached the end of the source.
|
|
|
+ private var hasMoreContent: Bool {
|
|
|
+ index != source.endIndex
|
|
|
+ }
|
|
|
|
|
|
- internal init(
|
|
|
- source: UnsafeRawBufferPointer,
|
|
|
- options: JSONDecodingOptions,
|
|
|
- extensions: (any ExtensionMap)?
|
|
|
- ) {
|
|
|
- self.source = source
|
|
|
- self.index = source.startIndex
|
|
|
- self.recursionBudget = options.messageDepthLimit
|
|
|
- self.options = options
|
|
|
- self.extensions = extensions ?? SimpleExtensionMap()
|
|
|
- }
|
|
|
+ /// The byte (UTF-8 code unit) at the scanner's current position.
|
|
|
+ private var currentByte: UInt8 {
|
|
|
+ source[index]
|
|
|
+ }
|
|
|
|
|
|
- internal mutating func incrementRecursionDepth() throws {
|
|
|
- recursionBudget -= 1
|
|
|
- if recursionBudget < 0 {
|
|
|
- throw JSONDecodingError.messageDepthLimit
|
|
|
+ internal init(
|
|
|
+ source: UnsafeRawBufferPointer,
|
|
|
+ options: JSONDecodingOptions,
|
|
|
+ extensions: (any ExtensionMap)?
|
|
|
+ ) {
|
|
|
+ self.source = source
|
|
|
+ self.index = source.startIndex
|
|
|
+ self.recursionBudget = options.messageDepthLimit
|
|
|
+ self.options = options
|
|
|
+ self.extensions = extensions ?? SimpleExtensionMap()
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- internal mutating func decrementRecursionDepth() {
|
|
|
- recursionBudget += 1
|
|
|
- // This should never happen, if it does, something is probably corrupting memory, and
|
|
|
- // simply throwing doesn't make much sense.
|
|
|
- if recursionBudget > options.messageDepthLimit {
|
|
|
- fatalError("Somehow JSONDecoding unwound more objects than it started")
|
|
|
+ internal mutating func incrementRecursionDepth() throws {
|
|
|
+ recursionBudget -= 1
|
|
|
+ if recursionBudget < 0 {
|
|
|
+ throw JSONDecodingError.messageDepthLimit
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Advances the scanner to the next position in the source.
|
|
|
- private mutating func advance() {
|
|
|
- source.formIndex(after: &index)
|
|
|
- }
|
|
|
+ internal mutating func decrementRecursionDepth() {
|
|
|
+ recursionBudget += 1
|
|
|
+ // This should never happen, if it does, something is probably corrupting memory, and
|
|
|
+ // simply throwing doesn't make much sense.
|
|
|
+ if recursionBudget > options.messageDepthLimit {
|
|
|
+ fatalError("Somehow JSONDecoding unwound more objects than it started")
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- /// Skip whitespace.
|
|
|
- private mutating func skipWhitespace() {
|
|
|
- while hasMoreContent {
|
|
|
- let u = currentByte
|
|
|
- switch u {
|
|
|
- case asciiSpace, asciiTab, asciiNewLine, asciiCarriageReturn:
|
|
|
- advance()
|
|
|
- default:
|
|
|
- return
|
|
|
- }
|
|
|
+ /// Advances the scanner to the next position in the source.
|
|
|
+ private mutating func advance() {
|
|
|
+ source.formIndex(after: &index)
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Returns (but does not consume) the next non-whitespace
|
|
|
- /// character. This is used by google.protobuf.Value, for
|
|
|
- /// example, for custom JSON parsing.
|
|
|
- internal mutating func peekOneCharacter() throws -> Character {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Skip whitespace.
|
|
|
+ private mutating func skipWhitespace() {
|
|
|
+ while hasMoreContent {
|
|
|
+ let u = currentByte
|
|
|
+ switch u {
|
|
|
+ case asciiSpace, asciiTab, asciiNewLine, asciiCarriageReturn:
|
|
|
+ advance()
|
|
|
+ default:
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return Character(UnicodeScalar(UInt32(currentByte))!)
|
|
|
- }
|
|
|
|
|
|
- // Parse the leading UInt64 from the provided utf8 bytes.
|
|
|
- //
|
|
|
- // This is called in three different situations:
|
|
|
- //
|
|
|
- // * Unquoted number.
|
|
|
- //
|
|
|
- // * Simple quoted number. If a number is quoted but has no
|
|
|
- // backslashes, the caller can use this directly on the UTF8 by
|
|
|
- // just verifying the quote marks. This code returns `nil` if it
|
|
|
- // sees a backslash, in which case the caller will need to handle ...
|
|
|
- //
|
|
|
- // * Complex quoted number. In this case, the caller must parse the
|
|
|
- // quoted value as a string, then convert the string to utf8 and
|
|
|
- // use this to parse the result. This is slow but fortunately
|
|
|
- // rare.
|
|
|
- //
|
|
|
- // In the common case where the number is written in integer form,
|
|
|
- // this code does a simple straight conversion. If the number is in
|
|
|
- // floating-point format, this uses a slower and less accurate
|
|
|
- // approach: it identifies a substring comprising a float, and then
|
|
|
- // uses Double() and UInt64() to convert that string to an unsigned
|
|
|
- // integer. In particular, it cannot preserve full 64-bit integer
|
|
|
- // values when they are written in floating-point format.
|
|
|
- //
|
|
|
- // If it encounters a "\" backslash character, it returns a nil. This
|
|
|
- // is used by callers that are parsing quoted numbers. See nextSInt()
|
|
|
- // and nextUInt() below.
|
|
|
- private func parseBareUInt64(
|
|
|
- source: UnsafeRawBufferPointer,
|
|
|
- index: inout UnsafeRawBufferPointer.Index,
|
|
|
- end: UnsafeRawBufferPointer.Index
|
|
|
- ) throws -> UInt64? {
|
|
|
- if index == end {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Returns (but does not consume) the next non-whitespace
|
|
|
+ /// character. This is used by google.protobuf.Value, for
|
|
|
+ /// example, for custom JSON parsing.
|
|
|
+ internal mutating func peekOneCharacter() throws -> Character {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ return Character(UnicodeScalar(UInt32(currentByte))!)
|
|
|
}
|
|
|
- let start = index
|
|
|
- let c = source[index]
|
|
|
- switch c {
|
|
|
- case asciiZero: // 0
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index != end {
|
|
|
- let after = source[index]
|
|
|
- switch after {
|
|
|
- case asciiZero...asciiNine: // 0...9
|
|
|
- // leading '0' forbidden unless it is the only digit
|
|
|
- throw JSONDecodingError.leadingZero
|
|
|
- case asciiPeriod, asciiLowerE, asciiUpperE: // . e
|
|
|
- // Slow path: JSON numbers can be written in floating-point notation
|
|
|
- index = start
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- if let u = UInt64(exactly: d) {
|
|
|
- return u
|
|
|
+
|
|
|
+ // Parse the leading UInt64 from the provided utf8 bytes.
|
|
|
+ //
|
|
|
+ // This is called in three different situations:
|
|
|
+ //
|
|
|
+ // * Unquoted number.
|
|
|
+ //
|
|
|
+ // * Simple quoted number. If a number is quoted but has no
|
|
|
+ // backslashes, the caller can use this directly on the UTF8 by
|
|
|
+ // just verifying the quote marks. This code returns `nil` if it
|
|
|
+ // sees a backslash, in which case the caller will need to handle ...
|
|
|
+ //
|
|
|
+ // * Complex quoted number. In this case, the caller must parse the
|
|
|
+ // quoted value as a string, then convert the string to utf8 and
|
|
|
+ // use this to parse the result. This is slow but fortunately
|
|
|
+ // rare.
|
|
|
+ //
|
|
|
+ // In the common case where the number is written in integer form,
|
|
|
+ // this code does a simple straight conversion. If the number is in
|
|
|
+ // floating-point format, this uses a slower and less accurate
|
|
|
+ // approach: it identifies a substring comprising a float, and then
|
|
|
+ // uses Double() and UInt64() to convert that string to an unsigned
|
|
|
+ // integer. In particular, it cannot preserve full 64-bit integer
|
|
|
+ // values when they are written in floating-point format.
|
|
|
+ //
|
|
|
+ // If it encounters a "\" backslash character, it returns a nil. This
|
|
|
+ // is used by callers that are parsing quoted numbers. See nextSInt()
|
|
|
+ // and nextUInt() below.
|
|
|
+ private func parseBareUInt64(
|
|
|
+ source: UnsafeRawBufferPointer,
|
|
|
+ index: inout UnsafeRawBufferPointer.Index,
|
|
|
+ end: UnsafeRawBufferPointer.Index
|
|
|
+ ) throws -> UInt64? {
|
|
|
+ if index == end {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let start = index
|
|
|
+ let c = source[index]
|
|
|
+ switch c {
|
|
|
+ case asciiZero: // 0
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index != end {
|
|
|
+ let after = source[index]
|
|
|
+ switch after {
|
|
|
+ case asciiZero...asciiNine: // 0...9
|
|
|
+ // leading '0' forbidden unless it is the only digit
|
|
|
+ throw JSONDecodingError.leadingZero
|
|
|
+ case asciiPeriod, asciiLowerE, asciiUpperE: // . e
|
|
|
+ // Slow path: JSON numbers can be written in floating-point notation
|
|
|
+ index = start
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ if let u = UInt64(exactly: d) {
|
|
|
+ return u
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ case asciiBackslash:
|
|
|
+ return nil
|
|
|
+ default:
|
|
|
+ return 0
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- case asciiBackslash:
|
|
|
- return nil
|
|
|
- default:
|
|
|
- return 0
|
|
|
- }
|
|
|
- }
|
|
|
- return 0
|
|
|
- case asciiOne...asciiNine: // 1...9
|
|
|
- var n = 0 as UInt64
|
|
|
- while index != end {
|
|
|
- let digit = source[index]
|
|
|
- switch digit {
|
|
|
- case asciiZero...asciiNine: // 0...9
|
|
|
- let val = UInt64(digit - asciiZero)
|
|
|
- if n > UInt64.max / 10 || n * 10 > UInt64.max - val {
|
|
|
- throw JSONDecodingError.numberRange
|
|
|
- }
|
|
|
- source.formIndex(after: &index)
|
|
|
- n = n * 10 + val
|
|
|
- case asciiPeriod, asciiLowerE, asciiUpperE: // . e
|
|
|
- // Slow path: JSON allows floating-point notation for integers
|
|
|
- index = start
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- if let u = UInt64(exactly: d) {
|
|
|
- return u
|
|
|
+ return 0
|
|
|
+ case asciiOne...asciiNine: // 1...9
|
|
|
+ var n = 0 as UInt64
|
|
|
+ while index != end {
|
|
|
+ let digit = source[index]
|
|
|
+ switch digit {
|
|
|
+ case asciiZero...asciiNine: // 0...9
|
|
|
+ let val = UInt64(digit - asciiZero)
|
|
|
+ if n > UInt64.max / 10 || n * 10 > UInt64.max - val {
|
|
|
+ throw JSONDecodingError.numberRange
|
|
|
+ }
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ n = n * 10 + val
|
|
|
+ case asciiPeriod, asciiLowerE, asciiUpperE: // . e
|
|
|
+ // Slow path: JSON allows floating-point notation for integers
|
|
|
+ index = start
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ if let u = UInt64(exactly: d) {
|
|
|
+ return u
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ case asciiBackslash:
|
|
|
+ return nil
|
|
|
+ default:
|
|
|
+ return n
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ return n
|
|
|
case asciiBackslash:
|
|
|
- return nil
|
|
|
+ return nil
|
|
|
default:
|
|
|
- return n
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
}
|
|
|
- }
|
|
|
- return n
|
|
|
- case asciiBackslash:
|
|
|
- return nil
|
|
|
- default:
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // Parse the leading Int64 from the provided utf8.
|
|
|
- //
|
|
|
- // This uses parseBareUInt64() to do the heavy lifting;
|
|
|
- // we just check for a leading minus and negate the result
|
|
|
- // as necessary.
|
|
|
- //
|
|
|
- // As with parseBareUInt64(), if it encounters a "\" backslash
|
|
|
- // character, it returns a nil. This allows callers to use this to
|
|
|
- // do a "fast-path" decode of simple quoted numbers by parsing the
|
|
|
- // UTF8 directly, only falling back to a full String decode when
|
|
|
- // absolutely necessary.
|
|
|
- private func parseBareSInt64(
|
|
|
- source: UnsafeRawBufferPointer,
|
|
|
- index: inout UnsafeRawBufferPointer.Index,
|
|
|
- end: UnsafeRawBufferPointer.Index
|
|
|
- ) throws -> Int64? {
|
|
|
- if index == end {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let c = source[index]
|
|
|
- if c == asciiMinus { // -
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- // character after '-' must be digit
|
|
|
- let digit = source[index]
|
|
|
- if digit < asciiZero || digit > asciiNine {
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
- if let n = try parseBareUInt64(source: source, index: &index, end: end) {
|
|
|
- let limit: UInt64 = 0x8000000000000000 // -Int64.min
|
|
|
- if n >= limit {
|
|
|
- if n > limit {
|
|
|
- // Too large negative number
|
|
|
- throw JSONDecodingError.numberRange
|
|
|
- } else {
|
|
|
- return Int64.min // Special case for Int64.min
|
|
|
- }
|
|
|
- }
|
|
|
- return -Int64(bitPattern: n)
|
|
|
- } else {
|
|
|
- return nil
|
|
|
- }
|
|
|
- } else if let n = try parseBareUInt64(source: source, index: &index, end: end) {
|
|
|
- if n > UInt64(bitPattern: Int64.max) {
|
|
|
- throw JSONDecodingError.numberRange
|
|
|
- }
|
|
|
- return Int64(bitPattern: n)
|
|
|
- } else {
|
|
|
- return nil
|
|
|
+ // Parse the leading Int64 from the provided utf8.
|
|
|
+ //
|
|
|
+ // This uses parseBareUInt64() to do the heavy lifting;
|
|
|
+ // we just check for a leading minus and negate the result
|
|
|
+ // as necessary.
|
|
|
+ //
|
|
|
+ // As with parseBareUInt64(), if it encounters a "\" backslash
|
|
|
+ // character, it returns a nil. This allows callers to use this to
|
|
|
+ // do a "fast-path" decode of simple quoted numbers by parsing the
|
|
|
+ // UTF8 directly, only falling back to a full String decode when
|
|
|
+ // absolutely necessary.
|
|
|
+ private func parseBareSInt64(
|
|
|
+ source: UnsafeRawBufferPointer,
|
|
|
+ index: inout UnsafeRawBufferPointer.Index,
|
|
|
+ end: UnsafeRawBufferPointer.Index
|
|
|
+ ) throws -> Int64? {
|
|
|
+ if index == end {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = source[index]
|
|
|
+ if c == asciiMinus { // -
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ // character after '-' must be digit
|
|
|
+ let digit = source[index]
|
|
|
+ if digit < asciiZero || digit > asciiNine {
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ if let n = try parseBareUInt64(source: source, index: &index, end: end) {
|
|
|
+ let limit: UInt64 = 0x8000_0000_0000_0000 // -Int64.min
|
|
|
+ if n >= limit {
|
|
|
+ if n > limit {
|
|
|
+ // Too large negative number
|
|
|
+ throw JSONDecodingError.numberRange
|
|
|
+ } else {
|
|
|
+ return Int64.min // Special case for Int64.min
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return -Int64(bitPattern: n)
|
|
|
+ } else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ } else if let n = try parseBareUInt64(source: source, index: &index, end: end) {
|
|
|
+ if n > UInt64(bitPattern: Int64.max) {
|
|
|
+ throw JSONDecodingError.numberRange
|
|
|
+ }
|
|
|
+ return Int64(bitPattern: n)
|
|
|
+ } else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // Identify a floating-point token in the upcoming UTF8 bytes.
|
|
|
- //
|
|
|
- // This implements the full grammar defined by the JSON RFC 7159.
|
|
|
- // Note that Swift's string-to-number conversions are much more
|
|
|
- // lenient, so this is necessary if we want to accurately reject
|
|
|
- // malformed JSON numbers.
|
|
|
- //
|
|
|
- // This is used by nextDouble() and nextFloat() to parse double and
|
|
|
- // floating-point values, including values that happen to be in quotes.
|
|
|
- // It's also used by the slow path in parseBareSInt64() and parseBareUInt64()
|
|
|
- // above to handle integer values that are written in float-point notation.
|
|
|
- private func parseBareDouble(
|
|
|
- source: UnsafeRawBufferPointer,
|
|
|
- index: inout UnsafeRawBufferPointer.Index,
|
|
|
- end: UnsafeRawBufferPointer.Index
|
|
|
- ) throws -> Double? {
|
|
|
- // RFC 7159 defines the grammar for JSON numbers as:
|
|
|
- // number = [ minus ] int [ frac ] [ exp ]
|
|
|
- if index == end {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let start = index
|
|
|
- var c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
+ // Identify a floating-point token in the upcoming UTF8 bytes.
|
|
|
+ //
|
|
|
+ // This implements the full grammar defined by the JSON RFC 7159.
|
|
|
+ // Note that Swift's string-to-number conversions are much more
|
|
|
+ // lenient, so this is necessary if we want to accurately reject
|
|
|
+ // malformed JSON numbers.
|
|
|
+ //
|
|
|
+ // This is used by nextDouble() and nextFloat() to parse double and
|
|
|
+ // floating-point values, including values that happen to be in quotes.
|
|
|
+ // It's also used by the slow path in parseBareSInt64() and parseBareUInt64()
|
|
|
+ // above to handle integer values that are written in float-point notation.
|
|
|
+ private func parseBareDouble(
|
|
|
+ source: UnsafeRawBufferPointer,
|
|
|
+ index: inout UnsafeRawBufferPointer.Index,
|
|
|
+ end: UnsafeRawBufferPointer.Index
|
|
|
+ ) throws -> Double? {
|
|
|
+ // RFC 7159 defines the grammar for JSON numbers as:
|
|
|
+ // number = [ minus ] int [ frac ] [ exp ]
|
|
|
+ if index == end {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let start = index
|
|
|
+ var c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
|
|
|
- // Optional leading minus sign
|
|
|
- if c == asciiMinus { // -
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- index = start
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
- } else if c == asciiUpperN { // Maybe NaN?
|
|
|
- // Return nil, let the caller deal with it.
|
|
|
- return nil
|
|
|
- }
|
|
|
+ // Optional leading minus sign
|
|
|
+ if c == asciiMinus { // -
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ index = start
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ } else if c == asciiUpperN { // Maybe NaN?
|
|
|
+ // Return nil, let the caller deal with it.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
|
|
|
- if c == asciiUpperI { // Maybe Infinity, Inf, -Infinity, or -Inf ?
|
|
|
- // Return nil, let the caller deal with it.
|
|
|
- return nil
|
|
|
- }
|
|
|
+ if c == asciiUpperI { // Maybe Infinity, Inf, -Infinity, or -Inf ?
|
|
|
+ // Return nil, let the caller deal with it.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
|
|
|
- // Integer part can be zero or a series of digits not starting with zero
|
|
|
- // int = zero / (digit1-9 *DIGIT)
|
|
|
- switch c {
|
|
|
- case asciiZero:
|
|
|
- // First digit can be zero only if not followed by a digit
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- return 0.0
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
- if c >= asciiZero && c <= asciiNine {
|
|
|
- throw JSONDecodingError.leadingZero
|
|
|
- }
|
|
|
- case asciiOne...asciiNine:
|
|
|
- while c >= asciiZero && c <= asciiNine {
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
+ // Integer part can be zero or a series of digits not starting with zero
|
|
|
+ // int = zero / (digit1-9 *DIGIT)
|
|
|
+ switch c {
|
|
|
+ case asciiZero:
|
|
|
+ // First digit can be zero only if not followed by a digit
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ return 0.0
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if c >= asciiZero && c <= asciiNine {
|
|
|
+ throw JSONDecodingError.leadingZero
|
|
|
+ }
|
|
|
+ case asciiOne...asciiNine:
|
|
|
+ while c >= asciiZero && c <= asciiNine {
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
+ return d
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.invalidUTF8
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ // Integer part cannot be empty
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+
|
|
|
+ // frac = decimal-point 1*DIGIT
|
|
|
+ if c == asciiPeriod {
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ // decimal point must have a following digit
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ switch c {
|
|
|
+ case asciiZero...asciiNine: // 0...9
|
|
|
+ while c >= asciiZero && c <= asciiNine {
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
+ return d
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.invalidUTF8
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case asciiBackslash:
|
|
|
+ return nil
|
|
|
+ default:
|
|
|
+ // decimal point must be followed by at least one digit
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // exp = e [ minus / plus ] 1*DIGIT
|
|
|
+ if c == asciiLowerE || c == asciiUpperE {
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ // "e" must be followed by +,-, or digit
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if c == asciiPlus || c == asciiMinus { // + -
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ // must be at least one digit in exponent
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ switch c {
|
|
|
+ case asciiZero...asciiNine:
|
|
|
+ while c >= asciiZero && c <= asciiNine {
|
|
|
+ source.formIndex(after: &index)
|
|
|
+ if index == end {
|
|
|
+ if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
+ return d
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.invalidUTF8
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c = source[index]
|
|
|
+ if c == asciiBackslash {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ // must be at least one digit in exponent
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
return d
|
|
|
- } else {
|
|
|
+ } else {
|
|
|
throw JSONDecodingError.invalidUTF8
|
|
|
- }
|
|
|
}
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Returns a fully-parsed string with all backslash escapes
|
|
|
+ /// correctly processed, or nil if next token is not a string.
|
|
|
+ ///
|
|
|
+ /// Assumes the leading quote has been verified (but not consumed)
|
|
|
+ private mutating func parseOptionalQuotedString() -> String? {
|
|
|
+ // Caller has already asserted that currentByte == quote here
|
|
|
+ var sawBackslash = false
|
|
|
+ advance()
|
|
|
+ let start = index
|
|
|
+ while hasMoreContent {
|
|
|
+ switch currentByte {
|
|
|
+ case asciiDoubleQuote: // "
|
|
|
+ let s = utf8ToString(bytes: source, start: start, end: index)
|
|
|
+ advance()
|
|
|
+ if let t = s {
|
|
|
+ if sawBackslash {
|
|
|
+ return decodeString(t)
|
|
|
+ } else {
|
|
|
+ return t
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return nil // Invalid UTF8
|
|
|
+ }
|
|
|
+ case asciiBackslash: // \
|
|
|
+ advance()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ return nil // Unterminated escape
|
|
|
+ }
|
|
|
+ sawBackslash = true
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ advance()
|
|
|
}
|
|
|
- }
|
|
|
- default:
|
|
|
- // Integer part cannot be empty
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ return nil // Unterminated quoted string
|
|
|
}
|
|
|
|
|
|
- // frac = decimal-point 1*DIGIT
|
|
|
- if c == asciiPeriod {
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- // decimal point must have a following digit
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- switch c {
|
|
|
- case asciiZero...asciiNine: // 0...9
|
|
|
- while c >= asciiZero && c <= asciiNine {
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
- return d
|
|
|
+ /// Parse an unsigned integer, whether or not its quoted.
|
|
|
+ /// This also handles cases such as quoted numbers that have
|
|
|
+ /// backslash escapes in them.
|
|
|
+ ///
|
|
|
+ /// This supports the full range of UInt64 (whether quoted or not)
|
|
|
+ /// unless the number is written in floating-point format. In that
|
|
|
+ /// case, we decode it with only Double precision.
|
|
|
+ internal mutating func nextUInt() throws -> UInt64 {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c == asciiDoubleQuote {
|
|
|
+ let start = index
|
|
|
+ advance()
|
|
|
+ if let u = try parseBareUInt64(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ if currentByte != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ advance()
|
|
|
+ return u
|
|
|
} else {
|
|
|
- throw JSONDecodingError.invalidUTF8
|
|
|
+ // Couldn't parse because it had a "\" in the string,
|
|
|
+ // so parse out the quoted string and then reparse
|
|
|
+ // the result to get a UInt.
|
|
|
+ index = start
|
|
|
+ let s = try nextQuotedString()
|
|
|
+ let raw = s.data(using: String.Encoding.utf8)!
|
|
|
+ let n = try raw.withUnsafeBytes {
|
|
|
+ (body: UnsafeRawBufferPointer) -> UInt64? in
|
|
|
+ if body.count > 0 {
|
|
|
+ var index = body.startIndex
|
|
|
+ let end = body.endIndex
|
|
|
+ if let u = try parseBareUInt64(
|
|
|
+ source: body,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ if index == end {
|
|
|
+ return u
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if let n = n {
|
|
|
+ return n
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
+ } else if let u = try parseBareUInt64(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ return u
|
|
|
}
|
|
|
- case asciiBackslash:
|
|
|
- return nil
|
|
|
- default:
|
|
|
- // decimal point must be followed by at least one digit
|
|
|
throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- // exp = e [ minus / plus ] 1*DIGIT
|
|
|
- if c == asciiLowerE || c == asciiUpperE {
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- // "e" must be followed by +,-, or digit
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
- if c == asciiPlus || c == asciiMinus { // + -
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- // must be at least one digit in exponent
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Parse a signed integer, quoted or not, including handling
|
|
|
+ /// backslash escapes for quoted values.
|
|
|
+ ///
|
|
|
+ /// This supports the full range of Int64 (whether quoted or not)
|
|
|
+ /// unless the number is written in floating-point format. In that
|
|
|
+ /// case, we decode it with only Double precision.
|
|
|
+ internal mutating func nextSInt() throws -> Int64 {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
- }
|
|
|
- switch c {
|
|
|
- case asciiZero...asciiNine:
|
|
|
- while c >= asciiZero && c <= asciiNine {
|
|
|
- source.formIndex(after: &index)
|
|
|
- if index == end {
|
|
|
- if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
- return d
|
|
|
+ let c = currentByte
|
|
|
+ if c == asciiDoubleQuote {
|
|
|
+ let start = index
|
|
|
+ advance()
|
|
|
+ if let s = try parseBareSInt64(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ if currentByte != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ advance()
|
|
|
+ return s
|
|
|
} else {
|
|
|
- throw JSONDecodingError.invalidUTF8
|
|
|
+ // Couldn't parse because it had a "\" in the string,
|
|
|
+ // so parse out the quoted string and then reparse
|
|
|
+ // the result as an SInt.
|
|
|
+ index = start
|
|
|
+ let s = try nextQuotedString()
|
|
|
+ let raw = s.data(using: String.Encoding.utf8)!
|
|
|
+ let n = try raw.withUnsafeBytes {
|
|
|
+ (body: UnsafeRawBufferPointer) -> Int64? in
|
|
|
+ if body.count > 0 {
|
|
|
+ var index = body.startIndex
|
|
|
+ let end = body.endIndex
|
|
|
+ if let s = try parseBareSInt64(
|
|
|
+ source: body,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ if index == end {
|
|
|
+ return s
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if let n = n {
|
|
|
+ return n
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- c = source[index]
|
|
|
- if c == asciiBackslash {
|
|
|
- return nil
|
|
|
- }
|
|
|
+ } else if let s = try parseBareSInt64(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ return s
|
|
|
}
|
|
|
- default:
|
|
|
- // must be at least one digit in exponent
|
|
|
throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
}
|
|
|
- if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) {
|
|
|
- return d
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.invalidUTF8
|
|
|
+
|
|
|
+ /// Parse the next Float value, regardless of whether it
|
|
|
+ /// is quoted, including handling backslash escapes for
|
|
|
+ /// quoted strings.
|
|
|
+ internal mutating func nextFloat() throws -> Float {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c == asciiDoubleQuote { // "
|
|
|
+ let start = index
|
|
|
+ advance()
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ if currentByte != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ advance()
|
|
|
+ return Float(d)
|
|
|
+ } else {
|
|
|
+ // Slow Path: parseBareDouble returned nil: It might be
|
|
|
+ // a valid float, but had something that
|
|
|
+ // parseBareDouble cannot directly handle. So we reset,
|
|
|
+ // try a full string parse, then examine the result:
|
|
|
+ index = start
|
|
|
+ let s = try nextQuotedString()
|
|
|
+ switch s {
|
|
|
+ case "NaN": return Float.nan
|
|
|
+ case "Inf": return Float.infinity
|
|
|
+ case "-Inf": return -Float.infinity
|
|
|
+ case "Infinity": return Float.infinity
|
|
|
+ case "-Infinity": return -Float.infinity
|
|
|
+ default:
|
|
|
+ let raw = s.data(using: String.Encoding.utf8)!
|
|
|
+ let n = try raw.withUnsafeBytes {
|
|
|
+ (body: UnsafeRawBufferPointer) -> Float? in
|
|
|
+ if body.count > 0 {
|
|
|
+ var index = body.startIndex
|
|
|
+ let end = body.endIndex
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: body,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ let f = Float(d)
|
|
|
+ if index == end && f.isFinite {
|
|
|
+ return f
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if let n = n {
|
|
|
+ return n
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ let f = Float(d)
|
|
|
+ if f.isFinite {
|
|
|
+ return f
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Returns a fully-parsed string with all backslash escapes
|
|
|
- /// correctly processed, or nil if next token is not a string.
|
|
|
- ///
|
|
|
- /// Assumes the leading quote has been verified (but not consumed)
|
|
|
- private mutating func parseOptionalQuotedString() -> String? {
|
|
|
- // Caller has already asserted that currentByte == quote here
|
|
|
- var sawBackslash = false
|
|
|
- advance()
|
|
|
- let start = index
|
|
|
- while hasMoreContent {
|
|
|
- switch currentByte {
|
|
|
- case asciiDoubleQuote: // "
|
|
|
- let s = utf8ToString(bytes: source, start: start, end: index)
|
|
|
- advance()
|
|
|
- if let t = s {
|
|
|
- if sawBackslash {
|
|
|
- return decodeString(t)
|
|
|
- } else {
|
|
|
- return t
|
|
|
- }
|
|
|
+ /// Parse the next Double value, regardless of whether it
|
|
|
+ /// is quoted, including handling backslash escapes for
|
|
|
+ /// quoted strings.
|
|
|
+ internal mutating func nextDouble() throws -> Double {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c == asciiDoubleQuote { // "
|
|
|
+ let start = index
|
|
|
+ advance()
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ if currentByte != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+ advance()
|
|
|
+ return d
|
|
|
+ } else {
|
|
|
+ // Slow Path: parseBareDouble returned nil: It might be
|
|
|
+ // a valid float, but had something that
|
|
|
+ // parseBareDouble cannot directly handle. So we reset,
|
|
|
+ // try a full string parse, then examine the result:
|
|
|
+ index = start
|
|
|
+ let s = try nextQuotedString()
|
|
|
+ switch s {
|
|
|
+ case "NaN": return Double.nan
|
|
|
+ case "Inf": return Double.infinity
|
|
|
+ case "-Inf": return -Double.infinity
|
|
|
+ case "Infinity": return Double.infinity
|
|
|
+ case "-Infinity": return -Double.infinity
|
|
|
+ default:
|
|
|
+ let raw = s.data(using: String.Encoding.utf8)!
|
|
|
+ let n = try raw.withUnsafeBytes {
|
|
|
+ (body: UnsafeRawBufferPointer) -> Double? in
|
|
|
+ if body.count > 0 {
|
|
|
+ var index = body.startIndex
|
|
|
+ let end = body.endIndex
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: body,
|
|
|
+ index: &index,
|
|
|
+ end: end
|
|
|
+ ) {
|
|
|
+ if index == end {
|
|
|
+ return d
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if let n = n {
|
|
|
+ return n
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
- return nil // Invalid UTF8
|
|
|
+ if let d = try parseBareDouble(
|
|
|
+ source: source,
|
|
|
+ index: &index,
|
|
|
+ end: source.endIndex
|
|
|
+ ) {
|
|
|
+ return d
|
|
|
+ }
|
|
|
}
|
|
|
- case asciiBackslash: // \
|
|
|
- advance()
|
|
|
+ throw JSONDecodingError.malformedNumber
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Return the contents of the following quoted string,
|
|
|
+ /// or throw an error if the next token is not a string.
|
|
|
+ internal mutating func nextQuotedString() throws -> String {
|
|
|
+ skipWhitespace()
|
|
|
guard hasMoreContent else {
|
|
|
- return nil // Unterminated escape
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
+ }
|
|
|
+ if let s = parseOptionalQuotedString() {
|
|
|
+ return s
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
- sawBackslash = true
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- advance()
|
|
|
}
|
|
|
- return nil // Unterminated quoted string
|
|
|
- }
|
|
|
|
|
|
- /// Parse an unsigned integer, whether or not its quoted.
|
|
|
- /// This also handles cases such as quoted numbers that have
|
|
|
- /// backslash escapes in them.
|
|
|
- ///
|
|
|
- /// This supports the full range of UInt64 (whether quoted or not)
|
|
|
- /// unless the number is written in floating-point format. In that
|
|
|
- /// case, we decode it with only Double precision.
|
|
|
- internal mutating func nextUInt() throws -> UInt64 {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Return the contents of the following quoted string,
|
|
|
+ /// or nil if the next token is not a string.
|
|
|
+ /// This will only throw an error if the next token starts
|
|
|
+ /// out as a string but is malformed in some way.
|
|
|
+ internal mutating func nextOptionalQuotedString() throws -> String? {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c != asciiDoubleQuote {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return try nextQuotedString()
|
|
|
}
|
|
|
- let c = currentByte
|
|
|
- if c == asciiDoubleQuote {
|
|
|
- let start = index
|
|
|
- advance()
|
|
|
- if let u = try parseBareUInt64(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
+
|
|
|
+ /// Return a Data with the decoded contents of the
|
|
|
+ /// following base-64 string.
|
|
|
+ ///
|
|
|
+ /// Notes on Google's implementation:
|
|
|
+ /// * Google's C++ implementation accepts arbitrary whitespace
|
|
|
+ /// mixed in with the base-64 characters
|
|
|
+ /// * Google's C++ implementation ignores missing '=' characters
|
|
|
+ /// but if present, there must be the exact correct number of them.
|
|
|
+ /// * Google's C++ implementation accepts both "regular" and
|
|
|
+ /// "web-safe" base-64 variants (it seems to prefer the
|
|
|
+ /// web-safe version as defined in RFC 4648
|
|
|
+ internal mutating func nextBytesValue() throws -> Data {
|
|
|
+ skipWhitespace()
|
|
|
guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
- if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ return try parseBytes(source: source, index: &index, end: source.endIndex)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Private function to help parse keywords.
|
|
|
+ private mutating func skipOptionalKeyword(bytes: [UInt8]) -> Bool {
|
|
|
+ let start = index
|
|
|
+ for b in bytes {
|
|
|
+ guard hasMoreContent else {
|
|
|
+ index = start
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ if c != b {
|
|
|
+ index = start
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ advance()
|
|
|
}
|
|
|
- advance()
|
|
|
- return u
|
|
|
- } else {
|
|
|
- // Couldn't parse because it had a "\" in the string,
|
|
|
- // so parse out the quoted string and then reparse
|
|
|
- // the result to get a UInt.
|
|
|
- index = start
|
|
|
- let s = try nextQuotedString()
|
|
|
- let raw = s.data(using: String.Encoding.utf8)!
|
|
|
- let n = try raw.withUnsafeBytes {
|
|
|
- (body: UnsafeRawBufferPointer) -> UInt64? in
|
|
|
- if body.count > 0 {
|
|
|
- var index = body.startIndex
|
|
|
- let end = body.endIndex
|
|
|
- if let u = try parseBareUInt64(source: body,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- if index == end {
|
|
|
- return u
|
|
|
- }
|
|
|
+ if hasMoreContent {
|
|
|
+ let c = currentByte
|
|
|
+ if (c >= asciiUpperA && c <= asciiUpperZ) || (c >= asciiLowerA && c <= asciiLowerZ) {
|
|
|
+ index = start
|
|
|
+ return false
|
|
|
}
|
|
|
- }
|
|
|
- return nil
|
|
|
}
|
|
|
- if let n = n {
|
|
|
- return n
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ /// If the next token is the identifier "null", consume it and return true.
|
|
|
+ internal mutating func skipOptionalNull() -> Bool {
|
|
|
+ skipWhitespace()
|
|
|
+ if hasMoreContent && currentByte == asciiLowerN {
|
|
|
+ return skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL,
|
|
|
+ ])
|
|
|
}
|
|
|
- }
|
|
|
- } else if let u = try parseBareUInt64(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
- return u
|
|
|
+ return false
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
|
|
|
- /// Parse a signed integer, quoted or not, including handling
|
|
|
- /// backslash escapes for quoted values.
|
|
|
- ///
|
|
|
- /// This supports the full range of Int64 (whether quoted or not)
|
|
|
- /// unless the number is written in floating-point format. In that
|
|
|
- /// case, we decode it with only Double precision.
|
|
|
- internal mutating func nextSInt() throws -> Int64 {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Return the following Bool "true" or "false", including
|
|
|
+ /// full processing of quoted boolean values. (Used in map
|
|
|
+ /// keys, for instance.)
|
|
|
+ internal mutating func nextBool() throws -> Bool {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let c = currentByte
|
|
|
+ switch c {
|
|
|
+ case asciiLowerF: // f
|
|
|
+ if skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE,
|
|
|
+ ]) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ case asciiLowerT: // t
|
|
|
+ if skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE,
|
|
|
+ ]) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.malformedBool
|
|
|
}
|
|
|
- let c = currentByte
|
|
|
- if c == asciiDoubleQuote {
|
|
|
- let start = index
|
|
|
- advance()
|
|
|
- if let s = try parseBareSInt64(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
+
|
|
|
+ /// Return the following Bool "true" or "false", including
|
|
|
+ /// full processing of quoted boolean values. (Used in map
|
|
|
+ /// keys, for instance.)
|
|
|
+ internal mutating func nextQuotedBool() throws -> Bool {
|
|
|
+ skipWhitespace()
|
|
|
guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ throw JSONDecodingError.unquotedMapKey
|
|
|
}
|
|
|
- advance()
|
|
|
- return s
|
|
|
- } else {
|
|
|
- // Couldn't parse because it had a "\" in the string,
|
|
|
- // so parse out the quoted string and then reparse
|
|
|
- // the result as an SInt.
|
|
|
- index = start
|
|
|
- let s = try nextQuotedString()
|
|
|
- let raw = s.data(using: String.Encoding.utf8)!
|
|
|
- let n = try raw.withUnsafeBytes {
|
|
|
- (body: UnsafeRawBufferPointer) -> Int64? in
|
|
|
- if body.count > 0 {
|
|
|
- var index = body.startIndex
|
|
|
- let end = body.endIndex
|
|
|
- if let s = try parseBareSInt64(source: body,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- if index == end {
|
|
|
- return s
|
|
|
- }
|
|
|
+ if let s = parseOptionalQuotedString() {
|
|
|
+ switch s {
|
|
|
+ case "false": return false
|
|
|
+ case "true": return true
|
|
|
+ default: break
|
|
|
}
|
|
|
- }
|
|
|
- return nil
|
|
|
- }
|
|
|
- if let n = n {
|
|
|
- return n
|
|
|
}
|
|
|
- }
|
|
|
- } else if let s = try parseBareSInt64(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
- return s
|
|
|
+ throw JSONDecodingError.malformedBool
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
|
|
|
- /// Parse the next Float value, regardless of whether it
|
|
|
- /// is quoted, including handling backslash escapes for
|
|
|
- /// quoted strings.
|
|
|
- internal mutating func nextFloat() throws -> Float {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let c = currentByte
|
|
|
- if c == asciiDoubleQuote { // "
|
|
|
- let start = index
|
|
|
- advance()
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
+ /// Returns pointer/count spanning the UTF8 bytes of the next regular
|
|
|
+ /// key or nil if the key contains a backslash (and therefore requires
|
|
|
+ /// the full string-parsing logic to properly parse).
|
|
|
+ private mutating func nextOptionalKey() throws -> UnsafeRawBufferPointer? {
|
|
|
+ skipWhitespace()
|
|
|
+ let stringStart = index
|
|
|
guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ return nil
|
|
|
}
|
|
|
advance()
|
|
|
- return Float(d)
|
|
|
- } else {
|
|
|
- // Slow Path: parseBareDouble returned nil: It might be
|
|
|
- // a valid float, but had something that
|
|
|
- // parseBareDouble cannot directly handle. So we reset,
|
|
|
- // try a full string parse, then examine the result:
|
|
|
- index = start
|
|
|
- let s = try nextQuotedString()
|
|
|
- switch s {
|
|
|
- case "NaN": return Float.nan
|
|
|
- case "Inf": return Float.infinity
|
|
|
- case "-Inf": return -Float.infinity
|
|
|
- case "Infinity": return Float.infinity
|
|
|
- case "-Infinity": return -Float.infinity
|
|
|
- default:
|
|
|
- let raw = s.data(using: String.Encoding.utf8)!
|
|
|
- let n = try raw.withUnsafeBytes {
|
|
|
- (body: UnsafeRawBufferPointer) -> Float? in
|
|
|
- if body.count > 0 {
|
|
|
- var index = body.startIndex
|
|
|
- let end = body.endIndex
|
|
|
- if let d = try parseBareDouble(source: body,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- let f = Float(d)
|
|
|
- if index == end && f.isFinite {
|
|
|
- return f
|
|
|
- }
|
|
|
- }
|
|
|
+ let nameStart = index
|
|
|
+ while hasMoreContent && currentByte != asciiDoubleQuote {
|
|
|
+ if currentByte == asciiBackslash {
|
|
|
+ index = stringStart // Reset to open quote
|
|
|
+ return nil
|
|
|
}
|
|
|
- return nil
|
|
|
- }
|
|
|
- if let n = n {
|
|
|
- return n
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
- let f = Float(d)
|
|
|
- if f.isFinite {
|
|
|
- return f
|
|
|
- }
|
|
|
- }
|
|
|
+ advance()
|
|
|
+ }
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let buff = UnsafeRawBufferPointer(
|
|
|
+ start: source.baseAddress! + nameStart,
|
|
|
+ count: index - nameStart
|
|
|
+ )
|
|
|
+ advance()
|
|
|
+ return buff
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
|
|
|
- /// Parse the next Double value, regardless of whether it
|
|
|
- /// is quoted, including handling backslash escapes for
|
|
|
- /// quoted strings.
|
|
|
- internal mutating func nextDouble() throws -> Double {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Parse a field name, look it up in the provided field name map,
|
|
|
+ /// and return the corresponding field number.
|
|
|
+ ///
|
|
|
+ /// Throws if field name cannot be parsed.
|
|
|
+ /// If it encounters an unknown field name, it throws
|
|
|
+ /// unless `options.ignoreUnknownFields` is set, in which case
|
|
|
+ /// it silently skips it.
|
|
|
+ internal mutating func nextFieldNumber(
|
|
|
+ names: _NameMap,
|
|
|
+ messageType: any Message.Type
|
|
|
+ ) throws -> Int? {
|
|
|
+ while true {
|
|
|
+ var fieldName: String
|
|
|
+ if let key = try nextOptionalKey() {
|
|
|
+ // Fast path: We parsed it as UTF8 bytes...
|
|
|
+ try skipRequiredCharacter(asciiColon) // :
|
|
|
+ if let fieldNumber = names.number(forJSONName: key) {
|
|
|
+ return fieldNumber
|
|
|
+ }
|
|
|
+ if let s = utf8ToString(bytes: key.baseAddress!, count: key.count) {
|
|
|
+ fieldName = s
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.invalidUTF8
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Slow path: We parsed a String; lookups from String are slower.
|
|
|
+ fieldName = try nextQuotedString()
|
|
|
+ try skipRequiredCharacter(asciiColon) // :
|
|
|
+ if let fieldNumber = names.number(forJSONName: fieldName) {
|
|
|
+ return fieldNumber
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if let first = fieldName.utf8.first, first == UInt8(ascii: "["),
|
|
|
+ let last = fieldName.utf8.last, last == UInt8(ascii: "]")
|
|
|
+ {
|
|
|
+ fieldName.removeFirst()
|
|
|
+ fieldName.removeLast()
|
|
|
+ if let fieldNumber = extensions.fieldNumberForProto(messageType: messageType, protoFieldName: fieldName)
|
|
|
+ {
|
|
|
+ return fieldNumber
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !options.ignoreUnknownFields {
|
|
|
+ throw JSONDecodingError.unknownField(fieldName)
|
|
|
+ }
|
|
|
+ // Unknown field, skip it and try to parse the next field name
|
|
|
+ try skipValue()
|
|
|
+ if skipOptionalObjectEnd() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ try skipRequiredComma()
|
|
|
+ }
|
|
|
}
|
|
|
- let c = currentByte
|
|
|
- if c == asciiDoubleQuote { // "
|
|
|
- let start = index
|
|
|
- advance()
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+
|
|
|
+ /// Parse the next token as a string or numeric enum value. Throws
|
|
|
+ /// unrecognizedEnumValue if the string/number can't initialize the
|
|
|
+ /// enum. Will throw other errors if the JSON is malformed.
|
|
|
+ internal mutating func nextEnumValue<E: Enum>() throws -> E? {
|
|
|
+ func throwOrIgnore() throws -> E? {
|
|
|
+ if options.ignoreUnknownFields {
|
|
|
+ return nil
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.unrecognizedEnumValue
|
|
|
+ }
|
|
|
}
|
|
|
- if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
- advance()
|
|
|
- return d
|
|
|
- } else {
|
|
|
- // Slow Path: parseBareDouble returned nil: It might be
|
|
|
- // a valid float, but had something that
|
|
|
- // parseBareDouble cannot directly handle. So we reset,
|
|
|
- // try a full string parse, then examine the result:
|
|
|
- index = start
|
|
|
- let s = try nextQuotedString()
|
|
|
- switch s {
|
|
|
- case "NaN": return Double.nan
|
|
|
- case "Inf": return Double.infinity
|
|
|
- case "-Inf": return -Double.infinity
|
|
|
- case "Infinity": return Double.infinity
|
|
|
- case "-Infinity": return -Double.infinity
|
|
|
- default:
|
|
|
- let raw = s.data(using: String.Encoding.utf8)!
|
|
|
- let n = try raw.withUnsafeBytes {
|
|
|
- (body: UnsafeRawBufferPointer) -> Double? in
|
|
|
- if body.count > 0 {
|
|
|
- var index = body.startIndex
|
|
|
- let end = body.endIndex
|
|
|
- if let d = try parseBareDouble(source: body,
|
|
|
- index: &index,
|
|
|
- end: end) {
|
|
|
- if index == end {
|
|
|
- return d
|
|
|
+ if currentByte == asciiDoubleQuote {
|
|
|
+ if let name = try nextOptionalKey() {
|
|
|
+ if let e = E(rawUTF8: name) {
|
|
|
+ return e
|
|
|
+ } else {
|
|
|
+ return try throwOrIgnore()
|
|
|
}
|
|
|
- }
|
|
|
}
|
|
|
- return nil
|
|
|
- }
|
|
|
- if let n = n {
|
|
|
- return n
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- if let d = try parseBareDouble(source: source,
|
|
|
- index: &index,
|
|
|
- end: source.endIndex) {
|
|
|
- return d
|
|
|
- }
|
|
|
+ let name = try nextQuotedString()
|
|
|
+ if let e = E(name: name) {
|
|
|
+ return e
|
|
|
+ } else {
|
|
|
+ return try throwOrIgnore()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let n = try nextSInt()
|
|
|
+ if let i = Int(exactly: n) {
|
|
|
+ if let e = E(rawValue: i) {
|
|
|
+ return e
|
|
|
+ } else {
|
|
|
+ return try throwOrIgnore()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw JSONDecodingError.numberRange
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedNumber
|
|
|
- }
|
|
|
|
|
|
- /// Return the contents of the following quoted string,
|
|
|
- /// or throw an error if the next token is not a string.
|
|
|
- internal mutating func nextQuotedString() throws -> String {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let c = currentByte
|
|
|
- if c != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
- }
|
|
|
- if let s = parseOptionalQuotedString() {
|
|
|
- return s
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
+ /// Helper for skipping a single-character token.
|
|
|
+ private mutating func skipRequiredCharacter(_ required: UInt8) throws {
|
|
|
+ skipWhitespace()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ let next = currentByte
|
|
|
+ if next == required {
|
|
|
+ advance()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.failure
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Return the contents of the following quoted string,
|
|
|
- /// or nil if the next token is not a string.
|
|
|
- /// This will only throw an error if the next token starts
|
|
|
- /// out as a string but is malformed in some way.
|
|
|
- internal mutating func nextOptionalQuotedString() throws -> String? {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- return nil
|
|
|
+ /// Skip "{", throw if that's not the next character.
|
|
|
+ internal mutating func skipRequiredObjectStart() throws {
|
|
|
+ try skipRequiredCharacter(asciiOpenCurlyBracket) // {
|
|
|
+ try incrementRecursionDepth()
|
|
|
}
|
|
|
- let c = currentByte
|
|
|
- if c != asciiDoubleQuote {
|
|
|
- return nil
|
|
|
- }
|
|
|
- return try nextQuotedString()
|
|
|
- }
|
|
|
|
|
|
- /// Return a Data with the decoded contents of the
|
|
|
- /// following base-64 string.
|
|
|
- ///
|
|
|
- /// Notes on Google's implementation:
|
|
|
- /// * Google's C++ implementation accepts arbitrary whitespace
|
|
|
- /// mixed in with the base-64 characters
|
|
|
- /// * Google's C++ implementation ignores missing '=' characters
|
|
|
- /// but if present, there must be the exact correct number of them.
|
|
|
- /// * Google's C++ implementation accepts both "regular" and
|
|
|
- /// "web-safe" base-64 variants (it seems to prefer the
|
|
|
- /// web-safe version as defined in RFC 4648
|
|
|
- internal mutating func nextBytesValue() throws -> Data {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// Skip ",", throw if that's not the next character.
|
|
|
+ internal mutating func skipRequiredComma() throws {
|
|
|
+ try skipRequiredCharacter(asciiComma)
|
|
|
}
|
|
|
- return try parseBytes(source: source, index: &index, end: source.endIndex)
|
|
|
- }
|
|
|
|
|
|
- /// Private function to help parse keywords.
|
|
|
- private mutating func skipOptionalKeyword(bytes: [UInt8]) -> Bool {
|
|
|
- let start = index
|
|
|
- for b in bytes {
|
|
|
- guard hasMoreContent else {
|
|
|
- index = start
|
|
|
- return false
|
|
|
- }
|
|
|
- let c = currentByte
|
|
|
- if c != b {
|
|
|
- index = start
|
|
|
- return false
|
|
|
- }
|
|
|
- advance()
|
|
|
- }
|
|
|
- if hasMoreContent {
|
|
|
- let c = currentByte
|
|
|
- if (c >= asciiUpperA && c <= asciiUpperZ) ||
|
|
|
- (c >= asciiLowerA && c <= asciiLowerZ) {
|
|
|
- index = start
|
|
|
- return false
|
|
|
- }
|
|
|
+ /// Skip ":", throw if that's not the next character.
|
|
|
+ internal mutating func skipRequiredColon() throws {
|
|
|
+ try skipRequiredCharacter(asciiColon)
|
|
|
}
|
|
|
- return true
|
|
|
- }
|
|
|
|
|
|
- /// If the next token is the identifier "null", consume it and return true.
|
|
|
- internal mutating func skipOptionalNull() -> Bool {
|
|
|
- skipWhitespace()
|
|
|
- if hasMoreContent && currentByte == asciiLowerN {
|
|
|
- return skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL
|
|
|
- ])
|
|
|
+ /// Skip "[", throw if that's not the next character.
|
|
|
+ internal mutating func skipRequiredArrayStart() throws {
|
|
|
+ try skipRequiredCharacter(asciiOpenSquareBracket) // [
|
|
|
}
|
|
|
- return false
|
|
|
- }
|
|
|
|
|
|
- /// Return the following Bool "true" or "false", including
|
|
|
- /// full processing of quoted boolean values. (Used in map
|
|
|
- /// keys, for instance.)
|
|
|
- internal mutating func nextBool() throws -> Bool {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let c = currentByte
|
|
|
- switch c {
|
|
|
- case asciiLowerF: // f
|
|
|
- if skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE
|
|
|
- ]) {
|
|
|
+ /// Helper for skipping optional single-character tokens.
|
|
|
+ private mutating func skipOptionalCharacter(_ c: UInt8) -> Bool {
|
|
|
+ skipWhitespace()
|
|
|
+ if hasMoreContent && currentByte == c {
|
|
|
+ advance()
|
|
|
+ return true
|
|
|
+ }
|
|
|
return false
|
|
|
- }
|
|
|
- case asciiLowerT: // t
|
|
|
- if skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE
|
|
|
- ]) {
|
|
|
- return true
|
|
|
- }
|
|
|
- default:
|
|
|
- break
|
|
|
}
|
|
|
- throw JSONDecodingError.malformedBool
|
|
|
- }
|
|
|
|
|
|
- /// Return the following Bool "true" or "false", including
|
|
|
- /// full processing of quoted boolean values. (Used in map
|
|
|
- /// keys, for instance.)
|
|
|
- internal mutating func nextQuotedBool() throws -> Bool {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// If the next non-whitespace character is "[", skip it
|
|
|
+ /// and return true. Otherwise, return false.
|
|
|
+ internal mutating func skipOptionalArrayStart() -> Bool {
|
|
|
+ skipOptionalCharacter(asciiOpenSquareBracket)
|
|
|
}
|
|
|
- if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.unquotedMapKey
|
|
|
- }
|
|
|
- if let s = parseOptionalQuotedString() {
|
|
|
- switch s {
|
|
|
- case "false": return false
|
|
|
- case "true": return true
|
|
|
- default: break
|
|
|
- }
|
|
|
- }
|
|
|
- throw JSONDecodingError.malformedBool
|
|
|
- }
|
|
|
|
|
|
- /// Returns pointer/count spanning the UTF8 bytes of the next regular
|
|
|
- /// key or nil if the key contains a backslash (and therefore requires
|
|
|
- /// the full string-parsing logic to properly parse).
|
|
|
- private mutating func nextOptionalKey() throws -> UnsafeRawBufferPointer? {
|
|
|
- skipWhitespace()
|
|
|
- let stringStart = index
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- if currentByte != asciiDoubleQuote {
|
|
|
- return nil
|
|
|
- }
|
|
|
- advance()
|
|
|
- let nameStart = index
|
|
|
- while hasMoreContent && currentByte != asciiDoubleQuote {
|
|
|
- if currentByte == asciiBackslash {
|
|
|
- index = stringStart // Reset to open quote
|
|
|
- return nil
|
|
|
- }
|
|
|
- advance()
|
|
|
- }
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ /// If the next non-whitespace character is "]", skip it
|
|
|
+ /// and return true. Otherwise, return false.
|
|
|
+ internal mutating func skipOptionalArrayEnd() -> Bool {
|
|
|
+ skipOptionalCharacter(asciiCloseSquareBracket) // ]// ]
|
|
|
}
|
|
|
- let buff = UnsafeRawBufferPointer(
|
|
|
- start: source.baseAddress! + nameStart,
|
|
|
- count: index - nameStart)
|
|
|
- advance()
|
|
|
- return buff
|
|
|
- }
|
|
|
|
|
|
- /// Parse a field name, look it up in the provided field name map,
|
|
|
- /// and return the corresponding field number.
|
|
|
- ///
|
|
|
- /// Throws if field name cannot be parsed.
|
|
|
- /// If it encounters an unknown field name, it throws
|
|
|
- /// unless `options.ignoreUnknownFields` is set, in which case
|
|
|
- /// it silently skips it.
|
|
|
- internal mutating func nextFieldNumber(
|
|
|
- names: _NameMap,
|
|
|
- messageType: any Message.Type
|
|
|
- ) throws -> Int? {
|
|
|
- while true {
|
|
|
- var fieldName: String
|
|
|
- if let key = try nextOptionalKey() {
|
|
|
- // Fast path: We parsed it as UTF8 bytes...
|
|
|
- try skipRequiredCharacter(asciiColon) // :
|
|
|
- if let fieldNumber = names.number(forJSONName: key) {
|
|
|
- return fieldNumber
|
|
|
- }
|
|
|
- if let s = utf8ToString(bytes: key.baseAddress!, count: key.count) {
|
|
|
- fieldName = s
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.invalidUTF8
|
|
|
- }
|
|
|
- } else {
|
|
|
- // Slow path: We parsed a String; lookups from String are slower.
|
|
|
- fieldName = try nextQuotedString()
|
|
|
- try skipRequiredCharacter(asciiColon) // :
|
|
|
- if let fieldNumber = names.number(forJSONName: fieldName) {
|
|
|
- return fieldNumber
|
|
|
- }
|
|
|
- }
|
|
|
- if let first = fieldName.utf8.first, first == UInt8(ascii: "["),
|
|
|
- let last = fieldName.utf8.last, last == UInt8(ascii: "]")
|
|
|
- {
|
|
|
- fieldName.removeFirst()
|
|
|
- fieldName.removeLast()
|
|
|
- if let fieldNumber = extensions.fieldNumberForProto(messageType: messageType, protoFieldName: fieldName) {
|
|
|
- return fieldNumber
|
|
|
- }
|
|
|
- }
|
|
|
- if !options.ignoreUnknownFields {
|
|
|
- throw JSONDecodingError.unknownField(fieldName)
|
|
|
- }
|
|
|
- // Unknown field, skip it and try to parse the next field name
|
|
|
- try skipValue()
|
|
|
- if skipOptionalObjectEnd() {
|
|
|
- return nil
|
|
|
- }
|
|
|
- try skipRequiredComma()
|
|
|
+ /// If the next non-whitespace character is "}", skip it
|
|
|
+ /// and return true. Otherwise, return false.
|
|
|
+ internal mutating func skipOptionalObjectEnd() -> Bool {
|
|
|
+ let result = skipOptionalCharacter(asciiCloseCurlyBracket) // }
|
|
|
+ if result {
|
|
|
+ decrementRecursionDepth()
|
|
|
+ }
|
|
|
+ return result
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Parse the next token as a string or numeric enum value. Throws
|
|
|
- /// unrecognizedEnumValue if the string/number can't initialize the
|
|
|
- /// enum. Will throw other errors if the JSON is malformed.
|
|
|
- internal mutating func nextEnumValue<E: Enum>() throws -> E? {
|
|
|
- func throwOrIgnore() throws -> E? {
|
|
|
- if options.ignoreUnknownFields {
|
|
|
- return nil
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.unrecognizedEnumValue
|
|
|
- }
|
|
|
- }
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- if currentByte == asciiDoubleQuote {
|
|
|
- if let name = try nextOptionalKey() {
|
|
|
- if let e = E(rawUTF8: name) {
|
|
|
- return e
|
|
|
+ /// Return the next complete JSON structure as a string.
|
|
|
+ /// For example, this might return "true", or "123.456",
|
|
|
+ /// or "{\"foo\": 7, \"bar\": [8, 9]}"
|
|
|
+ ///
|
|
|
+ /// Used by Any to get the upcoming JSON value as a string.
|
|
|
+ /// Note: The value might be an object or array.
|
|
|
+ internal mutating func skip() throws -> String {
|
|
|
+ skipWhitespace()
|
|
|
+ let start = index
|
|
|
+ try skipValue()
|
|
|
+ if let s = utf8ToString(bytes: source, start: start, end: index) {
|
|
|
+ return s
|
|
|
} else {
|
|
|
- return try throwOrIgnore()
|
|
|
- }
|
|
|
- }
|
|
|
- let name = try nextQuotedString()
|
|
|
- if let e = E(name: name) {
|
|
|
- return e
|
|
|
- } else {
|
|
|
- return try throwOrIgnore()
|
|
|
- }
|
|
|
- } else {
|
|
|
- let n = try nextSInt()
|
|
|
- if let i = Int(exactly: n) {
|
|
|
- if let e = E(rawValue: i) {
|
|
|
- return e
|
|
|
- } else {
|
|
|
- return try throwOrIgnore()
|
|
|
+ throw JSONDecodingError.invalidUTF8
|
|
|
}
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.numberRange
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Helper for skipping a single-character token.
|
|
|
- private mutating func skipRequiredCharacter(_ required: UInt8) throws {
|
|
|
- skipWhitespace()
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- let next = currentByte
|
|
|
- if next == required {
|
|
|
- advance()
|
|
|
- return
|
|
|
- }
|
|
|
- throw JSONDecodingError.failure
|
|
|
- }
|
|
|
-
|
|
|
- /// Skip "{", throw if that's not the next character.
|
|
|
- internal mutating func skipRequiredObjectStart() throws {
|
|
|
- try skipRequiredCharacter(asciiOpenCurlyBracket) // {
|
|
|
- try incrementRecursionDepth()
|
|
|
- }
|
|
|
-
|
|
|
- /// Skip ",", throw if that's not the next character.
|
|
|
- internal mutating func skipRequiredComma() throws {
|
|
|
- try skipRequiredCharacter(asciiComma)
|
|
|
- }
|
|
|
-
|
|
|
- /// Skip ":", throw if that's not the next character.
|
|
|
- internal mutating func skipRequiredColon() throws {
|
|
|
- try skipRequiredCharacter(asciiColon)
|
|
|
- }
|
|
|
-
|
|
|
- /// Skip "[", throw if that's not the next character.
|
|
|
- internal mutating func skipRequiredArrayStart() throws {
|
|
|
- try skipRequiredCharacter(asciiOpenSquareBracket) // [
|
|
|
- }
|
|
|
-
|
|
|
- /// Helper for skipping optional single-character tokens.
|
|
|
- private mutating func skipOptionalCharacter(_ c: UInt8) -> Bool {
|
|
|
- skipWhitespace()
|
|
|
- if hasMoreContent && currentByte == c {
|
|
|
- advance()
|
|
|
- return true
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- /// If the next non-whitespace character is "[", skip it
|
|
|
- /// and return true. Otherwise, return false.
|
|
|
- internal mutating func skipOptionalArrayStart() -> Bool {
|
|
|
- return skipOptionalCharacter(asciiOpenSquareBracket)
|
|
|
- }
|
|
|
-
|
|
|
- /// If the next non-whitespace character is "]", skip it
|
|
|
- /// and return true. Otherwise, return false.
|
|
|
- internal mutating func skipOptionalArrayEnd() -> Bool {
|
|
|
- return skipOptionalCharacter(asciiCloseSquareBracket) // ]
|
|
|
- }
|
|
|
-
|
|
|
- /// If the next non-whitespace character is "}", skip it
|
|
|
- /// and return true. Otherwise, return false.
|
|
|
- internal mutating func skipOptionalObjectEnd() -> Bool {
|
|
|
- let result = skipOptionalCharacter(asciiCloseCurlyBracket) // }
|
|
|
- if result {
|
|
|
- decrementRecursionDepth()
|
|
|
- }
|
|
|
- return result
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the next complete JSON structure as a string.
|
|
|
- /// For example, this might return "true", or "123.456",
|
|
|
- /// or "{\"foo\": 7, \"bar\": [8, 9]}"
|
|
|
- ///
|
|
|
- /// Used by Any to get the upcoming JSON value as a string.
|
|
|
- /// Note: The value might be an object or array.
|
|
|
- internal mutating func skip() throws -> String {
|
|
|
- skipWhitespace()
|
|
|
- let start = index
|
|
|
- try skipValue()
|
|
|
- if let s = utf8ToString(bytes: source, start: start, end: index) {
|
|
|
- return s
|
|
|
- } else {
|
|
|
- throw JSONDecodingError.invalidUTF8
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /// Advance index past the next value. This is used
|
|
|
- /// by skip() and by unknown field handling.
|
|
|
- /// Note: This handles objects {...} recursively but arrays [...] non-recursively
|
|
|
- /// This avoids us requiring excessive stack space for deeply nested
|
|
|
- /// arrays (which are not included in the recursion budget check).
|
|
|
- private mutating func skipValue() throws {
|
|
|
- skipWhitespace()
|
|
|
- var totalArrayDepth = 0
|
|
|
- while true {
|
|
|
- var arrayDepth = 0
|
|
|
- while skipOptionalArrayStart() {
|
|
|
- arrayDepth += 1
|
|
|
- }
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- switch currentByte {
|
|
|
- case asciiDoubleQuote: // " begins a string
|
|
|
- try skipString()
|
|
|
- case asciiOpenCurlyBracket: // { begins an object
|
|
|
- try skipObject()
|
|
|
- case asciiCloseSquareBracket: // ] ends an empty array
|
|
|
- if arrayDepth == 0 {
|
|
|
- throw JSONDecodingError.failure
|
|
|
- }
|
|
|
- // We also close out [[]] or [[[]]] here
|
|
|
- while arrayDepth > 0 && skipOptionalArrayEnd() {
|
|
|
- arrayDepth -= 1
|
|
|
+ /// Advance index past the next value. This is used
|
|
|
+ /// by skip() and by unknown field handling.
|
|
|
+ /// Note: This handles objects {...} recursively but arrays [...] non-recursively
|
|
|
+ /// This avoids us requiring excessive stack space for deeply nested
|
|
|
+ /// arrays (which are not included in the recursion budget check).
|
|
|
+ private mutating func skipValue() throws {
|
|
|
+ skipWhitespace()
|
|
|
+ var totalArrayDepth = 0
|
|
|
+ while true {
|
|
|
+ var arrayDepth = 0
|
|
|
+ while skipOptionalArrayStart() {
|
|
|
+ arrayDepth += 1
|
|
|
}
|
|
|
- case asciiLowerN: // n must be null
|
|
|
- if !skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL
|
|
|
- ]) {
|
|
|
+ guard hasMoreContent else {
|
|
|
throw JSONDecodingError.truncated
|
|
|
}
|
|
|
- case asciiLowerF: // f must be false
|
|
|
- if !skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE
|
|
|
- ]) {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ switch currentByte {
|
|
|
+ case asciiDoubleQuote: // " begins a string
|
|
|
+ try skipString()
|
|
|
+ case asciiOpenCurlyBracket: // { begins an object
|
|
|
+ try skipObject()
|
|
|
+ case asciiCloseSquareBracket: // ] ends an empty array
|
|
|
+ if arrayDepth == 0 {
|
|
|
+ throw JSONDecodingError.failure
|
|
|
+ }
|
|
|
+ // We also close out [[]] or [[[]]] here
|
|
|
+ while arrayDepth > 0 && skipOptionalArrayEnd() {
|
|
|
+ arrayDepth -= 1
|
|
|
+ }
|
|
|
+ case asciiLowerN: // n must be null
|
|
|
+ if !skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL,
|
|
|
+ ]) {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ case asciiLowerF: // f must be false
|
|
|
+ if !skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE,
|
|
|
+ ]) {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ case asciiLowerT: // t must be true
|
|
|
+ if !skipOptionalKeyword(bytes: [
|
|
|
+ asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE,
|
|
|
+ ]) {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ default: // everything else is a number token
|
|
|
+ _ = try nextDouble()
|
|
|
}
|
|
|
- case asciiLowerT: // t must be true
|
|
|
- if !skipOptionalKeyword(bytes: [
|
|
|
- asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE
|
|
|
- ]) {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ totalArrayDepth += arrayDepth
|
|
|
+ while totalArrayDepth > 0 && skipOptionalArrayEnd() {
|
|
|
+ totalArrayDepth -= 1
|
|
|
+ }
|
|
|
+ if totalArrayDepth > 0 {
|
|
|
+ try skipRequiredComma()
|
|
|
+ } else {
|
|
|
+ return
|
|
|
}
|
|
|
- default: // everything else is a number token
|
|
|
- _ = try nextDouble()
|
|
|
}
|
|
|
- totalArrayDepth += arrayDepth
|
|
|
- while totalArrayDepth > 0 && skipOptionalArrayEnd() {
|
|
|
- totalArrayDepth -= 1
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Advance the index past the next complete {...} construct.
|
|
|
+ private mutating func skipObject() throws {
|
|
|
+ try skipRequiredObjectStart()
|
|
|
+ if skipOptionalObjectEnd() {
|
|
|
+ return
|
|
|
}
|
|
|
- if totalArrayDepth > 0 {
|
|
|
+ while true {
|
|
|
+ skipWhitespace()
|
|
|
+ try skipString()
|
|
|
+ try skipRequiredColon()
|
|
|
+ try skipValue()
|
|
|
+ if skipOptionalObjectEnd() {
|
|
|
+ return
|
|
|
+ }
|
|
|
try skipRequiredComma()
|
|
|
- } else {
|
|
|
- return
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /// Advance the index past the next complete {...} construct.
|
|
|
- private mutating func skipObject() throws {
|
|
|
- try skipRequiredObjectStart()
|
|
|
- if skipOptionalObjectEnd() {
|
|
|
- return
|
|
|
- }
|
|
|
- while true {
|
|
|
- skipWhitespace()
|
|
|
- try skipString()
|
|
|
- try skipRequiredColon()
|
|
|
- try skipValue()
|
|
|
- if skipOptionalObjectEnd() {
|
|
|
- return
|
|
|
- }
|
|
|
- try skipRequiredComma()
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- /// Advance the index past the next complete quoted string.
|
|
|
- ///
|
|
|
- // Caveat: This does not fully validate; it will accept
|
|
|
- // strings that have malformed \ escapes.
|
|
|
- //
|
|
|
- // It would be nice to do better, but I don't think it's critical,
|
|
|
- // since there are many reasons that strings (and other tokens for
|
|
|
- // that matter) may be skippable but not parsable. For example:
|
|
|
- // Old clients that don't know new field types will skip fields
|
|
|
- // they don't know; newer clients may reject the same input due to
|
|
|
- // schema mismatches or other issues.
|
|
|
- private mutating func skipString() throws {
|
|
|
- guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
- if currentByte != asciiDoubleQuote {
|
|
|
- throw JSONDecodingError.malformedString
|
|
|
- }
|
|
|
- advance()
|
|
|
- while hasMoreContent {
|
|
|
- let c = currentByte
|
|
|
- switch c {
|
|
|
- case asciiDoubleQuote:
|
|
|
- advance()
|
|
|
- return
|
|
|
- case asciiBackslash:
|
|
|
- advance()
|
|
|
+ /// Advance the index past the next complete quoted string.
|
|
|
+ ///
|
|
|
+ // Caveat: This does not fully validate; it will accept
|
|
|
+ // strings that have malformed \ escapes.
|
|
|
+ //
|
|
|
+ // It would be nice to do better, but I don't think it's critical,
|
|
|
+ // since there are many reasons that strings (and other tokens for
|
|
|
+ // that matter) may be skippable but not parsable. For example:
|
|
|
+ // Old clients that don't know new field types will skip fields
|
|
|
+ // they don't know; newer clients may reject the same input due to
|
|
|
+ // schema mismatches or other issues.
|
|
|
+ private mutating func skipString() throws {
|
|
|
guard hasMoreContent else {
|
|
|
- throw JSONDecodingError.truncated
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ if currentByte != asciiDoubleQuote {
|
|
|
+ throw JSONDecodingError.malformedString
|
|
|
}
|
|
|
advance()
|
|
|
- default:
|
|
|
- advance()
|
|
|
- }
|
|
|
+ while hasMoreContent {
|
|
|
+ let c = currentByte
|
|
|
+ switch c {
|
|
|
+ case asciiDoubleQuote:
|
|
|
+ advance()
|
|
|
+ return
|
|
|
+ case asciiBackslash:
|
|
|
+ advance()
|
|
|
+ guard hasMoreContent else {
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
+ }
|
|
|
+ advance()
|
|
|
+ default:
|
|
|
+ advance()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw JSONDecodingError.truncated
|
|
|
}
|
|
|
- throw JSONDecodingError.truncated
|
|
|
- }
|
|
|
}
|