| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| import Foundation |
| |
| private enum CollectionHeader { |
| static let trackingRef: UInt8 = 0b0000_0001 |
| static let hasNull: UInt8 = 0b0000_0010 |
| static let declaredElementType: UInt8 = 0b0000_0100 |
| static let sameType: UInt8 = 0b0000_1000 |
| } |
| |
| private enum MapHeader { |
| static let trackingKeyRef: UInt8 = 0b0000_0001 |
| static let keyNull: UInt8 = 0b0000_0010 |
| static let declaredKeyType: UInt8 = 0b0000_0100 |
| |
| static let trackingValueRef: UInt8 = 0b0000_1000 |
| static let valueNull: UInt8 = 0b0001_0000 |
| static let declaredValueType: UInt8 = 0b0010_0000 |
| } |
| |
| private func primitiveArrayTypeID<Element: Serializer>(for _: Element.Type) -> TypeId? { |
| if Element.self == UInt8.self { return .uint8Array } |
| if Element.self == Bool.self { return .boolArray } |
| if Element.self == Int8.self { return .int8Array } |
| if Element.self == Int16.self { return .int16Array } |
| if Element.self == Int32.self { return .int32Array } |
| if Element.self == Int64.self { return .int64Array } |
| if Element.self == UInt16.self { return .uint16Array } |
| if Element.self == UInt32.self { return .uint32Array } |
| if Element.self == UInt64.self { return .uint64Array } |
| if Element.self == Float16.self { return .float16Array } |
| if Element.self == BFloat16.self { return .bfloat16Array } |
| if Element.self == Float.self { return .float32Array } |
| if Element.self == Double.self { return .float64Array } |
| return nil |
| } |
| |
| private let hostIsLittleEndian = Int(littleEndian: 1) == 1 |
| |
| @inline(__always) |
| private func uncheckedArrayCast<From, To>(_ array: [From], to _: To.Type) -> [To] { |
| assert(From.self == To.self) |
| return unsafeBitCast(array, to: [To].self) |
| } |
| |
| @inline(__always) |
| private func readArrayUninitialized<Element>( |
| count: Int, |
| _ initializer: (UnsafeMutablePointer<Element>) throws -> Void |
| ) rethrows -> [Element] { |
| try [Element](unsafeUninitializedCapacity: count) { destination, initializedCount in |
| if count > 0 { |
| try initializer(destination.baseAddress!) |
| } |
| initializedCount = count |
| } |
| } |
| |
| private func writePrimitiveArray<Element: Serializer>(_ value: [Element], context: WriteContext) { |
| if Element.self == UInt8.self { |
| let bytes = uncheckedArrayCast(value, to: UInt8.self) |
| context.buffer.writeVarUInt32(UInt32(bytes.count)) |
| context.buffer.writeBytes(bytes) |
| return |
| } |
| |
| if Element.self == Bool.self { |
| let bools = uncheckedArrayCast(value, to: Bool.self) |
| context.buffer.writeVarUInt32(UInt32(bools.count)) |
| for item in bools { |
| context.buffer.writeUInt8(item ? 1 : 0) |
| } |
| return |
| } |
| |
| if Element.self == Int8.self { |
| let values = uncheckedArrayCast(value, to: Int8.self) |
| context.buffer.writeVarUInt32(UInt32(values.count)) |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| return |
| } |
| |
| if Element.self == Int16.self { |
| let values = uncheckedArrayCast(value, to: Int16.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 2)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeInt16(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == Int32.self { |
| let values = uncheckedArrayCast(value, to: Int32.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 4)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeInt32(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == UInt32.self { |
| let values = uncheckedArrayCast(value, to: UInt32.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 4)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeUInt32(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == Int64.self { |
| let values = uncheckedArrayCast(value, to: Int64.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 8)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeInt64(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == UInt64.self { |
| let values = uncheckedArrayCast(value, to: UInt64.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 8)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeUInt64(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == UInt16.self { |
| let values = uncheckedArrayCast(value, to: UInt16.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 2)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeUInt16(item) |
| } |
| } |
| return |
| } |
| |
| if Element.self == Float16.self { |
| let values = uncheckedArrayCast(value, to: Float16.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 2)) |
| for item in values { |
| context.buffer.writeUInt16(item.bitPattern) |
| } |
| return |
| } |
| |
| if Element.self == BFloat16.self { |
| let values = uncheckedArrayCast(value, to: BFloat16.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 2)) |
| for item in values { |
| context.buffer.writeUInt16(item.rawValue) |
| } |
| return |
| } |
| |
| if Element.self == Float.self { |
| let values = uncheckedArrayCast(value, to: Float.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 4)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeFloat32(item) |
| } |
| } |
| return |
| } |
| |
| let values = uncheckedArrayCast(value, to: Double.self) |
| context.buffer.writeVarUInt32(UInt32(values.count * 8)) |
| if hostIsLittleEndian { |
| values.withUnsafeBytes { rawBytes in |
| context.buffer.writeBytes(rawBytes) |
| } |
| } else { |
| for item in values { |
| context.buffer.writeFloat64(item) |
| } |
| } |
| } |
| |
| private func readPrimitiveArray<Element: Serializer>(_ context: ReadContext) throws -> [Element] { |
| let payloadSize = Int(try context.buffer.readVarUInt32()) |
| try context.ensureRemainingBytes(payloadSize, label: "primitive_array_payload") |
| |
| if Element.self == UInt8.self { |
| try context.ensureCollectionLength(payloadSize, label: "uint8_array") |
| let bytes = try context.buffer.readBytes(count: payloadSize) |
| return uncheckedArrayCast(bytes, to: Element.self) |
| } |
| |
| if Element.self == Bool.self { |
| try context.ensureCollectionLength(payloadSize, label: "bool_array") |
| let out = try readArrayUninitialized(count: payloadSize) { destination in |
| for index in 0..<payloadSize { |
| destination.advanced(by: index).initialize(to: try context.buffer.readUInt8() != 0) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == Int8.self { |
| try context.ensureCollectionLength(payloadSize, label: "int8_array") |
| var out = Array(repeating: Int8(0), count: payloadSize) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == Int16.self { |
| if payloadSize % 2 != 0 { throw ForyError.invalidData("int16 array payload size mismatch") } |
| let count = payloadSize / 2 |
| try context.ensureCollectionLength(count, label: "int16_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: Int16(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readInt16()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == Int32.self { |
| if payloadSize % 4 != 0 { throw ForyError.invalidData("int32 array payload size mismatch") } |
| let count = payloadSize / 4 |
| try context.ensureCollectionLength(count, label: "int32_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: Int32(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readInt32()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == UInt32.self { |
| if payloadSize % 4 != 0 { throw ForyError.invalidData("uint32 array payload size mismatch") } |
| let count = payloadSize / 4 |
| try context.ensureCollectionLength(count, label: "uint32_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: UInt32(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readUInt32()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == Int64.self { |
| if payloadSize % 8 != 0 { throw ForyError.invalidData("int64 array payload size mismatch") } |
| let count = payloadSize / 8 |
| try context.ensureCollectionLength(count, label: "int64_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: Int64(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readInt64()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == UInt64.self { |
| if payloadSize % 8 != 0 { throw ForyError.invalidData("uint64 array payload size mismatch") } |
| let count = payloadSize / 8 |
| try context.ensureCollectionLength(count, label: "uint64_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: UInt64(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readUInt64()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == UInt16.self { |
| if payloadSize % 2 != 0 { throw ForyError.invalidData("uint16 array payload size mismatch") } |
| let count = payloadSize / 2 |
| try context.ensureCollectionLength(count, label: "uint16_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: UInt16(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readUInt16()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if Element.self == Float16.self { |
| if payloadSize % 2 != 0 { throw ForyError.invalidData("float16 array payload size mismatch") } |
| let count = payloadSize / 2 |
| try context.ensureCollectionLength(count, label: "float16_array") |
| let values = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: Float16(bitPattern: try context.buffer.readUInt16())) |
| } |
| } |
| return uncheckedArrayCast(values, to: Element.self) |
| } |
| |
| if Element.self == BFloat16.self { |
| if payloadSize % 2 != 0 { throw ForyError.invalidData("bfloat16 array payload size mismatch") } |
| let count = payloadSize / 2 |
| try context.ensureCollectionLength(count, label: "bfloat16_array") |
| let values = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: BFloat16(rawValue: try context.buffer.readUInt16())) |
| } |
| } |
| return uncheckedArrayCast(values, to: Element.self) |
| } |
| |
| if Element.self == Float.self { |
| if payloadSize % 4 != 0 { throw ForyError.invalidData("float32 array payload size mismatch") } |
| let count = payloadSize / 4 |
| try context.ensureCollectionLength(count, label: "float32_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: Float(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readFloat32()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| if payloadSize % 8 != 0 { throw ForyError.invalidData("float64 array payload size mismatch") } |
| let count = payloadSize / 8 |
| try context.ensureCollectionLength(count, label: "float64_array") |
| if hostIsLittleEndian { |
| var out = Array(repeating: Double(0), count: count) |
| try out.withUnsafeMutableBytes { rawBytes in |
| try context.buffer.readBytes(into: rawBytes) |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| let out = try readArrayUninitialized(count: count) { destination in |
| for index in 0..<count { |
| destination.advanced(by: index).initialize(to: try context.buffer.readFloat64()) |
| } |
| } |
| return uncheckedArrayCast(out, to: Element.self) |
| } |
| |
| extension Array: Serializer where Element: Serializer { |
| public static func foryDefault() -> [Element] { |
| [] |
| } |
| |
| public static var staticTypeId: TypeId { |
| // Primitive Swift arrays must use ARRAY ids in protocol, not LIST. |
| primitiveArrayTypeID(for: Element.self) ?? .list |
| } |
| |
| public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws { |
| if Element.self == UInt8.self, context.compatible { |
| context.buffer.writeUInt8(UInt8(truncatingIfNeeded: TypeId.binary.rawValue)) |
| return |
| } |
| context.buffer.writeUInt8(UInt8(truncatingIfNeeded: staticTypeId.rawValue)) |
| } |
| |
| public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? { |
| let rawTypeID = try context.buffer.readVarUInt32() |
| guard let actualTypeID = TypeId(rawValue: rawTypeID) else { |
| throw ForyError.invalidData("unknown type id \(rawTypeID)") |
| } |
| |
| if Element.self == UInt8.self { |
| if actualTypeID == .uint8Array || actualTypeID == .binary { |
| return nil |
| } |
| throw ForyError.typeMismatch(expected: TypeId.uint8Array.rawValue, actual: rawTypeID) |
| } |
| |
| let expectedTypeID = staticTypeId |
| if actualTypeID != expectedTypeID { |
| throw ForyError.typeMismatch(expected: expectedTypeID.rawValue, actual: rawTypeID) |
| } |
| return nil |
| } |
| |
| public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws { |
| if primitiveArrayTypeID(for: Element.self) != nil { |
| writePrimitiveArray(self, context: context) |
| return |
| } |
| |
| let buffer = context.buffer |
| buffer.writeVarUInt32(UInt32(self.count)) |
| if self.isEmpty { |
| return |
| } |
| |
| let hasNull = Element.isNullableType && self.contains(where: { $0.foryIsNone }) |
| let trackRef = context.trackRef && Element.isRefType |
| let declaredElementType = hasGenerics && !TypeId.needsTypeInfoForField(Element.staticTypeId) |
| let dynamicElementType = Element.staticTypeId == .unknown |
| |
| var header: UInt8 = dynamicElementType ? 0 : CollectionHeader.sameType |
| if trackRef { |
| header |= CollectionHeader.trackingRef |
| } |
| if hasNull { |
| header |= CollectionHeader.hasNull |
| } |
| if declaredElementType { |
| header |= CollectionHeader.declaredElementType |
| } |
| |
| buffer.writeUInt8(header) |
| if !dynamicElementType && !declaredElementType { |
| try Element.foryWriteStaticTypeInfo(context) |
| } |
| |
| if dynamicElementType { |
| let refMode: RefMode |
| if trackRef { |
| refMode = .tracking |
| } else if hasNull { |
| refMode = .nullOnly |
| } else { |
| refMode = .none |
| } |
| for element in self { |
| try element.foryWrite(context, refMode: refMode, writeTypeInfo: true, hasGenerics: hasGenerics) |
| } |
| return |
| } |
| |
| if trackRef { |
| for element in self { |
| try element.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics) |
| } |
| } else if hasNull { |
| for element in self { |
| if element.foryIsNone { |
| buffer.writeInt8(RefFlag.null.rawValue) |
| } else { |
| buffer.writeInt8(RefFlag.notNullValue.rawValue) |
| try element.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| } |
| } else { |
| for element in self { |
| try element.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| } |
| } |
| |
| public static func foryReadData(_ context: ReadContext) throws -> [Element] { |
| if primitiveArrayTypeID(for: Element.self) != nil { |
| return try readPrimitiveArray(context) |
| } |
| |
| let buffer = context.buffer |
| let length = Int(try buffer.readVarUInt32()) |
| try context.ensureCollectionLength(length, label: "array") |
| if length == 0 { |
| return [] |
| } |
| |
| let header = try buffer.readUInt8() |
| let trackRef = (header & CollectionHeader.trackingRef) != 0 |
| let hasNull = (header & CollectionHeader.hasNull) != 0 |
| let declared = (header & CollectionHeader.declaredElementType) != 0 |
| let sameType = (header & CollectionHeader.sameType) != 0 |
| if !sameType { |
| if trackRef { |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .tracking, readTypeInfo: true) |
| ) |
| } |
| } |
| } |
| |
| if hasNull { |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| let refFlag = try buffer.readInt8() |
| if refFlag == RefFlag.null.rawValue { |
| destination.advanced(by: index).initialize(to: Element.foryDefault()) |
| } else if refFlag == RefFlag.notNullValue.rawValue { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .none, readTypeInfo: true) |
| ) |
| } else { |
| throw ForyError.refError("invalid nullability flag \(refFlag)") |
| } |
| } |
| } |
| } |
| |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .none, readTypeInfo: true) |
| ) |
| } |
| } |
| } |
| |
| let elementTypeInfo = declared ? nil : try Element.foryReadTypeInfo(context) |
| return try context.withTypeInfo(elementTypeInfo, for: Element.self) { |
| if trackRef { |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .tracking, readTypeInfo: false) |
| ) |
| } |
| } |
| } |
| |
| if hasNull { |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| let refFlag = try buffer.readInt8() |
| if refFlag == RefFlag.null.rawValue { |
| destination.advanced(by: index).initialize(to: Element.foryDefault()) |
| } else if refFlag == RefFlag.notNullValue.rawValue { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .none, readTypeInfo: false) |
| ) |
| } else { |
| throw ForyError.refError("invalid nullability flag \(refFlag)") |
| } |
| } |
| } |
| } |
| |
| return try readArrayUninitialized(count: length) { destination in |
| for index in 0..<length { |
| destination.advanced(by: index).initialize( |
| to: try Element.foryRead(context, refMode: .none, readTypeInfo: false) |
| ) |
| } |
| } |
| } |
| } |
| } |
| |
| extension Set: Serializer where Element: Serializer & Hashable { |
| public static func foryDefault() -> Set<Element> { [] } |
| |
| public static var staticTypeId: TypeId { .set } |
| |
| public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws { |
| context.writeStaticTypeInfo(staticTypeId) |
| } |
| |
| public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? { |
| try context.readStaticTypeInfo(staticTypeId) |
| } |
| |
| public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws { |
| try Array(self).foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| |
| public static func foryReadData(_ context: ReadContext) throws -> Set<Element> { |
| Set(try [Element].foryReadData(context)) |
| } |
| } |
| |
| extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serializer { |
| public static func foryDefault() -> [Key: Value] { [:] } |
| |
| public static var staticTypeId: TypeId { .map } |
| |
| public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws { |
| context.writeStaticTypeInfo(staticTypeId) |
| } |
| |
| public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? { |
| try context.readStaticTypeInfo(staticTypeId) |
| } |
| |
| public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws { |
| context.buffer.writeVarUInt32(UInt32(self.count)) |
| if self.isEmpty { |
| return |
| } |
| |
| let trackKeyRef = context.trackRef && Key.isRefType |
| let trackValueRef = context.trackRef && Value.isRefType |
| let keyDeclared = hasGenerics && !TypeId.needsTypeInfoForField(Key.staticTypeId) |
| let valueDeclared = hasGenerics && !TypeId.needsTypeInfoForField(Value.staticTypeId) |
| let keyDynamicType = Key.staticTypeId == .unknown |
| let valueDynamicType = Value.staticTypeId == .unknown |
| |
| if keyDynamicType || valueDynamicType { |
| for pair in self { |
| let keyIsNil = pair.key.foryIsNone |
| let valueIsNil = pair.value.foryIsNone |
| var header: UInt8 = 0 |
| if trackKeyRef { |
| header |= MapHeader.trackingKeyRef |
| } |
| if trackValueRef { |
| header |= MapHeader.trackingValueRef |
| } |
| if keyIsNil { |
| header |= MapHeader.keyNull |
| } else if !keyDynamicType && keyDeclared { |
| header |= MapHeader.declaredKeyType |
| } |
| if valueIsNil { |
| header |= MapHeader.valueNull |
| } else if !valueDynamicType && valueDeclared { |
| header |= MapHeader.declaredValueType |
| } |
| context.buffer.writeUInt8(header) |
| |
| if keyIsNil && valueIsNil { |
| continue |
| } |
| if keyIsNil { |
| if !valueDeclared { |
| if valueDynamicType { |
| try pair.value.foryWriteTypeInfo(context) |
| } else { |
| try Value.foryWriteStaticTypeInfo(context) |
| } |
| } |
| if trackValueRef { |
| try pair.value.foryWrite( |
| context, |
| refMode: .tracking, |
| writeTypeInfo: false, |
| hasGenerics: hasGenerics |
| ) |
| } else { |
| try pair.value.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| continue |
| } |
| |
| if valueIsNil { |
| if !keyDeclared { |
| if keyDynamicType { |
| try pair.key.foryWriteTypeInfo(context) |
| } else { |
| try Key.foryWriteStaticTypeInfo(context) |
| } |
| } |
| if trackKeyRef { |
| try pair.key.foryWrite( |
| context, |
| refMode: .tracking, |
| writeTypeInfo: false, |
| hasGenerics: hasGenerics |
| ) |
| } else { |
| try pair.key.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| continue |
| } |
| |
| context.buffer.writeUInt8(1) |
| |
| if !keyDeclared { |
| if keyDynamicType { |
| try pair.key.foryWriteTypeInfo(context) |
| } else { |
| try Key.foryWriteStaticTypeInfo(context) |
| } |
| } |
| if !valueDeclared { |
| if valueDynamicType { |
| try pair.value.foryWriteTypeInfo(context) |
| } else { |
| try Value.foryWriteStaticTypeInfo(context) |
| } |
| } |
| |
| if trackKeyRef { |
| try pair.key.foryWrite( |
| context, |
| refMode: .tracking, |
| writeTypeInfo: false, |
| hasGenerics: hasGenerics |
| ) |
| } else { |
| try pair.key.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| if trackValueRef { |
| try pair.value.foryWrite( |
| context, |
| refMode: .tracking, |
| writeTypeInfo: false, |
| hasGenerics: hasGenerics |
| ) |
| } else { |
| try pair.value.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| } |
| return |
| } |
| |
| var iterator = makeIterator() |
| var pendingPair = iterator.next() |
| |
| while let pair = pendingPair { |
| let keyIsNil = pair.key.foryIsNone |
| let valueIsNil = pair.value.foryIsNone |
| |
| if keyIsNil || valueIsNil { |
| var header: UInt8 = 0 |
| if trackKeyRef { |
| header |= MapHeader.trackingKeyRef |
| } |
| if trackValueRef { |
| header |= MapHeader.trackingValueRef |
| } |
| if keyIsNil { header |= MapHeader.keyNull } |
| if valueIsNil { header |= MapHeader.valueNull } |
| if !keyIsNil && keyDeclared { header |= MapHeader.declaredKeyType } |
| if !valueIsNil && valueDeclared { header |= MapHeader.declaredValueType } |
| |
| context.buffer.writeUInt8(header) |
| if !keyIsNil { |
| if !keyDeclared { |
| try Key.foryWriteStaticTypeInfo(context) |
| } |
| if trackKeyRef { |
| try pair.key.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics) |
| } else { |
| try pair.key.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| } |
| if !valueIsNil { |
| if !valueDeclared { |
| try Value.foryWriteStaticTypeInfo(context) |
| } |
| if trackValueRef { |
| try pair.value.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics) |
| } else { |
| try pair.value.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| } |
| pendingPair = iterator.next() |
| continue |
| } |
| |
| var header: UInt8 = 0 |
| if trackKeyRef { header |= MapHeader.trackingKeyRef } |
| if trackValueRef { header |= MapHeader.trackingValueRef } |
| if keyDeclared { header |= MapHeader.declaredKeyType } |
| if valueDeclared { header |= MapHeader.declaredValueType } |
| |
| context.buffer.writeUInt8(header) |
| let chunkSizeOffset = context.buffer.count |
| context.buffer.writeUInt8(0) |
| |
| if !keyDeclared { |
| try Key.foryWriteStaticTypeInfo(context) |
| } |
| if !valueDeclared { |
| try Value.foryWriteStaticTypeInfo(context) |
| } |
| |
| var chunkSize: UInt8 = 0 |
| while chunkSize < UInt8.max, let current = pendingPair { |
| if current.key.foryIsNone || current.value.foryIsNone { |
| break |
| } |
| if trackKeyRef { |
| try current.key.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics) |
| } else { |
| try current.key.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| if trackValueRef { |
| try current.value.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics) |
| } else { |
| try current.value.foryWriteData(context, hasGenerics: hasGenerics) |
| } |
| chunkSize &+= 1 |
| pendingPair = iterator.next() |
| } |
| context.buffer.setByte(at: chunkSizeOffset, to: chunkSize) |
| } |
| } |
| |
| public static func foryReadData(_ context: ReadContext) throws -> [Key: Value] { |
| let totalLength = Int(try context.buffer.readVarUInt32()) |
| try context.ensureCollectionLength(totalLength, label: "map") |
| if totalLength == 0 { |
| return [:] |
| } |
| |
| var map: [Key: Value] = [:] |
| map.reserveCapacity(Swift.min(totalLength, context.buffer.remaining)) |
| let keyDynamicType = Key.staticTypeId == .unknown |
| let valueDynamicType = Value.staticTypeId == .unknown |
| if keyDynamicType || valueDynamicType { |
| var dynamicReadCount = 0 |
| while dynamicReadCount < totalLength { |
| let header = try context.buffer.readUInt8() |
| let trackKeyRef = (header & MapHeader.trackingKeyRef) != 0 |
| let keyNull = (header & MapHeader.keyNull) != 0 |
| let keyDeclared = (header & MapHeader.declaredKeyType) != 0 |
| |
| let trackValueRef = (header & MapHeader.trackingValueRef) != 0 |
| let valueNull = (header & MapHeader.valueNull) != 0 |
| let valueDeclared = (header & MapHeader.declaredValueType) != 0 |
| |
| if keyNull && valueNull { |
| map[Key.foryDefault()] = Value.foryDefault() |
| dynamicReadCount += 1 |
| continue |
| } |
| |
| if keyNull { |
| let value = try Value.foryRead( |
| context, |
| refMode: trackValueRef ? .tracking : .none, |
| readTypeInfo: valueDynamicType || !valueDeclared |
| ) |
| map[Key.foryDefault()] = value |
| dynamicReadCount += 1 |
| continue |
| } |
| |
| if valueNull { |
| let key = try Key.foryRead( |
| context, |
| refMode: trackKeyRef ? .tracking : .none, |
| readTypeInfo: keyDynamicType || !keyDeclared |
| ) |
| map[key] = Value.foryDefault() |
| dynamicReadCount += 1 |
| continue |
| } |
| |
| let chunkSize = Int(try context.buffer.readUInt8()) |
| if chunkSize > (totalLength - dynamicReadCount) { |
| throw ForyError.invalidData("map dynamic chunk size exceeds remaining entries") |
| } |
| let keyTypeInfo = keyDeclared ? nil : try Key.foryReadTypeInfo(context) |
| let valueTypeInfo = valueDeclared ? nil : try Value.foryReadTypeInfo(context) |
| for _ in 0..<chunkSize { |
| let key = try context.withTypeInfo(keyTypeInfo, for: Key.self) { |
| try Key.foryRead( |
| context, |
| refMode: trackKeyRef ? .tracking : .none, |
| readTypeInfo: false |
| ) |
| } |
| let value = try context.withTypeInfo(valueTypeInfo, for: Value.self) { |
| try Value.foryRead( |
| context, |
| refMode: trackValueRef ? .tracking : .none, |
| readTypeInfo: false |
| ) |
| } |
| map[key] = value |
| } |
| dynamicReadCount += chunkSize |
| } |
| return map |
| } |
| |
| var readCount = 0 |
| while readCount < totalLength { |
| let header = try context.buffer.readUInt8() |
| let trackKeyRef = (header & MapHeader.trackingKeyRef) != 0 |
| let keyNull = (header & MapHeader.keyNull) != 0 |
| let keyDeclared = (header & MapHeader.declaredKeyType) != 0 |
| |
| let trackValueRef = (header & MapHeader.trackingValueRef) != 0 |
| let valueNull = (header & MapHeader.valueNull) != 0 |
| let valueDeclared = (header & MapHeader.declaredValueType) != 0 |
| |
| if keyNull && valueNull { |
| map[Key.foryDefault()] = Value.foryDefault() |
| readCount += 1 |
| continue |
| } |
| |
| if keyNull { |
| let value = try Value.foryRead( |
| context, |
| refMode: trackValueRef ? .tracking : .none, |
| readTypeInfo: !valueDeclared |
| ) |
| map[Key.foryDefault()] = value |
| readCount += 1 |
| continue |
| } |
| |
| if valueNull { |
| let key = try Key.foryRead( |
| context, |
| refMode: trackKeyRef ? .tracking : .none, |
| readTypeInfo: !keyDeclared |
| ) |
| map[key] = Value.foryDefault() |
| readCount += 1 |
| continue |
| } |
| |
| let chunkSize = Int(try context.buffer.readUInt8()) |
| if chunkSize > (totalLength - readCount) { |
| throw ForyError.invalidData("map chunk size exceeds remaining entries") |
| } |
| let keyTypeInfo = keyDeclared ? nil : try Key.foryReadTypeInfo(context) |
| let valueTypeInfo = valueDeclared ? nil : try Value.foryReadTypeInfo(context) |
| for _ in 0..<chunkSize { |
| let key = try context.withTypeInfo(keyTypeInfo, for: Key.self) { |
| try Key.foryRead( |
| context, |
| refMode: trackKeyRef ? .tracking : .none, |
| readTypeInfo: false |
| ) |
| } |
| let value = try context.withTypeInfo(valueTypeInfo, for: Value.self) { |
| try Value.foryRead( |
| context, |
| refMode: trackValueRef ? .tracking : .none, |
| readTypeInfo: false |
| ) |
| } |
| map[key] = value |
| } |
| readCount += chunkSize |
| } |
| |
| return map |
| } |
| } |