├── Tests ├── LNTCSVCodingTests │ ├── XCTestManifests.swift │ └── CSVCodingTests.swift ├── LNTBinaryCodingTests │ ├── XCTestManifests.swift │ └── BinaryCoderTests.swift ├── LNTSharedCodingTests │ ├── XCTestManifests.swift │ └── SharedCodingTests.swift └── LinuxMain.swift ├── Sources ├── LNTBinaryCoding │ ├── Errors.swift │ ├── Public.swift │ ├── Header.swift │ ├── Encoding │ │ ├── Optimizers │ │ │ ├── Optimizer.swift │ │ │ ├── SingleOptimizer.swift │ │ │ ├── KeyedOptimizer.swift │ │ │ └── UnkeyedOptimizer.swift │ │ ├── RawEncoding.swift │ │ └── EncodingContainers.swift │ ├── Decoding │ │ ├── RawDecoding.swift │ │ └── DecodingContainers.swift │ └── Format.md ├── LNTSharedCoding │ ├── CodingPath.swift │ ├── CodingKeys.swift │ └── CodingContext.swift └── LNTCSVCoding │ ├── Misc.swift │ ├── Schema.swift │ ├── Tokenizer.swift │ ├── README.md │ ├── Public.swift │ ├── Decoder.swift │ └── Encoder.swift ├── LICENSE ├── Package.swift └── README.md /Tests/LNTCSVCodingTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(CSVCodingTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LNTBinaryCodingTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(BinaryCodingTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LNTSharedCodingTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SharedCodingTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LNTCSVCodingTests 4 | import LNTBinaryCodingTests 5 | import LNTSharedCodingTests 6 | 7 | var tests = [XCTestCaseEntry]() 8 | tests += LNTSharedCodingTests.allTests() 9 | tests += LNTCSVCodingTests.allTests() 10 | tests += LNTBinaryCodingTests.allTests() 11 | XCTMain(tests) 12 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 18/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BinaryDecodingError: Error { 11 | case invalidVSUI, invalidString, invalidTag, invalidStringMapIndex 12 | case emptyFile, invalidFileVersion, containerTooSmall 13 | } 14 | -------------------------------------------------------------------------------- /Sources/LNTSharedCoding/CodingPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodingPath.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 22/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Reconstructible coding path. 11 | public enum CodingPath { 12 | case root 13 | indirect case child(key: CodingKey, parent: CodingPath) 14 | 15 | public var expanded: [CodingKey] { 16 | switch self { 17 | case .root: return [] 18 | case let .child(key: key, parent: parent): 19 | return parent.expanded + CollectionOfOne(key) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Misc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Misc.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 1/8/2562 BE. 6 | // 7 | 8 | extension String { 9 | /// Returns escaped string (including double quote) as necessary. 10 | func escaped(separator: Character, forced: Bool) -> String { 11 | if contains("\"") { 12 | var result = "\"", remaining = self[...] 13 | while let index = remaining.firstIndex(of: "\"") { 14 | result.append(contentsOf: remaining[...index]) 15 | result.append("\"") 16 | remaining = remaining[index...].dropFirst() 17 | } 18 | result.append(contentsOf: remaining) 19 | result.append("\"") 20 | return result 21 | } 22 | 23 | func isPrintable(_ character: Character) -> Bool { 24 | character.asciiValue.map { 0x20...0x7E ~= $0 } ?? false 25 | } 26 | 27 | if forced || contains(separator) || !allSatisfy(isPrintable) { 28 | return "\"\(self)\"" 29 | } 30 | return self 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lantua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/LNTSharedCoding/CodingKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodingKeys.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 22/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Coding key for UnkeyedEn/DecodingContainer. 11 | public struct UnkeyedCodingKey: CodingKey { 12 | var index: Int 13 | 14 | public var intValue: Int? { return index } 15 | public var stringValue: String { return String(index) } 16 | 17 | public init(intValue: Int) { 18 | index = intValue 19 | } 20 | public init?(stringValue: String) { 21 | guard let value = Int(stringValue) else { 22 | return nil 23 | } 24 | index = value 25 | } 26 | } 27 | 28 | /// Coding key for `super` encoder. 29 | public struct SuperCodingKey: CodingKey { 30 | public var intValue: Int? { return 0 } 31 | public var stringValue: String { return "super" } 32 | 33 | public init?(intValue: Int) { 34 | if intValue != 0 { 35 | return nil 36 | } 37 | } 38 | public init?(stringValue: String) { 39 | if stringValue != "super" { 40 | return nil 41 | } 42 | } 43 | public init() { } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LNTSharedCoding/CodingContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 9/9/2563 BE. 6 | // 7 | 8 | public struct CodingContext { 9 | public let shared: Shared, userInfo: [CodingUserInfoKey: Any], path: CodingPath 10 | 11 | public init(_ shared: Shared, userInfo: [CodingUserInfoKey: Any], path: CodingPath = .root) { 12 | self.shared = shared 13 | self.userInfo = userInfo 14 | self.path = path 15 | } 16 | 17 | public func appending(_ key: CodingKey) -> Self { 18 | .init(shared, userInfo: userInfo, path: .child(key: key, parent: path)) 19 | } 20 | 21 | public func error(_ description: String = "", error: Error? = nil) -> DecodingError.Context { 22 | .init(codingPath: path.expanded, debugDescription: description, underlyingError: error) 23 | } 24 | public func error(_ description: String = "", error: Error? = nil) -> EncodingError.Context { 25 | .init(codingPath: path.expanded, debugDescription: description, underlyingError: error) 26 | } 27 | 28 | } 29 | 30 | public protocol ContextContainer { 31 | associatedtype Shared 32 | var context: CodingContext { get } 33 | } 34 | public extension ContextContainer { 35 | var userInfo: [CodingUserInfoKey: Any] { context.userInfo } 36 | var codingPath: [CodingKey] { context.path.expanded } 37 | } 38 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CommonCoding", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "LNTCSVCoding", 12 | targets: ["LNTCSVCoding"]), 13 | .library( 14 | name: "LNTBinaryCoding", 15 | targets: ["LNTBinaryCoding"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "LNTCSVCoding", 26 | dependencies: ["LNTSharedCoding"]), 27 | .target( 28 | name: "LNTBinaryCoding", 29 | dependencies: ["LNTSharedCoding"]), 30 | .target(name: "LNTSharedCoding"), 31 | 32 | .testTarget( 33 | name: "LNTCSVCodingTests", 34 | dependencies: ["LNTCSVCoding"]), 35 | .testTarget( 36 | name: "LNTBinaryCodingTests", 37 | dependencies: ["LNTBinaryCoding"]), 38 | .testTarget( 39 | name: "LNTSharedCodingTests", 40 | dependencies: ["LNTSharedCoding"]), 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommonCoding 2 | 3 | Common Swift Encoder/Decoder 4 | 5 | ## Binary Codings 6 | 7 | Encoder and decoder for binary file format. 8 | For format specification, see [Format](Sources/LNTBinaryCoding/Format.md) 9 | 10 | **Note**: We have not reached 1.0 yet, and so the format for these coders may change in incompatible ways over time. 11 | 12 | ## CSV Codings 13 | 14 | Encoder and decoder for CSV file format as per [RFC 4180](https://tools.ietf.org/html/rfc4180). 15 | For API information, see [CSV README](Sources/LNTCSVCoding/README.md). 16 | 17 | A decoding example, 18 | 19 | ```swift 20 | import LNTCSVCoding 21 | 22 | struct SomeStruct: Equatable, Codable { 23 | var a: Int, b: Double?, c: String 24 | } 25 | 26 | let decoder = CSVDecoder() // default options 27 | 28 | let string = """ 29 | a,b,c 30 | 4,,test 31 | 6,9.9,ss 32 | """ 33 | 34 | let values = try decoder.decode(SomeStruct.self, from: string) 35 | /* 36 | values = [ 37 | SomeStruct(a: 4, b: nil, c: "test"), // first row 38 | SomeStruct(a: 6, b: 9.9, c: "ss") // second row 39 | ] 40 | */ 41 | ``` 42 | 43 | An encoding example, 44 | 45 | ```swift 46 | import LNTCSVCoding 47 | 48 | struct SomeStruct: Equatable, Codable { 49 | var a: Int, b: Double?, c: String 50 | } 51 | struct OtherStruct: Equatable, Codable { 52 | var a: Float?, b: SomeStruct 53 | } 54 | 55 | let values = [ 56 | OtherStruct(a: 5.5, b: .init(a: 4.4, b: 1, c: "abc")), 57 | OtherStruct(a: nil, b: .init(a: .infinity, b: nil, c: "")) 58 | ] 59 | 60 | let encoder = CSVEncoder() // default options 61 | let string = encoder.encode(values) 62 | /* 63 | string = """ 64 | float,some.a,some.b,some.c 65 | 5.5,4,inf,abc 66 | ,-3,, 67 | """ 68 | */ 69 | ``` 70 | 71 | Note that both times the Swift data is a sequence of values. This is due to tabular nature of CSV. 72 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Public.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Public.swift 3 | // LNTBinaryCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 5/8/2562 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct BinaryDecoder { 11 | public var userInfo: [CodingUserInfoKey: Any] = [:] 12 | 13 | public func decode(_: T.Type, from data: Data) throws -> T where T: Decodable { 14 | return try .init(from: InternalDecoder(data: data, userInfo: userInfo)) 15 | } 16 | } 17 | 18 | public struct BinaryEncoder { 19 | public var userInfo: [CodingUserInfoKey: Any] = [:] 20 | 21 | public func encode(_ value: S) throws -> Data where S: Encodable { 22 | let temp = TopLevelTemporaryEncodingStorage() 23 | 24 | let encodingContext = EncodingContext(userInfo: userInfo) 25 | let encoder = InternalEncoder(parent: temp, context: encodingContext) 26 | try value.encode(to: encoder) 27 | 28 | let strings = encodingContext.optimize() 29 | let context = OptimizationContext(strings: strings) 30 | 31 | var storage = temp.value 32 | storage.optimize(for: context) 33 | 34 | let stringMapSize = strings.count.vsuiSize + strings.lazy.map { $0.utf8.count }.reduce(0, +) + strings.count 35 | 36 | var data = Data(count: 2 + stringMapSize + storage.size) 37 | 38 | data.withUnsafeMutableBytes { 39 | var data = $0[...] 40 | 41 | func append(_ value: UInt8) { 42 | data[data.startIndex] = value 43 | data.removeFirst() 44 | } 45 | 46 | append(0) 47 | append(0) 48 | strings.count.write(to: &data) 49 | for string in strings { 50 | let raw = string.utf8 51 | UnsafeMutableRawBufferPointer(rebasing: data).copyBytes(from: raw) 52 | data.removeFirst(raw.count) 53 | append(0) 54 | } 55 | 56 | storage.write(to: data) 57 | } 58 | 59 | return data 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Header.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Header.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Header { 11 | case `nil`, string 12 | case signed, unsigned 13 | case regularKeyed(RegularKeyedHeader), regularUnkeyed(RegularUnkeyedHeader) 14 | indirect case equisizeKeyed(EquisizeKeyedHeader), equisizeUnkeyed(EquisizedUnkeyedHeader) 15 | } 16 | 17 | extension Header { 18 | var isNil: Bool { 19 | guard case .nil = self else { 20 | return false 21 | } 22 | return true 23 | } 24 | } 25 | 26 | struct RegularKeyedHeader { 27 | var mapping: [(key: Int, size: Int)] 28 | 29 | var totalPayloadSize: Int { mapping.lazy.map { $0.size }.reduce(0, +) } 30 | } 31 | 32 | struct EquisizeKeyedHeader { 33 | var itemSize: Int, subheader: Header?, keys: [Int] 34 | 35 | var payloadSize: Int { itemSize - (subheader?.size ?? 0) } 36 | var totalPayloadSize: Int { payloadSize * keys.count } 37 | } 38 | 39 | struct RegularUnkeyedHeader { 40 | var sizes: [Int] 41 | 42 | var totalPayloadSize: Int { sizes.reduce(0, +) } 43 | } 44 | 45 | struct EquisizedUnkeyedHeader { 46 | var itemSize: Int, subheader: Header?, count: Int 47 | 48 | var payloadSize: Int { itemSize - (subheader?.size ?? 0) } 49 | var totalPayloadSize: Int { payloadSize * count } 50 | } 51 | 52 | extension Header { 53 | enum Tag: UInt8 { 54 | case `nil` = 0x1, signed = 0x2, unsigned = 0x3, string = 0x4 55 | case regularKeyed = 0x10, equisizeKeyed = 0x11, uniformKeyed = 0x12 56 | case regularUnkeyed = 0x20, equisizeUnkeyed = 0x21, uniformUnkeyed = 0x22 57 | } 58 | 59 | var tag: Tag { 60 | switch self { 61 | case .nil: return .nil 62 | case .signed: return .signed 63 | case .unsigned: return .unsigned 64 | case .string: return .string 65 | case .regularKeyed: return .regularKeyed 66 | case .regularUnkeyed: return .regularUnkeyed 67 | case let .equisizeKeyed(header): return header.subheader != nil ? .uniformKeyed : .equisizeKeyed 68 | case let .equisizeUnkeyed(header): return header.subheader != nil ? .uniformUnkeyed : .equisizeUnkeyed 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/Optimizers/Optimizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EncodingStorage.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 18/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Temporary Storage. Use while in the process of encoding data. 11 | protocol TemporaryEncodingStorage { 12 | func register(_: EncodingOptimizer) 13 | } 14 | 15 | class TopLevelTemporaryEncodingStorage: TemporaryEncodingStorage { 16 | var value: EncodingOptimizer = NilOptimizer() 17 | 18 | func register(_ newValue: EncodingOptimizer) { 19 | value = newValue 20 | } 21 | } 22 | 23 | /// Compiled Storage for optimization. 24 | /// Created after the encoding process but before the writing process. 25 | /// Can be optimized to different context. 26 | protocol EncodingOptimizer { 27 | var header: Header { get } 28 | var payloadSize: Int { get } 29 | func writePayload(to data: Slice) 30 | 31 | mutating func optimize(for context: OptimizationContext) 32 | } 33 | 34 | extension EncodingOptimizer { 35 | var size: Int { header.size + payloadSize } 36 | func write(to data: Slice) { 37 | assert(data.count >= size) 38 | 39 | var data = data 40 | header.write(to: &data) 41 | writePayload(to: data) 42 | } 43 | } 44 | 45 | struct OptimizationContext { 46 | private var strings: [String: Int] = [:] 47 | 48 | init(strings collection: C) where C: Collection, C.Element == String { 49 | for (offset, string) in collection.enumerated() { 50 | strings[string] = offset + 1 51 | } 52 | } 53 | 54 | func index(for string: String) -> Int { 55 | strings[string]! 56 | } 57 | } 58 | 59 | func uniformize(values: C) -> (elementSize: Int, header: Header)? where C: Collection, C.Element == EncodingOptimizer { 60 | guard let tag = values.first?.header.tag, 61 | values.dropFirst().allSatisfy({ $0.header.tag == tag }) else { 62 | return nil 63 | } 64 | 65 | switch tag { 66 | case .nil: return (0, .nil) 67 | case .signed: return (values.lazy.map { $0.size }.reduce(0, max), .signed) 68 | case .unsigned: return (values.lazy.map { $0.size }.reduce(0, max), .unsigned) 69 | case .string: return (values.lazy.map { $0.size }.reduce(0, max), .string) 70 | default: return nil // Unsupported types 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/LNTSharedCodingTests/SharedCodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import LNTSharedCoding 3 | 4 | final class SharedCodingTests: XCTestCase { 5 | func testUnkeyedCodingKey() { 6 | let key1: CodingKey = UnkeyedCodingKey(intValue: 9988) 7 | XCTAssertEqual(key1.intValue, 9988) 8 | XCTAssertEqual(key1.stringValue, "9988") 9 | 10 | let key2: CodingKey? = UnkeyedCodingKey(stringValue: "someValue") 11 | XCTAssertNil(key2) 12 | 13 | let key3: CodingKey? = UnkeyedCodingKey(stringValue: "1234") 14 | XCTAssertEqual(key3?.intValue, 1234) 15 | XCTAssertEqual(key3?.stringValue, "1234") 16 | } 17 | 18 | func testSuperCodingKey() { 19 | let key1: CodingKey? = SuperCodingKey(intValue: 1) 20 | XCTAssertNil(key1) 21 | 22 | let key2: CodingKey? = SuperCodingKey(intValue: 0) 23 | XCTAssertEqual(key2?.intValue, 0) 24 | XCTAssertEqual(key2?.stringValue, "super") 25 | 26 | let key3: CodingKey? = SuperCodingKey(stringValue: "super") 27 | XCTAssertEqual(key3?.intValue, 0) 28 | XCTAssertEqual(key3?.stringValue, "super") 29 | 30 | let key4: CodingKey? = SuperCodingKey(stringValue: "Something else") 31 | XCTAssertNil(key4) 32 | 33 | let key5: CodingKey? = SuperCodingKey() 34 | XCTAssertEqual(key5?.intValue, 0) 35 | XCTAssertEqual(key5?.stringValue, "super") 36 | } 37 | 38 | func testCodingPath() { 39 | var codingPath = CodingPath.root 40 | do { 41 | let codingPath = codingPath.expanded 42 | XCTAssert(codingPath.isEmpty) 43 | } 44 | codingPath = .child(key: UnkeyedCodingKey(intValue: 3), parent: codingPath) 45 | do { 46 | let codingPath = codingPath.expanded 47 | XCTAssert(codingPath.count == 1) 48 | XCTAssert(codingPath[0] is UnkeyedCodingKey) 49 | XCTAssert(codingPath[0].intValue == 3) 50 | } 51 | codingPath = .child(key: SuperCodingKey(), parent: codingPath) 52 | do { 53 | let codingPath = codingPath.expanded 54 | XCTAssert(codingPath.count == 2) 55 | XCTAssert(codingPath[0] is UnkeyedCodingKey) 56 | XCTAssert(codingPath[0].intValue == 3) 57 | XCTAssert(codingPath[1] is SuperCodingKey) 58 | } 59 | } 60 | 61 | static var allTests = [ 62 | ("testUnkeyedCodingKey", testUnkeyedCodingKey), 63 | ("testSuperCodingKey", testSuperCodingKey), 64 | ("testCodingPath", testCodingPath), 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Schema.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Schema.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 1/8/2562 BE. 6 | // 7 | 8 | /// Schema of the current object. Contains a single index (for simple types), or key-schema dictionary (for complex types). 9 | enum Schema { 10 | case value(Int), nested([String: Schema]) 11 | 12 | init(data: [(offset: Int, element: Array.SubSequence)]) throws { 13 | // By constrution, `data` can not be empty at non-top level. 14 | // `data` can not be empty at top-level either as tokenizer 15 | // always emit at least one element at the beginning. 16 | assert(!data.isEmpty) 17 | 18 | guard data.allSatisfy({ !$0.element.isEmpty }) else { 19 | guard data.allSatisfy({ $0.element.isEmpty }) else { 20 | throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Mixed multi/single-field entry found")) 21 | } 22 | guard data.count == 1 else { 23 | throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Duplicated fields")) 24 | } 25 | self = .value(data.first!.offset) 26 | return 27 | } 28 | let groupped = try Dictionary(grouping: data) { String($0.element.first!) }.mapValues { 29 | try Schema(data: $0.map { ($0.offset, $0.element.dropFirst()) }) 30 | } 31 | self = .nested(groupped) 32 | } 33 | 34 | /// Returns `true` if any index in the schema matches `predicate`. 35 | func contains(where predicate: (Int) throws -> Bool) rethrows -> Bool { 36 | switch self { 37 | case let .value(data): return try predicate(data) 38 | case let .nested(data): return try data.values.contains { try $0.contains(where: predicate) } 39 | } 40 | } 41 | 42 | func getKeyedContainer() -> [String: Schema]? { 43 | guard case let .nested(data) = self else { 44 | return nil 45 | } 46 | return data 47 | } 48 | 49 | func getUnkeyedContainer() -> [Schema]? { 50 | guard case let .nested(data) = self else { 51 | return nil 52 | } 53 | var result: [Schema] = [] 54 | for i in 0.. Int? { 64 | guard case let .value(value) = self else { 65 | return nil 66 | } 67 | return value 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/Optimizers/SingleOptimizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleOptimizer.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | struct NilOptimizer: EncodingOptimizer { 11 | var header: Header { .nil } 12 | var payloadSize: Int { 0 } 13 | 14 | func optimize(for context: OptimizationContext) { } 15 | 16 | func writePayload(to data: Slice) { } 17 | } 18 | 19 | struct SignedOptimizer: EncodingOptimizer { 20 | let value: Int64 21 | 22 | var header: Header { .signed } 23 | var payloadSize: Int 24 | 25 | init(value: T) where T: FixedWidthInteger, T: SignedInteger { 26 | self.value = Int64(value) 27 | switch T.bitWidth - max(value.leadingZeroBitCount, (~value).leadingZeroBitCount) { 28 | case 0..<8: payloadSize = 1 29 | case 8..<16: payloadSize = 2 30 | case 16..<32: payloadSize = 4 31 | default: payloadSize = 8 32 | } 33 | } 34 | 35 | func optimize(for context: OptimizationContext) { } 36 | 37 | func writePayload(to data: Slice) { 38 | switch data.count { 39 | case 1..<2: Int8(value).littleEndian.writeFixedWidth(to: data) 40 | case 2..<4: Int16(value).littleEndian.writeFixedWidth(to: data) 41 | case 4..<8: Int32(value).littleEndian.writeFixedWidth(to: data) 42 | default: Int64(value).littleEndian.writeFixedWidth(to: data) 43 | } 44 | } 45 | } 46 | 47 | struct UnsignedOptimizer: EncodingOptimizer { 48 | let value: UInt64 49 | 50 | var header: Header { .unsigned } 51 | var payloadSize: Int 52 | 53 | init(value: T) where T: FixedWidthInteger, T: UnsignedInteger { 54 | self.value = UInt64(value) 55 | switch T.bitWidth - value.leadingZeroBitCount { 56 | case 0..<8: payloadSize = 1 57 | case 8..<16: payloadSize = 2 58 | case 16..<32: payloadSize = 4 59 | default: payloadSize = 8 60 | } 61 | } 62 | 63 | func optimize(for context: OptimizationContext) { } 64 | 65 | func writePayload(to data: Slice) { 66 | switch data.count { 67 | case 1..<2: UInt8(value).littleEndian.writeFixedWidth(to: data) 68 | case 2..<4: UInt16(value).littleEndian.writeFixedWidth(to: data) 69 | case 4..<8: UInt32(value).littleEndian.writeFixedWidth(to: data) 70 | default: UInt64(value).littleEndian.writeFixedWidth(to: data) 71 | } 72 | } 73 | } 74 | 75 | struct StringStorage: EncodingOptimizer { 76 | private let string: String 77 | private var index = 0 78 | 79 | var header: Header { .string } 80 | var payloadSize: Int { index.vsuiSize } 81 | 82 | init(string: String) { 83 | self.string = string 84 | } 85 | 86 | mutating func optimize(for context: OptimizationContext) { 87 | index = context.index(for: string) 88 | } 89 | 90 | func writePayload(to data: Slice) { 91 | var data = data 92 | index.write(to: &data) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Tokenizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tokenizer.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 5/8/2562 BE. 6 | // 7 | 8 | /// Sequence of tokens given the csv content. The tokens may be de-escaped 9 | /// string (with escaping information), row boundary, and parsing error. 10 | struct Tokens: Sequence { 11 | let base: S 12 | 13 | enum Token { 14 | case escaped(String), unescaped(String), rowBoundary, invalid(TokenizationError) 15 | } 16 | 17 | enum TokenizationError: Error { 18 | /// Text has unescaped double quote 19 | case unescapedQuote 20 | /// Text has double quote followed by non-escaping character 21 | case invalidEscaping(Character) 22 | /// Text has opening double quote, but not closing one 23 | case unclosedQoute 24 | } 25 | 26 | struct Iterator: IteratorProtocol { 27 | enum State { 28 | case expecting, rowBoundary, end 29 | } 30 | var remaining: S.SubSequence, state = State.expecting 31 | 32 | mutating func next() -> Token? { 33 | switch state { 34 | case .expecting: 35 | defer { 36 | if remaining.first != separator, state != .end { 37 | state = .rowBoundary 38 | } 39 | remaining = remaining.dropFirst() 40 | } 41 | 42 | if remaining.first == "\"" { 43 | // Escaping 44 | remaining.removeFirst() 45 | var escaped = "" 46 | while let index = remaining.firstIndex(of: "\"") { 47 | escaped.append(contentsOf: remaining[.. Iterator { Iterator(remaining: base[...]) } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/Optimizers/KeyedOptimizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedOptimizer.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | struct KeyedOptimizer: EncodingOptimizer { 11 | private var values: [String: EncodingOptimizer] 12 | 13 | var header = Header.nil 14 | private(set) var payloadSize = 0 15 | 16 | init(values: [String: EncodingOptimizer]) { 17 | self.values = values 18 | } 19 | 20 | mutating func optimize(for context: OptimizationContext) { 21 | for key in values.keys { 22 | values[key]!.optimize(for: context) 23 | } 24 | 25 | let keys = values.keys.map { context.index(for: $0) } 26 | 27 | var bestOption = regularSize(keys: keys) 28 | var bestSize = bestOption.header.size + bestOption.payload 29 | 30 | func compare(candidate: (header: Header, payload: Int)) { 31 | let candidateSize = candidate.header.size + candidate.payload 32 | if candidateSize <= bestSize { 33 | bestOption = candidate 34 | bestSize = candidateSize 35 | } 36 | } 37 | 38 | if !values.isEmpty { 39 | compare(candidate: equisizeSize(keys: keys)) 40 | } 41 | 42 | if let candidate = uniformSize(keys: keys) { 43 | compare(candidate: candidate) 44 | } 45 | 46 | (header, payloadSize) = bestOption 47 | } 48 | 49 | func writePayload(to data: Slice) { 50 | assert(data.count >= payloadSize) 51 | 52 | var data = data 53 | 54 | switch header { 55 | case let .regularKeyed(header): 56 | let mapping = header.mapping 57 | for (value, map) in zip(values.values, mapping) { 58 | let size = map.size 59 | value.write(to: data.prefix(size)) 60 | data.removeFirst(size) 61 | } 62 | case let .equisizeKeyed(header): 63 | let size = header.payloadSize 64 | for value in values.values { 65 | if header.subheader != nil { 66 | value.writePayload(to: data.prefix(size)) 67 | } else { 68 | value.write(to: data.prefix(size)) 69 | } 70 | data.removeFirst(size) 71 | } 72 | default: fatalError("Unreachable") 73 | } 74 | } 75 | } 76 | 77 | private extension KeyedOptimizer { 78 | func regularSize(keys: [Int]) -> (header: Header, payload: Int) { 79 | let header = Header.regularKeyed(.init(mapping: .init(zip(keys, values.values.lazy.map { $0.size })))) 80 | return (header, values.values.lazy.map { $0.size }.reduce(0, +)) 81 | } 82 | 83 | func equisizeSize(keys: [Int]) -> (header: Header, payload: Int) { 84 | let maxSize = values.values.map { $0.size }.reduce(0, max) 85 | return (.equisizeKeyed(.init(itemSize: maxSize, subheader: nil, keys: keys)), maxSize * keys.count) 86 | } 87 | 88 | func uniformSize(keys: [Int]) -> (header: Header, payload: Int)? { 89 | guard let (elementSize, subheader) = uniformize(values: values.values) else { 90 | return nil 91 | } 92 | 93 | return (.equisizeKeyed(.init(itemSize: elementSize, subheader: subheader, keys: keys)), (elementSize - subheader.size) * keys.count) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/Optimizers/UnkeyedOptimizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnkeyedOptimizer.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UnkeyedOptimizer: EncodingOptimizer { 11 | var header = Header.nil 12 | private var values: [EncodingOptimizer] 13 | private(set) var payloadSize = 0 14 | 15 | init(values: [EncodingOptimizer]) { 16 | self.values = values 17 | } 18 | 19 | mutating func optimize(for context: OptimizationContext) { 20 | for index in values.indices { 21 | values[index].optimize(for: context) 22 | } 23 | 24 | var bestOption = regularSize() 25 | var bestSize = bestOption.header.size + bestOption.payload 26 | 27 | func compare(candidate: (header: Header, payload: Int)) { 28 | let candidateSize = candidate.header.size + candidate.payload 29 | if candidateSize <= bestSize { 30 | bestOption = candidate 31 | bestSize = candidateSize 32 | } 33 | } 34 | 35 | if let candidate = equisizeSize() { 36 | compare(candidate: candidate) 37 | } 38 | 39 | if let candidate = uniformSize() { 40 | compare(candidate: candidate) 41 | } 42 | 43 | (header, payloadSize) = bestOption 44 | } 45 | 46 | func writePayload(to data: Slice) { 47 | assert(data.count >= payloadSize) 48 | 49 | var data = data 50 | 51 | switch header { 52 | case let .equisizeUnkeyed(header): 53 | let size = header.payloadSize 54 | if header.subheader != nil { 55 | for value in values { 56 | value.writePayload(to: data.prefix(size)) 57 | data.removeFirst(size) 58 | } 59 | } else { 60 | for value in values { 61 | value.write(to: data.prefix(size)) 62 | data.removeFirst(size) 63 | } 64 | } 65 | default: 66 | assert(header.tag == .regularUnkeyed) 67 | 68 | for value in values { 69 | let size = value.size 70 | value.write(to: data.prefix(size)) 71 | data.removeFirst(size) 72 | } 73 | } 74 | } 75 | } 76 | 77 | private extension UnkeyedOptimizer { 78 | func regularSize() -> (header: Header, payload: Int) { 79 | let sizes = values.lazy.map { $0.size } 80 | let header = Header.regularUnkeyed(.init(sizes: Array(sizes))) 81 | return (header, sizes.reduce(0, +)) 82 | } 83 | 84 | func equisizeSize() -> (header: Header, payload: Int)? { 85 | let maxSize = values.lazy.map { $0.size }.reduce(0, max) 86 | guard maxSize > 0 else { 87 | return nil 88 | } 89 | return (.equisizeUnkeyed(.init(itemSize: maxSize, subheader: nil, count: values.count)), maxSize * values.count) 90 | } 91 | 92 | func uniformSize() -> (header: Header, payload: Int)? { 93 | guard let (itemSize, subheader) = uniformize(values: values) else { 94 | return nil 95 | } 96 | 97 | return (.equisizeUnkeyed(.init(itemSize: itemSize, subheader: subheader, count: values.count)), (itemSize - subheader.size) * values.count) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/RawEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Header { 11 | /// Size of this header 12 | var size: Int { 13 | switch self { 14 | case .nil: return 0 15 | case .signed, .unsigned, .string: return 1 16 | case let .regularKeyed(header): 17 | return 2 + header.mapping.lazy.map { $0.key.vsuiSize + $0.size.vsuiSize }.reduce(0, +) 18 | case let .equisizeKeyed(header): 19 | if let subheader = header.subheader { 20 | return 2 + header.itemSize.vsuiSize + subheader.size + header.keys.lazy.map { $0.vsuiSize }.reduce(0, +) 21 | } else { 22 | return 2 + header.payloadSize.vsuiSize + header.keys.lazy.map { $0.vsuiSize }.reduce(0, +) 23 | } 24 | case let .regularUnkeyed(header): 25 | return 2 + header.sizes.lazy.map { $0.vsuiSize }.reduce(0, +) 26 | case let .equisizeUnkeyed(header): 27 | if let subheader = header.subheader { 28 | return 1 + header.itemSize.vsuiSize + subheader.size + header.count.vsuiSize 29 | } else { 30 | return 1 + header.payloadSize.vsuiSize + header.count.vsuiSize 31 | } 32 | } 33 | } 34 | 35 | /// Write the header to `data`, and remove the written part. 36 | func write(to data: inout Slice) { 37 | guard !data.isEmpty else { 38 | assert(size == 0) 39 | return 40 | } 41 | 42 | func append(_ value: UInt8) { 43 | data[data.startIndex] = value 44 | data.removeFirst() 45 | } 46 | 47 | append(tag.rawValue) 48 | 49 | switch self { 50 | case .nil, .signed, .unsigned, .string: break 51 | 52 | case let .regularKeyed(header): 53 | for (key, size) in header.mapping { 54 | size.write(to: &data) 55 | key.write(to: &data) 56 | } 57 | append(0x01) 58 | case let .equisizeKeyed(header): 59 | header.itemSize.write(to: &data) 60 | for key in header.keys { 61 | key.write(to: &data) 62 | } 63 | append(0x00) 64 | header.subheader?.write(to: &data) 65 | case let .regularUnkeyed(header): 66 | for size in header.sizes { 67 | size.write(to: &data) 68 | } 69 | append(0x01) 70 | case let .equisizeUnkeyed(header): 71 | header.itemSize.write(to: &data) 72 | header.count.write(to: &data) 73 | header.subheader?.write(to: &data) 74 | } 75 | } 76 | } 77 | 78 | extension Int { 79 | /// Size of this value, in bytes, when being written in VSUI format. 80 | var vsuiSize: Int { 81 | assert(self >= 0) 82 | return Swift.max((bitWidth - leadingZeroBitCount + 6) / 7, 1) 83 | } 84 | 85 | /// Write a VSUI value to `data` and remove the written part. 86 | func write(to data: inout Slice) { 87 | let size = vsuiSize 88 | 89 | let last = data.index(data.startIndex, offsetBy: size - 1) 90 | var value = self, index = last 91 | 92 | for i in 0..>= 7 97 | } 98 | 99 | data[last] &= 0x7f 100 | assert(value == 0) 101 | 102 | data.removeFirst(size) 103 | } 104 | } 105 | 106 | extension FixedWidthInteger { 107 | func writeFixedWidth(to data: Slice) { 108 | assert(bitWidth / 8 <= data.count) 109 | 110 | withUnsafeBytes(of: self) { raw in 111 | UnsafeMutableRawBufferPointer(rebasing: data).copyMemory(from: raw) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/README.md: -------------------------------------------------------------------------------- 1 | # CSV Coding 2 | 3 | Encoder and decoder for CSV file format as per [RFC 4180](https://tools.ietf.org/html/rfc4180). 4 | 5 | ## Header structure 6 | 7 | CSV coders require the CSV string to contains header. 8 | * Header field for elements in `KeyedContainer` is the `stringValue` of the `key` used to encode/decode data. 9 | * Header field for elements in `UnkeyedContainer` is the offset (converted to `String`) from the beginning of the container `0, 1, 2, ...`. 10 | * Nested containers will use `subheaderSeparator` (defaulted to `.`) to separated between subfields at each level. Thus every subfield must not contain `subheaderSeparator`. 11 | * Super encoder uses `super` as a subfield if none is provided. 12 | 13 | As such, the following structure 14 | 15 | ``` 16 | class A: Codable { 17 | var a: ..., b: ..., c: ... 18 | } 19 | class B: A { 20 | var a: ..., b: ..., c: ... 21 | } 22 | ``` 23 | 24 | translates `B` into 25 | 26 | ``` 27 | - B -|------ A values ------- 28 | a,b,c,super.a,super.b,super.c 29 | ``` 30 | 31 | ## CSVEncoder Functions 32 | 33 | ``` 34 | public init(subheaderSeparator: Character = ".", options: CSVEncodingOptions = [], userInfo: [CodingUserInfoKey: Any] = [:]) 35 | ``` 36 | 37 | * `subheaderSeparator`: Separator used to separate each subheader if there are nested containers. 38 | * `options`: 39 | * `omitHeader`: Don't print header line. 40 | * `alwaysQuots`: Escape every data by inserting quotes `""` (including numerical values). 41 | * `useNullAsNil`: Uses unescaped string `null` to represent `nil` (default is unescaped empty string). 42 | * `userInfo`: Custom user info to pass into encoding process. 43 | 44 | Note that everything EXCEPT `separator` and `subheaderSeparator` can be changed after the initialization via appropriate accessor. 45 | 46 | ``` 47 | public func encode(_ values: S) throws -> String where S: Sequence, S.Element: Encodable 48 | ``` 49 | 50 | * Encode `values` into CSV string data. 51 | 52 | * `values`: `Sequence` of values to encode. 53 | 54 | * return value: `String` of the encoded CSV data. 55 | * throws `EncodingError` with the following descriptions: 56 | * _Key does not match any header fields_: if a new key is used after encoder encoded the first item (and finalized the header line). 57 | * _Duplicated field_: if the same field is encoded twice. 58 | 59 | ``` 60 | public func encode(_ values: S, into output: inout Output) throws where S: Sequence, S.Element: Encodable, Output: TextOutputStream 61 | ``` 62 | 63 | * Encode `values` and write the result into `output`. 64 | 65 | * `values`: `Sequence` of values to encode. 66 | * `output`: the destination of the encoded string. 67 | 68 | * throws `EncodingError` with the following descriptions: 69 | * _Key does not match any header fields_: if a new key is used after encoder encoded the first item (and finalized the header line). 70 | * _Duplicated field_: if the same field is encoded twice. 71 | 72 | ## CSVDecoder Functions 73 | 74 | ``` 75 | public init(subheaderSeparator: Character = ".", options: CSVDecodingOptions = [], userInfo: [CodingUserInfoKey: Any] = [:]) 76 | ``` 77 | 78 | * `subheaderSeparator`: Separator used to separate each subheader if there are nested containers. 79 | * `options`: 80 | * `treatEmptyStringAsValue`: treats unescaped empty string as empty string (default is to treat it as `nil`). 81 | * `treatNullAsNil`: treats unescaped `null` as `nil`. 82 | * `userInfo`: Custom user info to pass into encoding process . 83 | 84 | Note that everything EXCEPT `separator` and `subheaderSeparator` can be changed after the initialization via appropriate accessor. 85 | 86 | ``` 87 | public func decode(_ type: T.Type, from string: S) throws -> [T] where S: Sequence, S.Element == Character, T: Decodable 88 | ``` 89 | 90 | * Decode an array of type `T` from `string` 91 | 92 | * `type`: type to the decoded data. 93 | * `string`: CSV data to decode. 94 | 95 | * `throws` 96 | * `DecodingError.dataCorrupted` _Expecting multi-field object_: if the decoder found unnested data (`a`), but is expecting a nested data (`a.a`, `a.b`, etc). 97 | * `DecodingError.keyNotFound`: if the type tries to uses `key` not present in `string` for keyed container, or decode past the end for unkeyed container. 98 | * `DecodingError.typeMismatch` _Multi-field object found_: if the decoder found a nested data (`a.a`, `a.b`, etc), but is expecting unnested data (`a`). 99 | * `DecodingError.typeMismatch` _Trying to decode \`Type\`_: the value can not be converted to `Type`. 100 | * `DecodingError.valueNotFound`: found `nil` when expecting a non-nil value. 101 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Decoding/RawDecoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RawDecoding.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 20/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | mutating func extractVSUI() throws -> Int { 12 | var result = 0, first = 0 as UInt8 13 | repeat { 14 | guard !isEmpty, 15 | result.leadingZeroBitCount > 7 else { 16 | throw BinaryDecodingError.invalidVSUI 17 | } 18 | 19 | first = removeFirst() 20 | result <<= 7 21 | result |= Int(first) & (1<<7 - 1) 22 | } while first & 1<<7 != 0 23 | 24 | return result 25 | } 26 | 27 | mutating func extractString() throws -> String { 28 | guard let terminatorIndex = firstIndex(where: { $0 == 0 }), 29 | let string = String(data: self[startIndex.. Header { 38 | guard !isEmpty else { 39 | return .nil 40 | } 41 | 42 | let rawTag = removeFirst() 43 | guard let tag = Header.Tag(rawValue: rawTag) else { 44 | throw BinaryDecodingError.invalidTag 45 | } 46 | 47 | switch tag { 48 | case .nil: return .nil 49 | case .signed: return .signed 50 | case .unsigned: return .unsigned 51 | case .string: return .string 52 | case .regularKeyed: return try .regularKeyed(extractRegularKeyedHeader()) 53 | case .equisizeKeyed: return try .equisizeKeyed(extractEquisizedKeyedHeader()) 54 | case .uniformKeyed: return try .equisizeKeyed(extractUniformKeyedHeader()) 55 | case .regularUnkeyed: return try .regularUnkeyed(extractRegularUnkeyedHeader()) 56 | case .equisizeUnkeyed: return try .equisizeUnkeyed(extractEquisizedUnkeyedHeader()) 57 | case .uniformUnkeyed: return try .equisizeUnkeyed(extractUniformUnkeyedHeader()) 58 | } 59 | } 60 | } 61 | 62 | private extension Data { 63 | mutating func extractRegularKeyedHeader() throws -> RegularKeyedHeader { 64 | var mapping: [(key: Int, size: Int)] = [] 65 | var size = try extractVSUI() 66 | while size != 1 { 67 | let key = try extractVSUI() 68 | mapping.append((key, size)) 69 | size = try extractVSUI() 70 | } 71 | 72 | return .init(mapping: mapping) 73 | } 74 | 75 | mutating func extractEquisizedKeyedHeader() throws -> EquisizeKeyedHeader { 76 | var keys: [Int] = [] 77 | 78 | let size = try extractVSUI() 79 | var keyIndex = try extractVSUI() 80 | while keyIndex != 0 { 81 | keys.append(keyIndex) 82 | keyIndex = try extractVSUI() 83 | } 84 | 85 | return .init(itemSize: size, subheader: nil, keys: keys) 86 | } 87 | 88 | mutating func extractUniformKeyedHeader() throws -> EquisizeKeyedHeader { 89 | let itemSize = try extractVSUI() 90 | var keys: [Int] = [] 91 | var keyIndex = try extractVSUI() 92 | while keyIndex != 0 { 93 | keys.append(keyIndex) 94 | keyIndex = try extractVSUI() 95 | } 96 | 97 | let header = try extractHeader() 98 | 99 | return .init(itemSize: itemSize, subheader: header, keys: keys) 100 | } 101 | 102 | mutating func extractRegularUnkeyedHeader() throws -> RegularUnkeyedHeader { 103 | var sizes: [Int] = [] 104 | var size = try extractVSUI() 105 | while size != 1 { 106 | sizes.append(size) 107 | size = try extractVSUI() 108 | } 109 | 110 | return .init(sizes: sizes) 111 | } 112 | 113 | mutating func extractEquisizedUnkeyedHeader() throws -> EquisizedUnkeyedHeader { 114 | let size = try extractVSUI() 115 | let count = try extractVSUI() 116 | return .init(itemSize: size, subheader: nil, count: count) 117 | } 118 | 119 | mutating func extractUniformUnkeyedHeader() throws -> EquisizedUnkeyedHeader { 120 | let itemSize = try extractVSUI() 121 | let count = try extractVSUI() 122 | let subheader = try extractHeader() 123 | return .init(itemSize: itemSize, subheader: subheader, count: count) 124 | } 125 | } 126 | 127 | extension Data { 128 | private func readFixed(_: T.Type) -> T where T: FixedWidthInteger { 129 | var result: T = 0 130 | Swift.withUnsafeMutableBytes(of: &result) { raw in 131 | raw.copyBytes(from: prefix(T.bitWidth / 8)) 132 | } 133 | return .init(littleEndian: result) 134 | } 135 | func readSigned(_: T.Type) -> T? where T: FixedWidthInteger { 136 | switch count { 137 | case 0: return 0 138 | case 1..<2: return T(exactly: readFixed(Int8.self)) 139 | case 2..<4: return T(exactly: readFixed(Int16.self)) 140 | case 4..<8: return T(exactly: readFixed(Int32.self)) 141 | default: return T(exactly: readFixed(Int64.self)) 142 | } 143 | } 144 | func readUnsigned(_: T.Type) -> T? where T: FixedWidthInteger { 145 | switch count { 146 | case 0: return 0 147 | case 1..<2: return T(exactly: readFixed(UInt8.self)) 148 | case 2..<4: return T(exactly: readFixed(UInt16.self)) 149 | case 4..<8: return T(exactly: readFixed(UInt32.self)) 150 | default: return T(exactly: readFixed(UInt64.self)) 151 | } 152 | } 153 | 154 | func readVSUI() throws -> Int { 155 | var tmp = self 156 | return try tmp.extractVSUI() 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Public.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Public.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 1/8/2562 BE. 6 | // 7 | 8 | import LNTSharedCoding 9 | 10 | let separator: Character = "," 11 | 12 | public struct CSVEncodingOptions: OptionSet { 13 | public let rawValue: Int 14 | public init(rawValue: Int) { 15 | self.rawValue = rawValue 16 | } 17 | 18 | /// Don't write header line 19 | public static let omitHeader = CSVEncodingOptions(rawValue: 1 << 0) 20 | /// Force escape every value 21 | public static let alwaysQuote = CSVEncodingOptions(rawValue: 1 << 1) 22 | /// Use unescaped "null" as nil value 23 | public static let useNullAsNil = CSVEncodingOptions(rawValue: 1 << 2) 24 | } 25 | 26 | public struct CSVEncoder { 27 | public var options: CSVEncodingOptions 28 | public var userInfo: [CodingUserInfoKey: Any] 29 | 30 | let subheaderSeparator: Character 31 | 32 | public init(subheaderSeparator: Character = ".", options: CSVEncodingOptions = [], userInfo: [CodingUserInfoKey: Any] = [:]) { 33 | self.subheaderSeparator = subheaderSeparator 34 | self.options = options 35 | self.userInfo = userInfo 36 | } 37 | 38 | private func escape(_ string: String?) -> String { 39 | switch string { 40 | case nil: return options.contains(.useNullAsNil) ? "null" : "" 41 | case "null" where options.contains(.useNullAsNil): return "\"null\"" 42 | case let string?: 43 | return string.escaped(separator: separator, forced: options.contains(.alwaysQuote)) 44 | } 45 | } 46 | 47 | public func encode(_ values: S) throws -> String where S: Sequence, S.Element: Encodable { 48 | var result = "" 49 | try encode(values, into: &result) 50 | return result 51 | } 52 | 53 | public func encode(_ values: S, into output: inout Output) throws where S: Sequence, S.Element: Encodable, Output: TextOutputStream { 54 | var fieldIndices: [String: Int]? 55 | 56 | for value in values { 57 | let context = SharedEncodingContext(encoder: self, fieldIndices: fieldIndices) 58 | let encoder = CSVInternalEncoder(context: .init(context, userInfo: userInfo)) 59 | try value.encode(to: encoder) 60 | 61 | let (currentFieldIndices, entry) = context.finalize() 62 | assert(Set(currentFieldIndices.values) == Set(0..(_ type: T.Type, from string: S) throws -> [T] where S: StringProtocol, T: Decodable { 104 | var buffer: [String?] = [], schema: Schema!, fieldCount: Int?, results: [T] = [] 105 | for token in Tokens(base: string) { 106 | switch token { 107 | case let .escaped(string): buffer.append(string) 108 | case let .unescaped(string): 109 | guard !options.contains(.treatNullAsNil) || string.lowercased() != "null", 110 | options.contains(.treatEmptyStringAsValue) || !string.isEmpty else { 111 | buffer.append(nil) 112 | break 113 | } 114 | 115 | buffer.append(string) 116 | case let .invalid(error): 117 | throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid CSV format", underlyingError: error)) 118 | case .rowBoundary: 119 | defer { buffer.removeAll(keepingCapacity: true) } 120 | 121 | guard fieldCount != nil else { 122 | fieldCount = buffer.count 123 | 124 | schema = try Schema(data: Array(buffer.map { 125 | ($0?.split(separator: subheaderSeparator) ?? [])[...] 126 | }.enumerated())) 127 | continue 128 | } 129 | 130 | guard buffer.count == fieldCount else { 131 | throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Each row must have equal number of fields")) 132 | } 133 | 134 | let decoder = CSVInternalDecoder(decoder: self, values: buffer, schema: schema) 135 | try results.append(T(from: decoder)) 136 | } 137 | } 138 | 139 | assert(buffer.isEmpty) 140 | return results 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Decoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Decoder.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 1/8/2562 BE. 6 | // 7 | 8 | import LNTSharedCoding 9 | 10 | // MARK: Context 11 | 12 | struct SharedDecodingContext { 13 | let values: [String?] 14 | } 15 | extension CodingContext where Shared == SharedDecodingContext { 16 | func value(at schema: Schema) throws -> T where T: LosslessStringConvertible { 17 | guard let index = schema.getValue() else { 18 | throw DecodingError.typeMismatch(T.self, error("Multi-field object found")) 19 | } 20 | guard let string = shared.values[index] else { 21 | throw DecodingError.valueNotFound(String.self, error()) 22 | } 23 | guard let result = T(string) else { 24 | throw DecodingError.typeMismatch(T.self, error("Trying to decode `\(string)`")) 25 | } 26 | 27 | return result 28 | } 29 | 30 | func hasValue(at schema: Schema) -> Bool { 31 | schema.contains { shared.values[$0] != nil } 32 | } 33 | } 34 | 35 | // MARK: Decoder 36 | 37 | /// Internal decoder. This is what the `Decodable` uses when decoding 38 | struct CSVInternalDecoder: ContextContainer, Decoder { 39 | let context: CodingContext, schema: Schema 40 | 41 | func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { 42 | try .init(CSVKeyedDecodingContainer(decoder: self)) 43 | } 44 | func unkeyedContainer() throws -> UnkeyedDecodingContainer { try CSVUnkeyedDecodingContainer(decoder: self) } 45 | func singleValueContainer() throws -> SingleValueDecodingContainer { self } 46 | } 47 | 48 | extension CSVInternalDecoder { 49 | init(decoder: CSVDecoder, values: [String?], schema: Schema) { 50 | context = .init(.init(values: values), userInfo: decoder.userInfo) 51 | self.schema = schema 52 | } 53 | } 54 | 55 | // MARK: Keyed Container 56 | 57 | private struct CSVKeyedDecodingContainer: ContextContainer, KeyedDecodingContainerProtocol { 58 | let context: CodingContext, schemas: [String: Schema] 59 | 60 | var allKeys: [Key] { 61 | // Includes only keys with non-nil value. 62 | // 63 | // Decodables that uses this is usually dynamic, so `nil` fields would be used 64 | // to mark the absence of key. If the key definitely must be present, it's usually 65 | // hard-coded in the generated/user-defined `init(from:)` and bypass this value anyway. 66 | schemas.filter { context.hasValue(at: $0.value) }.compactMap { Key(stringValue: $0.key) } 67 | } 68 | 69 | init(decoder: CSVInternalDecoder) throws { 70 | self.context = decoder.context 71 | 72 | guard let schemas = decoder.schema.getKeyedContainer() else { 73 | throw DecodingError.dataCorrupted(context.error("Expecting multi-field object")) 74 | } 75 | self.schemas = schemas 76 | } 77 | 78 | private func schema(forKey key: CodingKey) throws -> Schema { 79 | guard let schema = schemas[key.stringValue] else { 80 | throw DecodingError.keyNotFound(key, context.error()) 81 | } 82 | return schema 83 | } 84 | private func decoder(forKey key: CodingKey) throws -> CSVInternalDecoder { 85 | try .init(context: context.appending(key), schema: schema(forKey: key)) 86 | } 87 | 88 | func contains(_ key: Key) -> Bool { schemas[key.stringValue] != nil } 89 | 90 | func decodeNil(forKey key: Key) throws -> Bool { try !context.hasValue(at: schema(forKey: key)) } 91 | 92 | func decode(_: T.Type, forKey key: Key) throws -> T where T: Decodable, T: LosslessStringConvertible { 93 | try context.value(at: schema(forKey: key)) 94 | } 95 | 96 | func decode(_: T.Type, forKey key: Key) throws -> T where T: Decodable { try .init(from: decoder(forKey: key)) } 97 | func superDecoder() throws -> Decoder { try decoder(forKey: SuperCodingKey()) } 98 | func superDecoder(forKey key: Key) throws -> Decoder { try decoder(forKey: key) } 99 | func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { try decoder(forKey: key).unkeyedContainer() } 100 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { 101 | try decoder(forKey: key).container(keyedBy: NestedKey.self) 102 | } 103 | } 104 | 105 | // MARK: Unkeyed Container 106 | 107 | private struct CSVUnkeyedDecodingContainer: ContextContainer, UnkeyedDecodingContainer { 108 | let context: CodingContext, schemas: [Schema] 109 | 110 | let count: Int? 111 | var currentIndex = 0 112 | var isAtEnd: Bool { currentIndex == count } 113 | 114 | init(decoder: CSVInternalDecoder) throws { 115 | self.context = decoder.context 116 | 117 | guard let schemas = decoder.schema.getUnkeyedContainer() else { 118 | throw DecodingError.dataCorrupted(context.error("Expecting multi-field object")) 119 | } 120 | self.schemas = schemas 121 | self.count = 1 + (schemas.lastIndex(where: decoder.context.hasValue) ?? -1) 122 | } 123 | 124 | private mutating func consumeSchema() throws -> Schema { 125 | defer { currentIndex += 1 } 126 | guard schemas.indices ~= currentIndex else { 127 | throw DecodingError.keyNotFound(UnkeyedCodingKey(intValue: currentIndex), context.error()) 128 | } 129 | return schemas[currentIndex] 130 | } 131 | 132 | private mutating func consumeDecoder() throws -> CSVInternalDecoder { 133 | try .init(context: context, schema: consumeSchema()) 134 | } 135 | 136 | mutating func decodeNil() throws -> Bool { 137 | let hasValue = try context.hasValue(at: consumeSchema()) 138 | if hasValue { 139 | currentIndex -= 1 140 | } 141 | return !hasValue 142 | } 143 | 144 | mutating func decode(_: T.Type) throws -> T where T: Decodable, T: LosslessStringConvertible { 145 | try context.appending(UnkeyedCodingKey(intValue: currentIndex)).value(at: consumeSchema()) 146 | } 147 | mutating func decode(_: T.Type) throws -> T where T: Decodable { try .init(from: consumeDecoder()) } 148 | 149 | mutating func superDecoder() throws -> Decoder { try consumeDecoder() } 150 | mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { try consumeDecoder().unkeyedContainer() } 151 | mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey: CodingKey { 152 | try .init(CSVKeyedDecodingContainer(decoder: consumeDecoder())) 153 | } 154 | } 155 | 156 | // MARK: Single Value Container 157 | 158 | extension CSVInternalDecoder: SingleValueDecodingContainer { 159 | func decodeNil() -> Bool { !context.hasValue(at: schema) } 160 | 161 | func decode(_: T.Type) throws -> T where T: Decodable { try .init(from: self) } 162 | func decode(_: T.Type) throws -> T where T: Decodable, T: LosslessStringConvertible { try context.value(at: schema) } 163 | } 164 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Encoding/EncodingContainers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalEncoder.swift 3 | // 4 | // 5 | // Created by Natchanon Luangsomboon on 18/2/2563 BE. 6 | // 7 | 8 | import Foundation 9 | import LNTSharedCoding 10 | 11 | // MARK: Context 12 | 13 | /// Encoding context, shared between all internal encoders, containers, etc. during encoding process. 14 | struct EncodingContext { 15 | fileprivate class Shared { 16 | let userInfo: [CodingUserInfoKey: Any] 17 | var strings: [String: Int] = [:] 18 | 19 | init(userInfo: [CodingUserInfoKey: Any]) { 20 | self.userInfo = userInfo 21 | } 22 | } 23 | 24 | fileprivate let shared: Shared 25 | var path: CodingPath = .root 26 | 27 | var userInfo: [CodingUserInfoKey: Any] { shared.userInfo } 28 | var codingPath: [CodingKey] { path.expanded } 29 | 30 | init(userInfo: [CodingUserInfoKey: Any]) { 31 | shared = .init(userInfo: userInfo) 32 | } 33 | 34 | func register(string: String) { 35 | shared.strings[string, default: 0] += 1 36 | } 37 | 38 | func optimize() -> [String] { 39 | shared.strings.sorted { $0.1 > $1.1 }.map { $0.key } 40 | } 41 | } 42 | 43 | extension EncodingContext { 44 | func appending(_ key: CodingKey) -> EncodingContext { 45 | var temp = self 46 | temp.path = .child(key: key, parent: path) 47 | return temp 48 | } 49 | } 50 | 51 | // MARK: Encoder 52 | 53 | struct InternalEncoder: Encoder { 54 | let parent: TemporaryEncodingStorage, context: EncodingContext 55 | 56 | var userInfo: [CodingUserInfoKey : Any] { context.userInfo } 57 | var codingPath: [CodingKey] { context.codingPath } 58 | 59 | func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { 60 | .init(KeyedBinaryEncodingContainer(parent: parent, context: context)) 61 | } 62 | 63 | func unkeyedContainer() -> UnkeyedEncodingContainer { 64 | UnkeyedBinaryEncodingContainer(parent: parent, context: context) 65 | } 66 | 67 | func singleValueContainer() -> SingleValueEncodingContainer { 68 | self 69 | } 70 | } 71 | 72 | // MARK: Keyed Container 73 | 74 | struct KeyedBinaryEncodingContainer: KeyedEncodingContainerProtocol where Key: CodingKey { 75 | private let storage: Storage, context: EncodingContext 76 | 77 | var codingPath: [CodingKey] { context.codingPath } 78 | 79 | init(parent: TemporaryEncodingStorage, context: EncodingContext) { 80 | storage = .init(parent: parent) 81 | self.context = context 82 | } 83 | 84 | private func encoder(for key: CodingKey) -> InternalEncoder { 85 | let keyString = key.stringValue 86 | context.register(string: keyString) 87 | return .init(parent: storage.temporaryWriter(for: keyString), context: context.appending(key)) 88 | } 89 | 90 | func encodeNil(forKey key: Key) throws { 91 | var container = encoder(for: key).singleValueContainer() 92 | try container.encodeNil() 93 | } 94 | 95 | func encode(_ value: T, forKey key: Key) throws where T : Encodable { try value.encode(to: encoder(for: key)) } 96 | func superEncoder() -> Encoder { encoder(for: SuperCodingKey()) } 97 | func superEncoder(forKey key: Key) -> Encoder { encoder(for: key) } 98 | func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { encoder(for: key).unkeyedContainer() } 99 | 100 | func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { 101 | encoder(for: key).container(keyedBy: NestedKey.self) 102 | } 103 | } 104 | 105 | extension KeyedBinaryEncodingContainer { 106 | private class Storage { 107 | let parent: TemporaryEncodingStorage 108 | private var values: [String: EncodingOptimizer] = [:] 109 | 110 | init(parent: TemporaryEncodingStorage) { 111 | self.parent = parent 112 | } 113 | 114 | func temporaryWriter(for key: String) -> Writer { 115 | values[key] = NilOptimizer() 116 | return .init(parent: self, key: key) 117 | } 118 | 119 | struct Writer: TemporaryEncodingStorage { 120 | let parent: Storage, key: String 121 | 122 | func register(_ newValue: EncodingOptimizer) { 123 | parent.values[key] = newValue 124 | } 125 | } 126 | 127 | deinit { parent.register(KeyedOptimizer(values: values)) } 128 | } 129 | } 130 | 131 | // MARK: Unkeyed Container 132 | 133 | struct UnkeyedBinaryEncodingContainer: UnkeyedEncodingContainer { 134 | private let storage: Storage, context: EncodingContext 135 | 136 | var codingPath: [CodingKey] { context.codingPath } 137 | var count: Int { storage.count } 138 | 139 | init(parent: TemporaryEncodingStorage, context: EncodingContext) { 140 | storage = .init(parent: parent) 141 | self.context = context 142 | } 143 | 144 | private func encoder() -> InternalEncoder { 145 | let encoderContext = context.appending(UnkeyedCodingKey(intValue: count)) 146 | return .init(parent: storage.temporaryWriter(), context: encoderContext) 147 | } 148 | 149 | func encodeNil() throws { 150 | var container = encoder().singleValueContainer() 151 | try container.encodeNil() 152 | } 153 | 154 | func encode(_ value: T) throws where T: Encodable { try value.encode(to: encoder()) } 155 | func superEncoder() -> Encoder { encoder() } 156 | func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { encoder().unkeyedContainer() } 157 | func nestedContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { 158 | encoder().container(keyedBy: NestedKey.self) 159 | } 160 | } 161 | 162 | extension UnkeyedBinaryEncodingContainer { 163 | private class Storage { 164 | private let parent: TemporaryEncodingStorage 165 | private var values: [EncodingOptimizer] = [] 166 | 167 | var count: Int { values.count } 168 | 169 | init(parent: TemporaryEncodingStorage) { 170 | self.parent = parent 171 | } 172 | 173 | func temporaryWriter() -> Writer { 174 | defer { values.append(NilOptimizer()) } 175 | return .init(parent: self, index: values.count) 176 | } 177 | 178 | struct Writer: TemporaryEncodingStorage { 179 | let parent: Storage, index: Int 180 | 181 | func register(_ newValue: EncodingOptimizer) { 182 | parent.values[index] = newValue 183 | } 184 | } 185 | 186 | deinit { parent.register(UnkeyedOptimizer(values: values)) } 187 | } 188 | } 189 | 190 | // MARK: Single Value Container 191 | 192 | extension InternalEncoder: SingleValueEncodingContainer { 193 | func encodeNil() throws { parent.register(NilOptimizer()) } 194 | 195 | func encode(_ value: String) throws { 196 | context.register(string: value) 197 | parent.register(StringStorage(string: value)) 198 | } 199 | 200 | func encode(_ value: Bool) throws { try encode(value ? 1 : 0 as UInt8) } 201 | func encode(_ value: Double) throws { try encode(value.bitPattern) } 202 | func encode(_ value: Float) throws { try encode(value.bitPattern) } 203 | 204 | func encode(_ value: T) throws where T: Encodable, T: FixedWidthInteger, T: SignedInteger { 205 | parent.register(SignedOptimizer(value: value)) 206 | } 207 | func encode(_ value: T) throws where T: Encodable, T: FixedWidthInteger, T: UnsignedInteger { 208 | parent.register(UnsignedOptimizer(value: value)) 209 | } 210 | 211 | func encode(_ value: T) throws where T : Encodable { 212 | try value.encode(to: self) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Sources/LNTCSVCoding/Encoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encoder.swift 3 | // LNTCSVCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 31/7/2562 BE. 6 | // 7 | 8 | import LNTSharedCoding 9 | 10 | // MARK: Context 11 | 12 | /// Encoding context, shared between all internal encoders, containers, etc. during single-element encoding process. 13 | class SharedEncodingContext { 14 | let isFixed: Bool, subheaderSeparator: String 15 | var fieldIndices: [String: Int], values: [String??] 16 | 17 | init(encoder: CSVEncoder, fieldIndices: [String: Int]? = nil) { 18 | self.fieldIndices = fieldIndices ?? [:] 19 | self.subheaderSeparator = .init(encoder.subheaderSeparator) 20 | 21 | isFixed = fieldIndices != nil 22 | values = Array(repeating: nil, count: (fieldIndices?.count ?? 0)) 23 | } 24 | 25 | func finalize() -> (fieldIndices: [String: Int], values: [String?]) { 26 | (fieldIndices, values.map { $0 as? String }) 27 | } 28 | } 29 | 30 | extension CodingContext where Shared == SharedEncodingContext { 31 | func add(unescaped: String?) throws { 32 | let fieldName = path.expanded.map { $0.stringValue }.joined(separator: shared.subheaderSeparator) 33 | if shared.isFixed { 34 | guard let index = shared.fieldIndices[fieldName] else { 35 | guard unescaped != nil else { 36 | // It's fine to add `nil` to non-existing field 37 | return 38 | } 39 | throw EncodingError.invalidValue(unescaped as Any, error("Key does not match any header fields")) 40 | } 41 | if let oldValue = shared.values[index] { 42 | guard oldValue != unescaped else { 43 | // It's fine to add same value to duplicated field 44 | return 45 | } 46 | throw EncodingError.invalidValue(unescaped as Any, error("Duplicated field")) 47 | } 48 | 49 | shared.values[index] = unescaped 50 | } else { 51 | if let oldIndex = shared.fieldIndices.updateValue(shared.values.count, forKey: fieldName) { 52 | guard shared.values[oldIndex]! != unescaped else { 53 | // It's fine to add same value to duplicated field 54 | shared.fieldIndices.updateValue(oldIndex, forKey: fieldName) 55 | return 56 | } 57 | throw EncodingError.invalidValue(unescaped as Any, error("Duplicated field")) 58 | } 59 | shared.values.append(unescaped) 60 | } 61 | } 62 | } 63 | 64 | // MARK: Encoder 65 | 66 | /// Internal decoder. This is what the `Decodable` uses when decoding 67 | struct CSVInternalEncoder: ContextContainer, Encoder { 68 | let context: CodingContext 69 | 70 | func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { 71 | .init(CSVKeyedEncodingContainer(context: context)) 72 | } 73 | func unkeyedContainer() -> UnkeyedEncodingContainer { CSVUnkeyedEncodingContainer(context: context) } 74 | func singleValueContainer() -> SingleValueEncodingContainer { self } 75 | } 76 | 77 | // MARK: Keyed Container 78 | 79 | private struct CSVKeyedEncodingContainer: ContextContainer, KeyedEncodingContainerProtocol { 80 | let context: CodingContext 81 | 82 | private func encoder(forKey key: CodingKey) -> CSVInternalEncoder { 83 | .init(context: context.appending(key)) 84 | } 85 | 86 | mutating func encodeNil(forKey key: Key) throws { try context.appending(key).add(unescaped: nil) } 87 | 88 | mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable, T: LosslessStringConvertible { 89 | try context.appending(key).add(unescaped: String(value)) 90 | } 91 | mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable { try value.encode(to: encoder(forKey: key)) } 92 | 93 | mutating func superEncoder() -> Encoder { encoder(forKey: SuperCodingKey()) } 94 | mutating func superEncoder(forKey key: Key) -> Encoder { encoder(forKey: key) } 95 | mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { encoder(forKey: key).unkeyedContainer() } 96 | mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { 97 | encoder(forKey: key).container(keyedBy: NestedKey.self) 98 | } 99 | 100 | mutating func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 101 | mutating func encodeIfPresent(_ value: Double?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init(_:)), forKey: key) } 102 | mutating func encodeIfPresent(_ value: Float?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init(_:)), forKey: key) } 103 | mutating func encodeIfPresent(_ value: Int?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 104 | mutating func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 105 | mutating func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 106 | mutating func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 107 | mutating func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 108 | mutating func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 109 | mutating func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 110 | mutating func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 111 | mutating func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 112 | mutating func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { try encodeIfPresent(value.map(String.init), forKey: key) } 113 | mutating func encodeIfPresent(_ value: String?, forKey key: Key) throws { 114 | try value != nil ? encode(value!, forKey: key) : encodeNil(forKey: key) 115 | } 116 | } 117 | 118 | // MARK: Unkeyed Container 119 | 120 | private struct CSVUnkeyedEncodingContainer: ContextContainer, UnkeyedEncodingContainer { 121 | var count = 0 122 | 123 | let context: CodingContext 124 | 125 | private mutating func consumeContext() -> CodingContext { 126 | defer { count += 1 } 127 | return context.appending(UnkeyedCodingKey(intValue: count)) 128 | } 129 | private mutating func consumeEncoder() -> CSVInternalEncoder { 130 | .init(context: consumeContext()) 131 | } 132 | 133 | mutating func encodeNil() throws { try consumeContext().add(unescaped: nil) } 134 | 135 | mutating func encode(_ value: T) throws where T: Encodable, T: LosslessStringConvertible { 136 | try consumeContext().add(unescaped: String(value)) 137 | } 138 | mutating func encode(_ value: T) throws where T: Encodable { try value.encode(to: consumeEncoder()) } 139 | 140 | mutating func superEncoder() -> Encoder { consumeEncoder() } 141 | mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { consumeEncoder().unkeyedContainer() } 142 | mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey: CodingKey { 143 | .init(CSVKeyedEncodingContainer(context: consumeContext())) 144 | } 145 | } 146 | 147 | // MARK: Single Value Container 148 | 149 | extension CSVInternalEncoder: SingleValueEncodingContainer { 150 | mutating func encodeNil() throws { try context.add(unescaped: nil) } 151 | 152 | mutating func encode(_ value: T) throws where T: Encodable { try value.encode(to: self) } 153 | mutating func encode(_ value: T) throws where T: Encodable, T: LosslessStringConvertible { 154 | try context.add(unescaped: String(value)) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Decoding/DecodingContainers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalDecoder.swift 3 | // LNTBinaryCoding 4 | // 5 | // Created by Natchanon Luangsomboon on 5/8/2562 BE. 6 | // 7 | 8 | import Foundation 9 | import LNTSharedCoding 10 | 11 | // MARK: Context 12 | 13 | struct SharedDecodingContext { 14 | let strings: [String] 15 | 16 | fileprivate init(data: inout Data) throws { 17 | guard data.count >= 2 else { 18 | throw BinaryDecodingError.emptyFile 19 | } 20 | 21 | guard data.removeFirst() == 0x00, 22 | data.removeFirst() == 0x00 else { 23 | throw BinaryDecodingError.invalidFileVersion 24 | } 25 | 26 | let count = try data.extractVSUI() 27 | self.strings = try (0.. String { 35 | let index = index - 1 36 | guard shared.strings.indices ~= index else { 37 | throw BinaryDecodingError.invalidStringMapIndex 38 | } 39 | return shared.strings[index] 40 | } 41 | } 42 | 43 | // MARK: Encoder 44 | 45 | struct InternalDecoder: ContextContainer, Decoder { 46 | var header: Header, data: Data 47 | public var context: CodingContext 48 | 49 | func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { 50 | try .init(KeyedBinaryDecodingContainer(decoder: self)) 51 | } 52 | func unkeyedContainer() throws -> UnkeyedDecodingContainer { try UnkeyedBinaryDecodingContainer(decoder: self) } 53 | func singleValueContainer() throws -> SingleValueDecodingContainer { self } 54 | } 55 | 56 | extension InternalDecoder { 57 | init(data: Data, userInfo: [CodingUserInfoKey: Any]) throws { 58 | var data = data 59 | context = try .init(.init(data: &data), userInfo: userInfo) 60 | header = try data.extractHeader() 61 | self.data = data 62 | } 63 | } 64 | 65 | // MARK: Keyed Container 66 | 67 | private struct KeyedBinaryDecodingContainer: ContextContainer, KeyedDecodingContainerProtocol where Key: CodingKey { 68 | let values: [String: Data], sharedHeader: Header?, context: CodingContext 69 | 70 | init(decoder: InternalDecoder) throws { 71 | self.context = decoder.context 72 | 73 | var data = decoder.data 74 | var tmp: [String: Data] = [:] 75 | 76 | switch decoder.header { 77 | case let .regularKeyed(header): 78 | for (key, size) in header.mapping { 79 | guard data.count >= size else { 80 | throw BinaryDecodingError.containerTooSmall 81 | } 82 | try tmp[context.string(at: key)] = data.prefix(size) 83 | data.removeFirst(size) 84 | } 85 | sharedHeader = nil 86 | case let .equisizeKeyed(header): 87 | let size = header.payloadSize 88 | guard data.count >= size * header.keys.count else { 89 | throw BinaryDecodingError.containerTooSmall 90 | } 91 | for key in header.keys { 92 | try tmp[context.string(at: key)] = data.prefix(size) 93 | data.removeFirst(size) 94 | } 95 | sharedHeader = header.subheader 96 | default: throw DecodingError.typeMismatch(KeyedDecodingContainer.self, context.error("Incompatible container found")) 97 | } 98 | values = tmp 99 | } 100 | 101 | var allKeys: [Key] { values.keys.compactMap(Key.init(stringValue:)) } 102 | func contains(_ key: Key) -> Bool { values.keys.contains(key.stringValue) } 103 | 104 | private func decoder(for key: CodingKey) throws -> InternalDecoder { 105 | guard var data = values[key.stringValue] else { 106 | throw DecodingError.keyNotFound(key, context.error()) 107 | } 108 | let header = try sharedHeader ?? data.extractHeader() 109 | return .init(header: header, data: data, context: context.appending(key)) 110 | } 111 | 112 | func decodeNil(forKey key: Key) throws -> Bool { 113 | guard var data = values[key.stringValue] else { 114 | return true 115 | } 116 | return try (sharedHeader ?? data.extractHeader()).isNil 117 | } 118 | func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { try T(from: decoder(for: key)) } 119 | 120 | func superDecoder() throws -> Decoder { try decoder(for: SuperCodingKey()) } 121 | func superDecoder(forKey key: Key) throws -> Decoder { try decoder(for: key) } 122 | func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { try decoder(for: key).unkeyedContainer() } 123 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 124 | try decoder(for: key).container(keyedBy: NestedKey.self) 125 | } 126 | } 127 | 128 | // MARK: Unkeyed Container 129 | 130 | private struct UnkeyedBinaryDecodingContainer: ContextContainer, UnkeyedDecodingContainer { 131 | private var values: ArraySlice 132 | let sharedHeader: Header?, context: CodingContext 133 | 134 | let count: Int? 135 | var currentIndex = 0 136 | 137 | init(decoder: InternalDecoder) throws { 138 | self.context = decoder.context 139 | 140 | var tmp: [Data] = [], data = decoder.data 141 | switch decoder.header { 142 | case let .regularUnkeyed(header): 143 | tmp.reserveCapacity(header.sizes.count) 144 | for size in header.sizes { 145 | guard data.count >= size else { 146 | throw BinaryDecodingError.containerTooSmall 147 | } 148 | tmp.append(data.prefix(size)) 149 | data.removeFirst(size) 150 | } 151 | sharedHeader = nil 152 | case let .equisizeUnkeyed(header): 153 | tmp.reserveCapacity(header.count) 154 | let size = header.payloadSize 155 | guard data.count >= size * header.count else { 156 | throw BinaryDecodingError.containerTooSmall 157 | } 158 | for _ in 0.. InternalDecoder { 172 | guard !isAtEnd else { 173 | throw DecodingError.keyNotFound(UnkeyedCodingKey(intValue: currentIndex), context.error("End of container reached")) 174 | } 175 | 176 | defer { currentIndex += 1 } 177 | var data = values.removeFirst() 178 | let header = try sharedHeader ?? data.extractHeader() 179 | return .init(header: header, data: data, context: context.appending(UnkeyedCodingKey(intValue: currentIndex))) 180 | } 181 | 182 | mutating func decodeNil() throws -> Bool { 183 | guard var data = values.first else { 184 | return true 185 | } 186 | return try (sharedHeader ?? data.extractHeader()).isNil 187 | } 188 | mutating func decode(_ type: T.Type) throws -> T where T : Decodable { try T(from: consumeDecoder()) } 189 | 190 | mutating func superDecoder() throws -> Decoder { try consumeDecoder() } 191 | mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { try consumeDecoder().unkeyedContainer() } 192 | mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 193 | try consumeDecoder().container(keyedBy: NestedKey.self) 194 | } 195 | } 196 | 197 | // MARK: Single Value Container 198 | 199 | extension InternalDecoder: SingleValueDecodingContainer { 200 | func decodeNil() -> Bool { 201 | if case .nil = header { 202 | return true 203 | } 204 | return false 205 | } 206 | func decode(_: Bool.Type) throws -> Bool { try decode(UInt8.self) != 0 } 207 | func decode(_: Float.Type) throws -> Float { try Float(bitPattern: decode(UInt32.self)) } 208 | func decode(_: Double.Type) throws -> Double { try Double(bitPattern: decode(UInt64.self)) } 209 | 210 | func decode(_: T.Type) throws -> T where T: Decodable, T: FixedWidthInteger { 211 | let tmp: T? 212 | switch header { 213 | case .signed: tmp = data.readSigned(T.self) 214 | case .unsigned: tmp = data.readUnsigned(T.self) 215 | default: throw DecodingError.typeMismatch(T.self, context.error("Incompatible container")) 216 | } 217 | 218 | guard let result = tmp else { 219 | throw DecodingError.typeMismatch(T.self, context.error("Value out of range")) 220 | } 221 | return result 222 | } 223 | 224 | func decode(_: String.Type) throws -> String { 225 | guard case .string = header else { 226 | throw DecodingError.typeMismatch(String.self, context.error("Incompatible container")) 227 | } 228 | 229 | return try context.string(at: data.readVSUI()) 230 | } 231 | 232 | func decode(_ type: T.Type) throws -> T where T : Decodable { try T(from: self) } 233 | } 234 | -------------------------------------------------------------------------------- /Sources/LNTBinaryCoding/Format.md: -------------------------------------------------------------------------------- 1 | # Variable-Sized Unsigned Integer 2 | 3 | We extensively uses Variable-Sized Unsigned Integer (VSUI). The format is as follows, for each byte: 4 | 5 | - The Most Significant Bit (MSB) is used as continuation bit. It is 1 if the next byte is part of this integer, and 0 otherwise. 6 | - Other 7 bits is used for the actual value, in big-endian order. 7 | 8 | For example, consider `[0x99, 0xf2, 0xe3, 0x17]` 9 | 10 | 1. This integer consists of `[0x99, 0xf2, 0xe3, 0x17]` as `0x17` is the first byte with clear MSB. 11 | 2. Stripping MSB from each bytes: `[0x19, 0x72, 0x63, 0x17]`. 12 | 3. Adding each byte together: `(0x19 << 21) + (0x72 << 14) + (0x63 << 7) + (0x17)` 13 | 4. The result from Step 3 is `0x33cb197`, or `54309271`. 14 | 15 | So the value of this byte pattern is `54309271`. 16 | 17 | **Note** 18 | 19 | In theory, VSUI permits integer of any size. In practive, the encoder and decoder use native `Int` to calculate these values. As such, data encoded with 64-bit machine may not be decoded successfully on 32-bit machine. 20 | 21 | # File Format 22 | 23 | Binary file format consists of 3 parts: Version Number, String map, and Data storage. Each part is stored separately as shown below: 24 | 25 | ``` 26 | *--------------------------------------------* 27 | | Version Number | String Map | Data Storage | 28 | *--------------------------------------------* 29 | ``` 30 | 31 | # Version Number 32 | 33 | The first two bytes are version number. Currently the value is `0x00 0x00` 34 | 35 | ``` 36 | *-------------* 37 | | 0x00 | 0x00 | 38 | *-------------* 39 | ``` 40 | 41 | # String Map 42 | 43 | String map is an array containing all strings found in the data storage (especially keys). Data storage refers to values in string map using 1-based indices. 44 | 45 | String map starts with a VSUI number indicating the total number of strings, followed by list of null-terminated strings. 46 | 47 | ``` 48 | *-------------------------------------------------* 49 | | N (VSUI) | String 1 | String 2 | ... | String N | 50 | *-------------------------------------------------* 51 | ``` 52 | 53 | # Data Storage 54 | 55 | Data storage contains a single object in form of a container. This section describes all valid containers. 56 | 57 | ## Tags, Headers, and Payload 58 | 59 | Every container consists of two portions; header, and payload. 60 | - Header contains the metadata to the data block (including Tag). 61 | - Tag is the first byte of the header. It is used to determine the type of the container. 62 | - Payload contains the data that is being stored. 63 | 64 | For all diagrams, we use the following legend: 65 | 66 | - Item: The payload. 67 | - Payload: The payload with the header stripped out. 68 | - Size (VSUI): The size of the item. 69 | - Key (VSUI): The index to the String map. Mostly for containing keys in keyed containers. 70 | 71 | For multi-item containers: 72 | - Count (VSUI): The number of item. 73 | - Tag: The tag shared among items. 74 | - Header: The header shared among items. 75 | 76 | ## Single-Value Container 77 | 78 | This section includes all supported single-value container. The headers of these containers consists of only tags, the rest are payloads. 79 | 80 | Note: 81 | This is different from `SingleValueDecodingContainer` and `SingleValueEncodingContainer`, which are transparent to the format. 82 | 83 | ### Nil 84 | 85 | `nil` is stored as either an empty block or a non-empty block with the first byte being the tag `0x01`. 86 | 87 | ``` 88 | *--------* 89 | | (0x01) | 90 | *--------* 91 | ``` 92 | 93 | ### Signed Integer Types 94 | 95 | Types in this category include `Int`, `Int8`, `Int16`, `Int32`, `Int64`. It is stored as a tag followed by the payload in little endian byte order. Zero-byte payload is treated as `0`. 96 | 97 | This format uses the largest type that fits the payload. For example, if the payload is five-byte long, it will use `Int32`. 98 | 99 | ``` 100 | *-------------* 101 | | 0x02 | Data | 102 | *-------------* 103 | ``` 104 | 105 | ### Unsigned Integer Types 106 | 107 | Types in this category include `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`. It is stored as a tag followed by the payload in little endian byte order. Zero-byte payload is treated as `0`. 108 | 109 | This format uses the largest type that fits the payload. For example, if the payload is five-byte long, it will use `UInt32`. 110 | 111 | ``` 112 | *-------------* 113 | | 0x03 | Data | 114 | *-------------* 115 | ``` 116 | 117 | ### Delegating Types 118 | 119 | Types in this category delegates the encoding to another types. 120 | 121 | - `Float` is encoded as `UInt32` (using bit pattern). 122 | - `Double` is encoded as `UInt64` (using bit pattern). 123 | - `Bool` is encoded as `UInt8`. It is `false` if the value is `0`, and is `true` otherwise. 124 | 125 | ### String 126 | 127 | `String` is stored inside String map, and be refered to using (VSUI) index. 128 | 129 | ``` 130 | *------*-------* 131 | | 0x04 | Index | 132 | *------*-------* 133 | ``` 134 | 135 | ## String-Based Keyed Container 136 | 137 | String-based keyed container has a few representations with different size-performance tradeoff. Encoders may choose any valid representation. 138 | 139 | ### Regular Case 140 | 141 | This is valid for all containers. 142 | 143 | ``` 144 | *-----------------------------------------------------*--------------------------------* 145 | | Header | Payload | 146 | *-----------------------------------------------------*--------------------------------* 147 | | 0x10 | Size 1 | Key 1 | ... | Size n | Key n | 0x01 | Item 1 | Item 2 | ... | Item n | 148 | *-----------------------------------------------------*--------------------------------* 149 | ``` 150 | 151 | ### Equisized Case 152 | 153 | This is valid if every item has the same size. 154 | 155 | ``` 156 | *--------------------------------------------------*--------------------------------* 157 | | Header | Payload | 158 | *--------------------------------------------------*--------------------------------* 159 | | 0x11 | Size | Key 1 | Key 2 | ... | Key n | 0x00 | Item 1 | Item 2 | ... | Item n | 160 | *--------------------------------------------------*--------------------------------* 161 | ``` 162 | 163 | ### Uniform Case 164 | 165 | This is valid if every item has the same size and header. 166 | 167 | ``` 168 | *-----------------------------------------------------------*-----------------------------------------* 169 | | Header | Payload | 170 | *-----------------------------------------------------------*-----------------------------------------* 171 | | 0x12 | Size | Key 1 | Key 2 | ... | Key n | 0x00 | Header | Payload 1 | Payload 2 | ... | Payload n | 172 | *-----------------------------------------------------------*-----------------------------------------* 173 | ``` 174 | 175 | Note that `Size` referes to the size of the item (with header attached) not the size of the payload. 176 | 177 | ## Int-Based Keyed Container 178 | 179 | This format does not support `Int`-based keyed container. Keys are converted to `String` and the container is encoded as a string-based keyed container. 180 | 181 | ## Unkeyed Container 182 | 183 | Unkeyed container has a few representations with different size-performance tradeoff. Encoders may choose any valid representation. 184 | 185 | ### Regular Case 186 | 187 | This is valid for all containers. 188 | 189 | ``` 190 | *----------------------------------------------*--------------------------------* 191 | | Header | Payload | 192 | *----------------------------------------------*--------------------------------* 193 | | 0x20 | Size 1 | Size 2 | ... | Size n | 0x01 | Item 1 | Item 2 | ... | Item n | 194 | *----------------------------------------------*--------------------------------* 195 | ``` 196 | 197 | ### Equisized Case 198 | 199 | This is valid if every item has the same size. 200 | 201 | ``` 202 | *---------------------*--------------------------------* 203 | | Header | Payload | 204 | *---------------------*--------------------------------* 205 | | 0x21 | Size | Count | Item 1 | Item 2 | ... | Item n | 206 | *---------------------*--------------------------------* 207 | ``` 208 | 209 | ### Uniform Case 210 | 211 | This is valid if every item has the same size and header. 212 | 213 | ``` 214 | *------------------------------*-----------------------------------------* 215 | | Header | Payload | 216 | *------------------------------*-----------------------------------------* 217 | | 0x22 | Size | Count | Header | Payload 1 | Payload 2 | ... | Payload n | 218 | *------------------------------*-----------------------------------------* 219 | ``` 220 | 221 | Note that `Size` refers to the size of the item (with header attached) not the size of the payload. 222 | 223 | # Design Note 224 | 225 | ## Padding 226 | 227 | This format is designed specifically to allow padding at the end of the data. 228 | 229 | For keyed and unkeyed containers, this allows payloads with different sizes to be padded to make the containers eligible for equisized and uniform forms. 230 | 231 | ## Reserved Values 232 | 233 | The tag `0x00` and the string index `0x00` are reserved. It can serve as an endpoint marker should we add support for formats that doesn't know the number of items up front. 234 | 235 | We also reserve *high value* tags (`0x80` - `0xff`) for when the tag space is filled up. At which point we can use them as a head for multi-byte tags. 236 | 237 | ## Size of an Item 238 | 239 | It is possible to avoid using object of size 1 altogether. `nil` object can be encoded as empty block, and all other objects requires at least 2 bytes (1 for tag, 1 for data). As such, some containers uses `0x01` as a marker that a list of valid size has ended. 240 | 241 | One reason this would be problematic is if we add another data-less type. There are some good candidates, such as empty signed/unsigned containers, which can be interpreted as zero. 242 | 243 | One alternative would be to have `nil` use exactly 1 byte, and use `0x00` as the marker, but having `nil` be 0 byte saves more space without much sacrifice in performance. 244 | 245 | ## Uniqueness of VSUI 246 | 247 | A VSUI byte pattern uniquely determines the integer value. The converse is not true. 248 | For example, following byte patterns matches `0x42`: 249 | 250 | - `[0x42]` 251 | - `[0x80, 0x42]` 252 | - `[0x80, 0x80, 0x42]` 253 | - `[0x80, 0x80, 0x80, 0x42]` 254 | 255 | and so on. This is intentional as it allows the encoder to make a trade-off between compression rate and encoding performance. An encoder may not know value of each integer at the time of encoding and put a fixed-size placeholder for editing later. Achieving higher compression rate may require the encoder to run another pass to optimize those placeholders. 256 | 257 | ## Non-overlapping Rule 258 | 259 | The data storage employs non-overlapping rule. As such, the size of an object can be calculated using the position of the next object (and the position of the current object). 260 | -------------------------------------------------------------------------------- /Tests/LNTCSVCodingTests/CSVCodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import LNTCSVCoding 3 | 4 | final class CSVCodingTests: XCTestCase { 5 | let encoder = CSVEncoder(), decoder = CSVDecoder() 6 | 7 | func testEscaping() { 8 | // Non-escaping 9 | XCTAssertEqual("aall".escaped(separator: ",", forced: false), "aall") // Non-escaping 10 | 11 | // Escaping 12 | XCTAssertEqual("\"".escaped(separator: ",", forced: false), "\"\"\"\"") // Double quote 13 | XCTAssertEqual("aa\njj".escaped(separator: ",", forced: false), "\"aa\njj\"") // \n 14 | XCTAssertEqual("🧐".escaped(separator: ",", forced: false), "\"🧐\"") // Non-ascii 15 | XCTAssertEqual("abc".escaped(separator: ",", forced: true), "\"abc\"") // Forced 16 | } 17 | 18 | func testTokenizer() { 19 | do { 20 | let value = """ 21 | a,"b",,"" 22 | 23 | "llk""d" 24 | a 25 | """ 26 | let tokens = Tokens(base: value) 27 | XCTAssertEqual(Array(tokens), [ 28 | .unescaped("a"), .escaped("b"), .unescaped(""), .escaped(""), .rowBoundary, 29 | .unescaped(""), .rowBoundary, 30 | .escaped("llk\"d"), .rowBoundary, 31 | .unescaped("a"), .rowBoundary 32 | ]) 33 | } 34 | do { 35 | let value = "\"test\"" 36 | let tokens = Tokens(base: value) 37 | XCTAssertEqual(Array(tokens), [ 38 | .escaped("test"), .rowBoundary 39 | ]) 40 | } 41 | do { 42 | let value = """ 43 | a,b,bad", 44 | """ 45 | let tokens = Tokens(base: value) 46 | XCTAssertEqual(Array(tokens), [ 47 | .unescaped("a"), .unescaped("b"), .invalid(.unescapedQuote) 48 | ]) 49 | } 50 | do { 51 | let value = #""a\"k""# 52 | let tokens = Tokens(base: value) 53 | XCTAssertEqual(Array(tokens), [ 54 | .invalid(.invalidEscaping("k")) 55 | ]) 56 | } 57 | do { 58 | let value = #""un""closed"# 59 | let tokens = Tokens(base: value) 60 | XCTAssertEqual(Array(tokens), [ 61 | .invalid(.unclosedQoute) 62 | ]) 63 | } 64 | } 65 | 66 | func testRoundtrip() { 67 | do { 68 | let value1: Int? = nil 69 | let encoder = CSVEncoder(options: .useNullAsNil) 70 | let decoder = CSVDecoder(options: .treatNullAsNil) 71 | try XCTAssertEqual(decoder.decode(Int?.self, from: encoder.encode([value1])), [value1]) 72 | 73 | let value2 = "null" 74 | try XCTAssertEqual(decoder.decode(String?.self, from: encoder.encode([value2])), [value2]) 75 | } 76 | do { 77 | struct Test: Codable, Equatable { 78 | var value: String 79 | 80 | init(_ value: String) { 81 | self.value = value 82 | } 83 | 84 | init(from decoder: Decoder) throws { 85 | XCTAssertEqual(decoder.userInfo[CodingUserInfoKey(rawValue: "decodingKey")!] as? String, "decodingValue") 86 | value = try String(from: decoder) 87 | } 88 | 89 | func encode(to encoder: Encoder) throws { 90 | XCTAssertEqual(encoder.userInfo[CodingUserInfoKey(rawValue: "encodingKey")!] as? String, "encodingValue") 91 | try value.encode(to: encoder) 92 | } 93 | } 94 | let encoder = CSVEncoder(userInfo: [CodingUserInfoKey(rawValue: "encodingKey")! : "encodingValue"]) 95 | let decoder = CSVDecoder(userInfo: [CodingUserInfoKey(rawValue: "decodingKey")! : "decodingValue"]) 96 | let value = Test("some string") 97 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode([value])), [value]) 98 | } 99 | } 100 | 101 | func testSingleValueRoundtrip() throws { 102 | let values = [144, nil] 103 | try XCTAssertEqual(decoder.decode(Int?.self, from: encoder.encode(values)), values) 104 | } 105 | 106 | func testKeyedRoundtrip() throws { 107 | do { 108 | struct Test: Codable, Equatable { 109 | var a: Int, b: [String] 110 | } 111 | let values = [Test(a: 1, b: ["foo"]), Test(a: 77, b: ["bar"])] 112 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(values)), values) 113 | } 114 | do { 115 | struct Test: Codable, Equatable { 116 | var b: Bool?, d: Double?, f: Float? 117 | var i: Int?, i8: Int8?, i16: Int16?, i32: Int32?, i64: Int64? 118 | var u: UInt?, u8: UInt8?, u16: UInt16?, u32: UInt32?, u64: UInt64? 119 | } 120 | let values = [ 121 | Test(b: true, d: 0.2, f: 0.2, i8: 9, i64: -737, u: 87, u16: 37, u32: 7874) 122 | ] 123 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(values)), values) 124 | } 125 | do { 126 | let values = [["a": 1, "b": 2], ["a": 3]] 127 | try XCTAssertEqual(decoder.decode([String: Int].self, from: encoder.encode(values)), values) 128 | } 129 | } 130 | 131 | func testUnkeyedRoundtrip() throws { 132 | do { 133 | let values = [[1, 2, 3], [1, nil, 9], []] 134 | try XCTAssertEqual(decoder.decode([Int?].self, from: encoder.encode(values)), values) 135 | } 136 | do { 137 | let values = [["sst", "jkj", "uuy"], ["uuh", nil], [nil, nil, nil, nil]] 138 | let expected: [[String?]] = values.map { array in 139 | guard let index = array.lastIndex(where: { $0 != nil }) else { 140 | return [] 141 | } 142 | return Array(array.prefix(through: index)) 143 | } 144 | try XCTAssertEqual(decoder.decode([String?].self, from: encoder.encode(values)), expected) 145 | } 146 | do { // Statically call decode(_:), decodeIfPresent(_:) 147 | struct Test: Codable, Equatable { 148 | var s: String, b: Bool?, c: Int? 149 | 150 | init(s: String, b: Bool?) { 151 | self.s = s 152 | self.b = b 153 | } 154 | 155 | init(from decoder: Decoder) throws { 156 | var container = try decoder.unkeyedContainer() 157 | s = try container.decode(String.self) 158 | b = try container.decodeIfPresent(Bool.self) 159 | c = try container.decode(Int?.self) 160 | } 161 | 162 | func encode(to encoder: Encoder) throws { 163 | var container = encoder.unkeyedContainer() 164 | try container.encode(s) 165 | try container.encode(b) 166 | try container.encodeNil() 167 | } 168 | } 169 | 170 | let values = [Test(s: "Something", b: false), Test(s: "lld", b: nil)] 171 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(values)), values) 172 | } 173 | } 174 | 175 | func testBehaviours() throws { 176 | do { 177 | try XCTAssertEqual(decoder.decode(Int.self, from: "\n1\n2\n3\n"), [1, 2, 3]) 178 | } 179 | do { // Interesting interaction between Dictionary and Array 180 | let dictionary = [0: "test", 3: "some"] 181 | let array = ["test", nil, nil, "some"] 182 | 183 | try XCTAssertEqual(decoder.decode([Int: String].self, from: encoder.encode([dictionary])), [dictionary]) 184 | try XCTAssertEqual(decoder.decode([Int: String].self, from: encoder.encode([array])), [dictionary]) 185 | try XCTAssertEqual(decoder.decode([String?].self, from: encoder.encode([dictionary])), [["test"]]) 186 | try XCTAssertEqual(decoder.decode([String?].self, from: encoder.encode([array])), [array]) 187 | } 188 | do { 189 | struct Test: Encodable { 190 | var duplicatedValue: String? = nil, addExtraKey = false 191 | 192 | init(duplicatedValue: String? = nil, addExtraKey: Bool = false) { 193 | self.duplicatedValue = duplicatedValue 194 | self.addExtraKey = addExtraKey 195 | } 196 | 197 | func encode(to encoder: Encoder) throws { 198 | var container = encoder.container(keyedBy: CodingKeys.self) 199 | try container.encode("Test", forKey: .a) 200 | 201 | if let value = duplicatedValue { 202 | try container.encode(value, forKey: .a) 203 | } 204 | if addExtraKey { 205 | try container.encode("Value", forKey: .b) 206 | } 207 | } 208 | 209 | enum CodingKeys: CodingKey { 210 | case a, b 211 | } 212 | } 213 | 214 | // Unconstrained duplicated keys 215 | try XCTAssertThrowsError(encoder.encode([Test(duplicatedValue: "Some value")])) 216 | // Constrained duplicated keys 217 | try XCTAssertThrowsError(encoder.encode([Test(), Test(duplicatedValue: "Other value")])) 218 | 219 | // Unconstrained duplicated keys with same value 220 | try XCTAssertNoThrow(encoder.encode([Test(duplicatedValue: "Test")])) 221 | // Constrained duplicated keys with same value 222 | try XCTAssertNoThrow(encoder.encode([Test(), Test(duplicatedValue: "Test")])) 223 | 224 | // Added extra key in constrained variable 225 | try XCTAssertThrowsError(encoder.encode([Test(), Test(addExtraKey: true)])) 226 | } 227 | 228 | do { 229 | struct BadDecoder: Decodable { 230 | init(from decoder: Decoder) throws { 231 | var container = try decoder.unkeyedContainer() 232 | _ = try container.decode(Bool.self) 233 | _ = try container.decode(Int.self) 234 | _ = try container.decodeIfPresent(Bool.self) 235 | } 236 | } 237 | // Decode more than what unkeyed container has 238 | try XCTAssertNoThrow(decoder.decode(BadDecoder.self, from: "0,1,2\ntrue,0,")) 239 | try XCTAssertNoThrow(decoder.decode(BadDecoder.self, from: "0,1\ntrue,0")) 240 | try XCTAssertThrowsError(decoder.decode(BadDecoder.self, from: "0\ntrue")) 241 | } 242 | do { 243 | struct Test: Decodable { 244 | var s: String?, b: Bool 245 | } 246 | 247 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "s,g\nkjk,ll")) // Key not found 248 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "s,b\nsomeString,")) // Value not found 249 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "s.1,s.3,b\n4,ll,true")) // Multi-field as simple object 250 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "s,b\nsdff,0.5")) // Type mismatch 251 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "s,s.1,s.3,b\n883,4,ll,true")) // Mixed multi/single field 252 | } 253 | do { 254 | struct Test: Decodable, Equatable { 255 | var a: [Int?], d: [Int: String] 256 | } 257 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "a,d.1\n3,sdf")) // Complex as simple object 258 | try XCTAssertThrowsError(decoder.decode(Test.self, from: "a.0,d\n3,sdf")) // Complex as simple object 259 | try XCTAssertEqual(decoder.decode(Test.self, from: "a.0,d.1\n3,sdf"), [Test(a: [3], d: [1: "sdf"])]) 260 | } 261 | do { 262 | try XCTAssertThrowsError(decoder.decode(Int.self, from: "\n1\n7,")) // Unequal rows 263 | try XCTAssertThrowsError(decoder.decode(Int.self, from: "b,a,a,d\n,,,")) // Duplicated field `a` 264 | try XCTAssertThrowsError(decoder.decode(Int.self, from: "b,,,d\n,,,")) // Duplicated field `` 265 | try XCTAssertThrowsError(decoder.decode(Int.self, from: "\"")) // Invalid CSV 266 | } 267 | } 268 | 269 | func testNestedKeyedContainers() throws { 270 | struct KeyedCodable: Codable, Equatable { 271 | var a: Float, b: String, c: Int, d: Double 272 | 273 | init(a: Float, b: String, c: Int, d: Double) { 274 | self.a = a 275 | self.b = b 276 | self.c = c 277 | self.d = d 278 | } 279 | 280 | init(from decoder: Decoder) throws { 281 | let container = try decoder.container(keyedBy: CodingKeys.self) 282 | do { 283 | var nested = try container.nestedUnkeyedContainer(forKey: .a) 284 | a = try nested.decode(Float.self) 285 | } 286 | b = try container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .b).decode(String.self, forKey: .a) 287 | c = try Int(from: container.superDecoder(forKey: .c)) 288 | d = try Double(from: container.superDecoder()) 289 | } 290 | func encode(to encoder: Encoder) throws { 291 | var container = encoder.container(keyedBy: CodingKeys.self) 292 | do { 293 | var nested = container.nestedUnkeyedContainer(forKey: .a) 294 | try nested.encode(a) 295 | } 296 | do { 297 | var nested = container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .b) 298 | try nested.encode(b, forKey: .a) 299 | } 300 | try c.encode(to: container.superEncoder(forKey: .c)) 301 | try d.encode(to: container.superEncoder()) 302 | } 303 | 304 | enum CodingKeys: CodingKey { 305 | case a, b, c, d 306 | } 307 | enum NestedCodingKeys: CodingKey { 308 | case a 309 | } 310 | } 311 | 312 | let values = KeyedCodable(a: 0.0, b: "test", c: -33, d: .infinity) 313 | try XCTAssertEqual(decoder.decode(KeyedCodable.self, from: encoder.encode([values])), [values]) 314 | } 315 | 316 | func testNestedUnkeyedContainer() { 317 | struct UnkeyedCodable: Codable, Equatable { 318 | var a: Float, b: String, c: Int 319 | 320 | init(a: Float, b: String, c: Int) { 321 | self.a = a 322 | self.b = b 323 | self.c = c 324 | } 325 | 326 | init(from decoder: Decoder) throws { 327 | var container = try decoder.unkeyedContainer() 328 | do { 329 | var nested = try container.nestedUnkeyedContainer() 330 | a = try nested.decode(Float.self) 331 | } 332 | b = try container.nestedContainer(keyedBy: NestedCodingKeys.self).decode(String.self, forKey: .a) 333 | c = try Int(from: container.superDecoder()) 334 | } 335 | func encode(to encoder: Encoder) throws { 336 | var container = encoder.unkeyedContainer() 337 | do { 338 | var nested = container.nestedUnkeyedContainer() 339 | try nested.encode(a) 340 | } 341 | do { 342 | var nested = container.nestedContainer(keyedBy: NestedCodingKeys.self) 343 | try nested.encode(b, forKey: .a) 344 | } 345 | try c.encode(to: container.superEncoder()) 346 | } 347 | 348 | enum NestedCodingKeys: CodingKey { 349 | case a 350 | } 351 | } 352 | 353 | let values = UnkeyedCodable(a: 0.0, b: "test", c: -33) 354 | try XCTAssertEqual(decoder.decode(UnkeyedCodable.self, from: encoder.encode([values])), [values]) 355 | } 356 | 357 | func testReadme() throws { 358 | struct SomeStruct: Equatable, Codable { 359 | var a: Int, b: Double?, c: String 360 | } 361 | struct OtherStruct: Equatable, Codable { 362 | var float: Float?, some: SomeStruct 363 | } 364 | 365 | do { 366 | let values = [ 367 | OtherStruct(float: 5.5, some: .init(a: 4, b: .infinity, c: "abc")), 368 | OtherStruct(float: nil, some: .init(a: -3, b: nil, c: "")) 369 | ] 370 | 371 | let string = try encoder.encode(values) 372 | print(string) 373 | } 374 | do { 375 | let string = """ 376 | a,b,c 377 | 4,,test 378 | 6,9.9,ss 379 | """ 380 | 381 | let value = try decoder.decode(SomeStruct.self, from: string) 382 | XCTAssertEqual(value, [ 383 | SomeStruct(a: 4, b: nil, c: "test"), 384 | SomeStruct(a: 6, b: 9.9, c: "ss") 385 | ]) 386 | } 387 | } 388 | 389 | static var allTests = [ 390 | ("testEscaping", testEscaping), 391 | ("testTokenizer", testTokenizer), 392 | ("testRoundtrip", testRoundtrip), 393 | 394 | ("testSingleValueRoundtrip", testSingleValueRoundtrip), 395 | ("testKeyedRoundtrip", testKeyedRoundtrip), 396 | ("testUnkeyedRoundtrip", testUnkeyedRoundtrip), 397 | 398 | ("testBehaviours", testBehaviours), 399 | 400 | ("testNestedKeyedContainers", testNestedKeyedContainers), 401 | ("testNestedUnkeyedContainer", testNestedUnkeyedContainer), 402 | 403 | ("testReadme", testReadme), 404 | ] 405 | } 406 | 407 | extension Tokens.Token: Equatable { 408 | public static func == (lhs: Tokens.Token, rhs: Tokens.Token) -> Bool { 409 | switch (lhs, rhs) { 410 | case let (.escaped(l), .escaped(r)) where l == r, 411 | let (.unescaped(l), .unescaped(r)) where l == r: 412 | return true 413 | case let (.invalid(l), .invalid(r)): 414 | switch (l, r) { 415 | case (.unclosedQoute, .unclosedQoute), 416 | (.unescapedQuote, .unescapedQuote): 417 | return true 418 | case let (.invalidEscaping(lc), .invalidEscaping(rc)) where lc == rc: 419 | return true 420 | default: return false 421 | } 422 | case (.rowBoundary, .rowBoundary): 423 | return true 424 | default: return false 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /Tests/LNTBinaryCodingTests/BinaryCoderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import LNTBinaryCoding 3 | 4 | final class BinaryCodingTests: XCTestCase { 5 | let encoder = BinaryEncoder(), decoder = BinaryDecoder() 6 | 7 | func testCodingPath() throws { 8 | struct A: Codable { 9 | init() { } 10 | init(from decoder: Decoder) throws { 11 | var path: [CodingKey] 12 | 13 | let container = try decoder.container(keyedBy: CodingKeys.self) 14 | path = container.codingPath 15 | XCTAssertTrue(path.isEmpty) 16 | 17 | do { 18 | let decoder = try container.superDecoder(forKey: .a) 19 | var container = try decoder.unkeyedContainer() 20 | path = container.codingPath 21 | XCTAssertEqual(path.map { $0.intValue }, [nil]) 22 | XCTAssertEqual(path.map { $0.stringValue }, ["a"]) 23 | 24 | do { 25 | let decoder = try container.superDecoder() 26 | let container = try decoder.singleValueContainer() 27 | path = container.codingPath 28 | XCTAssertEqual(path.map { $0.intValue }, [nil, 0]) 29 | XCTAssertEqual(path.map { $0.stringValue }, ["a", "0"]) 30 | 31 | path = decoder.codingPath 32 | XCTAssertEqual(path.map { $0.intValue }, [nil, 0]) 33 | XCTAssertEqual(path.map { $0.stringValue }, ["a", "0"]) 34 | 35 | XCTAssertEqual(decoder.userInfo[CodingUserInfoKey(rawValue: "asdf")!] as? Int, 343) 36 | } 37 | } 38 | } 39 | func encode(to encoder: Encoder) throws { 40 | var path: [CodingKey] 41 | 42 | var container = encoder.container(keyedBy: CodingKeys.self) 43 | path = container.codingPath 44 | XCTAssertTrue(path.isEmpty) 45 | 46 | do { 47 | let encoder = container.superEncoder(forKey: .a) 48 | var container = encoder.unkeyedContainer() 49 | path = container.codingPath 50 | XCTAssertEqual(path.map { $0.intValue }, [nil]) 51 | XCTAssertEqual(path.map { $0.stringValue }, ["a"]) 52 | 53 | do { 54 | let encoder = container.superEncoder() 55 | let container = encoder.singleValueContainer() 56 | path = container.codingPath 57 | XCTAssertEqual(path.map { $0.intValue }, [nil, 0]) 58 | XCTAssertEqual(path.map { $0.stringValue }, ["a", "0"]) 59 | 60 | path = encoder.codingPath 61 | XCTAssertEqual(path.map { $0.intValue }, [nil, 0]) 62 | XCTAssertEqual(path.map { $0.stringValue }, ["a", "0"]) 63 | 64 | XCTAssertEqual(encoder.userInfo[CodingUserInfoKey(rawValue: "asdg")!] as? Int, 324) 65 | } 66 | } 67 | } 68 | 69 | enum CodingKeys: CodingKey { 70 | case a 71 | } 72 | } 73 | 74 | var decoder = self.decoder, encoder = self.encoder 75 | decoder.userInfo[CodingUserInfoKey(rawValue: "asdf")!] = 343 76 | encoder.userInfo[CodingUserInfoKey(rawValue: "asdg")!] = 324 77 | _ = try decoder.decode(A.self, from: encoder.encode(A())) 78 | } 79 | 80 | func testSingleValueContainerRoundtrip() throws { 81 | // Delegated roundtrips 82 | try XCTAssertEqual(decoder.decode(Bool.self, from: encoder.encode(false)), false) 83 | try XCTAssertEqual(decoder.decode(Bool.self, from: encoder.encode(true)), true) 84 | try XCTAssertEqual(decoder.decode(Double?.self, from: encoder.encode(5.0 as Double?)), 5.0) 85 | try XCTAssertEqual(decoder.decode(Float.self, from: encoder.encode(4.2 as Float)), 4.2) 86 | 87 | // String roundtrips 88 | try XCTAssertEqual(decoder.decode(String.self, from: encoder.encode("ffah")), "ffah") 89 | 90 | // Signed roundtrips 91 | try XCTAssertEqual(decoder.decode(Int.self, from: encoder.encode(0x17)), 0x17) 92 | try XCTAssertEqual(decoder.decode(Int.self, from: encoder.encode(-0x1419)), -0x1419) 93 | try XCTAssertEqual(decoder.decode(Int.self, from: encoder.encode(-0x1919a077)), -0x1919a077) 94 | try XCTAssertEqual(decoder.decode(Int.self, from: encoder.encode(-0x19197fabd93bca07)), -0x19197fabd93bca07) 95 | 96 | // Unsigned roundtrips 97 | try XCTAssertEqual(decoder.decode(UInt.self, from: encoder.encode(0x17 as UInt)), 0x17) 98 | try XCTAssertEqual(decoder.decode(UInt.self, from: encoder.encode(0x1419 as UInt)), 0x1419) 99 | try XCTAssertEqual(decoder.decode(UInt.self, from: encoder.encode(0x19154977 as UInt)), 0x19154977) 100 | try XCTAssertEqual(decoder.decode(UInt.self, from: encoder.encode(0x19197fabd93bca07 as UInt)), 0x19197fabd93bca07) 101 | } 102 | 103 | func testUnkeyedContainerRoundtrip() throws { 104 | do { 105 | let value = [1, 2, 3, 4, nil, 5, 6, 7, 5, 3, 4, 5, 6] 106 | try XCTAssertEqual(decoder.decode([Int?].self, from: encoder.encode(value)), value) 107 | } 108 | 109 | do { 110 | let value: [Int?] = [nil, nil, nil, nil, nil, nil] 111 | try XCTAssertEqual(decoder.decode([Int?].self, from: encoder.encode(value)), value) 112 | } 113 | do { 114 | let value = (0..<128).map(String.init) 115 | try XCTAssertEqual(decoder.decode([String].self, from: encoder.encode(value)), value) 116 | } 117 | } 118 | 119 | func testKeyedContainerRoundtrip() { 120 | do { 121 | struct Test: Codable, Equatable { 122 | enum B: Int, Codable { case a, b, c } 123 | var a: String?, b: B, c: Int 124 | } 125 | 126 | let value = Test(a: "asdfhjjdn", b: .a, c: 994) 127 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(value)), value) 128 | } 129 | 130 | do { 131 | struct Test: Codable, Equatable { var a, b, c, d: Int } 132 | let value = Test(a: 1, b: 2, c: 3, d: 5) 133 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(value)), value) 134 | } 135 | do { 136 | struct Test: Codable, Equatable { var a, b, c, d, e: Int16, f: String } 137 | let value = Test(a: 1, b: 2, c: 3, d: 5, e: 2, f: "") 138 | try XCTAssertEqual(decoder.decode(Test.self, from: encoder.encode(value)), value) 139 | } 140 | do { 141 | let value: [String: Int?] = ["a": 1, "b": nil] 142 | try XCTAssertEqual(decoder.decode([String: Int?].self, from: encoder.encode(value)), value) 143 | } 144 | do { 145 | struct A: Codable, Equatable { var a, b, c, d, e, f, g: Int? } 146 | try XCTAssertEqual(decoder.decode(A.self, from: encoder.encode(A())), A()) 147 | } 148 | } 149 | 150 | func testDecoder() throws { 151 | try XCTAssertNil(decoder.decode(Int?.self, from: Data([0,0,0]))) 152 | 153 | // Keyed containers will picked latter values. 154 | do { 155 | try XCTAssertEqual(decoder.decode([String: Int8].self, from: Data( 156 | [0,0, 157 | 1,Character("a").asciiValue!,0, 158 | Header.Tag.regularKeyed.rawValue, 2,1, 2,1, 0x1, 159 | 1,0, 160 | 2,1 161 | ])), ["a": 1]) 162 | try XCTAssertEqual(decoder.decode([String: Int8].self, from: Data( 163 | [0,0, 164 | 1,Character("a").asciiValue!,0, 165 | Header.Tag.equisizeKeyed.rawValue, 2, 1,1,0x0, 166 | 1,0, 167 | 2,1 168 | ])), ["a": 1]) 169 | try XCTAssertEqual(decoder.decode([String: Int8].self, from: Data( 170 | [0,0, 171 | 1,Character("a").asciiValue!,0, 172 | Header.Tag.uniformKeyed.rawValue, 2, 1,1,0x0, Header.Tag.signed.rawValue, 173 | 2, 174 | 1 175 | ])), ["a": 1]) 176 | } 177 | 178 | // Uniform unkeyed container of `nil` 179 | do { 180 | let data = try encoder.encode(Array(repeating: nil as Int?, count: 10)) 181 | 182 | struct A: Decodable { 183 | init(from decoder: Decoder) throws { 184 | var container = try decoder.unkeyedContainer() 185 | for _ in 0..<10 { 186 | try XCTAssertTrue(container.decodeNil()) 187 | try XCTAssertNil(container.decode(Int?.self)) 188 | } 189 | } 190 | } 191 | 192 | try _ = decoder.decode(A.self, from: data) 193 | } 194 | 195 | // Conding fixed width of differing sizes 196 | do { 197 | struct A: Encodable { 198 | func encode(to encoder: Encoder) throws { 199 | var container = encoder.unkeyedContainer() 200 | try container.encode(-10 as Int8) 201 | try container.encode(200 as Int16) 202 | try container.encode(201 as Int16) 203 | } 204 | } 205 | 206 | try XCTAssertEqual(decoder.decode([Int].self, from: encoder.encode(A())), [-10, 200, 201]) 207 | } 208 | } 209 | 210 | func testNestedKeyedContainer() throws { 211 | struct KeyedCodable: Codable, Equatable { 212 | var a: Float, b: String, c: Int, d: UInt 213 | 214 | init(_ values: (Float, String, Int, UInt)) { (a, b, c, d) = values } 215 | 216 | init(from decoder: Decoder) throws { 217 | let container = try decoder.container(keyedBy: CodingKeys.self) 218 | try XCTAssertFalse(container.decodeNil(forKey: .a)) 219 | try XCTAssertTrue(container.decodeNil(forKey: .e)) 220 | try XCTAssertTrue(container.decodeNil(forKey: .f)) 221 | XCTAssertTrue(container.contains(.e)) 222 | XCTAssertFalse(container.contains(.f)) 223 | XCTAssertEqual(Set(container.allKeys), [.a, .b, .c, .e]) 224 | 225 | do { 226 | var nested = try container.nestedUnkeyedContainer(forKey: .a) 227 | a = try nested.decode(Float.self) 228 | } 229 | b = try container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .b).decode(String.self, forKey: .a) 230 | c = try Int(from: container.superDecoder(forKey: .c)) 231 | d = try UInt(from: container.superDecoder()) 232 | } 233 | func encode(to encoder: Encoder) throws { 234 | var container = encoder.container(keyedBy: CodingKeys.self) 235 | do { 236 | var nested = container.nestedUnkeyedContainer(forKey: .a) 237 | try nested.encode(a) 238 | } 239 | do { 240 | var nested = container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .b) 241 | try nested.encode(b, forKey: .a) 242 | } 243 | try c.encode(to: container.superEncoder(forKey: .c)) 244 | try d.encode(to: container.superEncoder()) 245 | try container.encodeNil(forKey: .e) 246 | } 247 | 248 | enum CodingKeys: CodingKey, Equatable { 249 | case a, b, c, e, f 250 | } 251 | enum NestedCodingKeys: CodingKey { 252 | case a 253 | } 254 | } 255 | 256 | let values = KeyedCodable((0.0, "test", -33, 4)) 257 | try XCTAssertEqual(decoder.decode(KeyedCodable.self, from: encoder.encode(values)), values) 258 | } 259 | 260 | func testNestedUnkeyedContainer() { 261 | struct UnkeyedCodable: Codable, Equatable { 262 | var a: Float, b: String, c: Int 263 | 264 | init(_ values: (Float, String, Int)) { (a, b, c) = values } 265 | 266 | init(from decoder: Decoder) throws { 267 | var container = try decoder.unkeyedContainer() 268 | 269 | try XCTAssertFalse(container.decodeNil()) 270 | do { 271 | var nested = try container.nestedUnkeyedContainer() 272 | a = try nested.decode(Float.self) 273 | } 274 | b = try container.nestedContainer(keyedBy: NestedCodingKeys.self).decode(String.self, forKey: .a) 275 | c = try Int(from: container.superDecoder()) 276 | try XCTAssertTrue(container.decodeNil()) 277 | XCTAssertFalse(container.isAtEnd) 278 | try XCTAssertNil(container.decode(Bool?.self)) 279 | 280 | XCTAssertTrue(container.isAtEnd) 281 | try XCTAssertTrue(container.decodeNil()) 282 | } 283 | func encode(to encoder: Encoder) throws { 284 | var container = encoder.unkeyedContainer() 285 | do { 286 | var nested = container.nestedUnkeyedContainer() 287 | try nested.encode(a) 288 | } 289 | do { 290 | var nested = container.nestedContainer(keyedBy: NestedCodingKeys.self) 291 | try nested.encode(b, forKey: .a) 292 | } 293 | try c.encode(to: container.superEncoder()) 294 | try container.encodeNil() 295 | } 296 | 297 | enum NestedCodingKeys: CodingKey { 298 | case a 299 | } 300 | } 301 | 302 | let values = UnkeyedCodable((0.0, "test", -33)) 303 | try XCTAssertEqual(decoder.decode(UnkeyedCodable.self, from: encoder.encode(values)), values) 304 | } 305 | 306 | func testStructuralError() { 307 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data())) // Empty file 308 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data([0,1]))) // Invalid version 309 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data([0,0,0x80]))) // Invalid String Map count 310 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data([0,0, 0, 0x0, 3,0x01,0]))) // Invalid Tag 311 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data([0,0, 1,0x80,0]))) // Invalid String 312 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data([0,0, 1,0x80]))) // Invalid String 313 | } 314 | 315 | func testSingleValueError() { 316 | // Decoding from nil 317 | try XCTAssertThrowsError(decoder.decode(Int.self, from: Data([0,0,0,]))) 318 | try XCTAssertThrowsError(decoder.decode(String.self, from: Data([0,0,0,]))) 319 | 320 | // Past the end 321 | do { 322 | struct A: Codable { 323 | init() { } 324 | init(from decoder: Decoder) throws { 325 | var container = try decoder.unkeyedContainer() 326 | for _ in 0..<127 { 327 | try XCTAssertNil(container.decode(Bool?.self)) 328 | } 329 | try _ = container.decode(Bool?.self) 330 | } 331 | func encode(to encoder: Encoder) throws { 332 | var container = encoder.unkeyedContainer() 333 | for _ in 0..<127 { 334 | try container.encodeNil() 335 | } 336 | } 337 | } 338 | 339 | try XCTAssertThrowsError(decoder.decode(A.self, from: encoder.encode(A()))) 340 | } 341 | } 342 | 343 | func testKeyedError() { 344 | let stringMapA: [UInt8] = [0,0, 1,Character("a").asciiValue!,0] 345 | 346 | // Container Too Small 347 | try XCTAssertThrowsError(decoder.decode([String: Int].self, from: Data(stringMapA + [Header.Tag.regularKeyed.rawValue,10,1,0x01, 00]))) 348 | try XCTAssertThrowsError(decoder.decode([String: Int].self, from: Data(stringMapA + [Header.Tag.equisizeKeyed.rawValue,10,1,0x00, 00]))) 349 | try XCTAssertThrowsError(decoder.decode([String: Int].self, from: Data(stringMapA + [Header.Tag.uniformKeyed.rawValue,10,0x01,1, 00]))) 350 | 351 | // Invalid Element 352 | try XCTAssertThrowsError(decoder.decode([String: Int].self, from: Data(stringMapA + [Header.Tag.regularKeyed.rawValue,2,1,0x01, 0x00,0]))) 353 | try XCTAssertThrowsError(decoder.decode([String: Int].self, from: Data(stringMapA + [Header.Tag.equisizeKeyed.rawValue,2,1,0x00, 0x00,0]))) 354 | 355 | // Key not found 356 | do { 357 | struct A: Codable { var a, b: Int } 358 | struct B: Codable { var a = 0, c = "" } 359 | try XCTAssertThrowsError(decoder.decode(A.self, from: encoder.encode(B()))) 360 | } 361 | 362 | // Key not found - Uniform 363 | do { 364 | struct A: Codable { var a = 0, b = 0, c = 0, d = 0, e = 0, f = 0 } 365 | struct B: Codable { var a = 0, b = 0, c = 0, d = 0, e = 0 } 366 | try XCTAssertThrowsError(decoder.decode(A.self, from: encoder.encode(B()))) 367 | } 368 | } 369 | 370 | func testUnkeyedError() { 371 | // Container Too Small 372 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data([0,0,0, Header.Tag.regularUnkeyed.rawValue,2,2,0x1, 0]))) 373 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data([0,0,0, Header.Tag.equisizeUnkeyed.rawValue,2,1]))) 374 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data([0,0,0, Header.Tag.uniformUnkeyed.rawValue,2,2,1, 1]))) 375 | 376 | // Invalid Element 377 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data([0,0,0, Header.Tag.regularUnkeyed.rawValue,2,2,0x1, 0,0,0,0]))) 378 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data( 379 | [0,0,0, Header.Tag.equisizeUnkeyed.rawValue,2,1, 380 | Header.Tag.string.rawValue,0x80]))) 381 | try XCTAssertThrowsError(decoder.decode([Int].self, from: Data( 382 | [0,0,0, Header.Tag.uniformUnkeyed.rawValue,2,2, 383 | Header.Tag.string.rawValue,0x80,0x80]))) 384 | 385 | // Invalid element while `decodeNil` 386 | do { 387 | struct A: Decodable { 388 | init(from decoder: Decoder) throws { 389 | var container = try decoder.unkeyedContainer() 390 | _ = try container.decodeNil() 391 | } 392 | } 393 | 394 | try XCTAssertThrowsError(decoder.decode(A.self, from: Data( 395 | [0,0,0, Header.Tag.equisizeUnkeyed.rawValue,2,1, 396 | 0,0x80]))) 397 | } 398 | } 399 | 400 | func testErrorWrongContainer() { 401 | // Value out of range 402 | try XCTAssertThrowsError(decoder.decode(Int.self, from: encoder.encode(UInt.max))) 403 | try XCTAssertThrowsError(decoder.decode(UInt.self, from: encoder.encode(-1))) 404 | 405 | // To String 406 | try XCTAssertThrowsError(decoder.decode(String.self, from: encoder.encode(0))) 407 | try XCTAssertThrowsError(decoder.decode(String.self, from: encoder.encode(0 as UInt))) 408 | try XCTAssertThrowsError(decoder.decode(String.self, from: Data([0,0,0, Header.Tag.string.rawValue,1]))) 409 | 410 | // Requesting keyed, unkeyed, and single from different category. 411 | try XCTAssertThrowsError(decoder.decode([Int: String].self, from: encoder.encode([1, 2, 3]))) 412 | try XCTAssertThrowsError(decoder.decode(Int.self, from: encoder.encode([1, 2, 3]))) 413 | try XCTAssertThrowsError(decoder.decode([Int].self, from: encoder.encode(2))) 414 | } 415 | 416 | static var allTests = [ 417 | ("testSingleValueContainerRoundtrip", testSingleValueContainerRoundtrip), 418 | ("testUnkeyedContainerRoundtrip", testUnkeyedContainerRoundtrip), 419 | ("testKeyedContainerRoundtrip", testKeyedContainerRoundtrip), 420 | 421 | ("testDecoder", testDecoder), 422 | 423 | ("testNestedKeyedContainer", testNestedKeyedContainer), 424 | ("testNestedUnkeyedContainer", testNestedUnkeyedContainer), 425 | 426 | ("testSingleValueError", testSingleValueError), 427 | ("testKeyedError", testKeyedError), 428 | ("testUnkeyedError", testUnkeyedError), 429 | ("testErrorWrongContainer", testErrorWrongContainer) 430 | ] 431 | } 432 | 433 | --------------------------------------------------------------------------------