From 0490d2cd6f623e556fd786d6bba7a35987530316 Mon Sep 17 00:00:00 2001 From: Damiaan Dufaux Date: Tue, 1 Aug 2017 17:26:55 +0200 Subject: [PATCH] Encode keyed containers --- .gitignore | 1 + MsgPack.xcodeproj/project.pbxproj | 8 +- MsgPack/Encoder.swift | 240 ++++++++++++++++++++++----- MsgPack/Format.swift | 188 ++++++++++++--------- MsgPackTests/MsgPackTests.swift | 44 +++-- Playground.playground/Contents.swift | 38 +++-- 6 files changed, 363 insertions(+), 156 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f564e54 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/MsgPack/JSON.swift diff --git a/MsgPack.xcodeproj/project.pbxproj b/MsgPack.xcodeproj/project.pbxproj index 7060283..6bc6701 100644 --- a/MsgPack.xcodeproj/project.pbxproj +++ b/MsgPack.xcodeproj/project.pbxproj @@ -7,12 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 7495EA251F30B393000EBDF6 /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FA3B621F2C9096005CE521 /* Encoder.swift */; }; 74A1AE171F2E3E8300B139A3 /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1AE161F2E3E8300B139A3 /* Format.swift */; }; 74FA3B501F2C9060005CE521 /* MsgPack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74FA3B461F2C9060005CE521 /* MsgPack.framework */; }; 74FA3B551F2C9060005CE521 /* MsgPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FA3B541F2C9060005CE521 /* MsgPackTests.swift */; }; 74FA3B571F2C9060005CE521 /* MsgPack.h in Headers */ = {isa = PBXBuildFile; fileRef = 74FA3B491F2C9060005CE521 /* MsgPack.h */; settings = {ATTRIBUTES = (Public, ); }; }; 74FA3B611F2C908D005CE521 /* DecoderHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FA3B601F2C908D005CE521 /* DecoderHelper.swift */; }; - 74FA3B631F2C9096005CE521 /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FA3B621F2C9096005CE521 /* Encoder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -27,6 +27,7 @@ /* Begin PBXFileReference section */ 74A1AE161F2E3E8300B139A3 /* Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Format.swift; sourceTree = ""; }; + 74A426DB1F30774F001BE9C4 /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; 74FA3B461F2C9060005CE521 /* MsgPack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MsgPack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74FA3B491F2C9060005CE521 /* MsgPack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MsgPack.h; sourceTree = ""; }; 74FA3B4A1F2C9060005CE521 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -82,6 +83,7 @@ 74FA3B621F2C9096005CE521 /* Encoder.swift */, 74A1AE161F2E3E8300B139A3 /* Format.swift */, 74FA3B4A1F2C9060005CE521 /* Info.plist */, + 74A426DB1F30774F001BE9C4 /* JSON.swift */, ); path = MsgPack; sourceTree = ""; @@ -204,7 +206,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74FA3B631F2C9096005CE521 /* Encoder.swift in Sources */, + 7495EA251F30B393000EBDF6 /* Encoder.swift in Sources */, 74A1AE171F2E3E8300B139A3 /* Format.swift in Sources */, 74FA3B611F2C908D005CE521 /* DecoderHelper.swift in Sources */, ); @@ -353,7 +355,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - GCC_OPTIMIZATION_LEVEL = fast; + GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = MsgPack/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; diff --git a/MsgPack/Encoder.swift b/MsgPack/Encoder.swift index 5976806..6b09c24 100644 --- a/MsgPack/Encoder.swift +++ b/MsgPack/Encoder.swift @@ -9,26 +9,29 @@ import Foundation public class Encoder { - let serialiser = Serialiser() + let intermediate = IntermediateEncoder() public init() {} public func encode(_ value: T) throws -> Data { - try value.encode(to: serialiser) + try value.encode(to: intermediate) var data = Data() - try serialiser.storage?.appendTo(data: &data) + try intermediate.container?.getFormat().appendTo(data: &data) return data } } -class Serialiser: Swift.Encoder { +class IntermediateEncoder: Swift.Encoder { + var codingPath = [CodingKey]() var userInfo = [CodingUserInfoKey : Any]() - var storage: Format? + var container: MessagePackEncodingContainer? func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - preconditionFailure() + let keyedContainer = MsgPackKeyedEncodingContainer() + container = keyedContainer + return KeyedEncodingContainer(keyedContainer) } func unkeyedContainer() -> UnkeyedEncodingContainer { @@ -36,94 +39,239 @@ class Serialiser: Swift.Encoder { } func singleValueContainer() -> SingleValueEncodingContainer { - return self + let singleValueContainer = MsgPackSingleValueEncodingContainer() + container = singleValueContainer + return singleValueContainer } } -extension MsgPack.Serialiser: SingleValueEncodingContainer { +extension Format { + static func from(string: String) throws -> Format { + guard let data = string.data(using: .utf8) else {throw MsgPackEncodingError.stringNotConvertibleToUTF8(string)} + switch data.count { + case 1..<32: + return .fixString(data) + case 32..<256: + return .string8(data) + case 256..<65536: + return .string16(data) + default: + return .string32(data) + } + } + + static func from(keyValuePairs: [(Format, Format)]) -> Format { + switch keyValuePairs.count { + case 1..<16: + return .fixMap(keyValuePairs) + case 16..<65536: + return .map16(keyValuePairs) + default: + return .map32(keyValuePairs) + } + } + + static func from(int: Int) -> Format { + #if arch(arm) || arch(i386) + return .int32(Int32(int)) + #else + return .int64(Int64(int)) + #endif + } + + static func from(uInt: UInt) -> Format { + #if arch(arm) || arch(i386) + return .uInt32(UInt32(uInt)) + #else + return .uInt64(UInt64(uInt)) + #endif + } +} + +class MessagePackEncodingContainer { + var codingPath: [CodingKey] = [] + + func getFormat() throws -> Format { + preconditionFailure() + } +} + +enum MsgPackEncodingError: Swift.Error { + case notImplemented, stringNotConvertibleToUTF8(String) +} + +class MsgPackSingleValueEncodingContainer: MessagePackEncodingContainer, SingleValueEncodingContainer { + + var storage: Format? + enum Error: Swift.Error { - case notImplemented, stringNotConvertibleToUTF8 + case noValue + } + + override func getFormat() throws -> Format { + guard let format = storage else {throw Error.noValue} + return format + } + + init(with storage: Format? = nil) { + self.storage = storage } func encodeNil() throws { storage = .nil } - + func encode(_ value: Bool) throws { storage = .boolean(value) } - + func encode(_ value: Int) throws { - #if arch(arm) || arch(i386) - storage = .int32(Int32(value)) - #else - storage = .int64(Int64(value)) - #endif + storage = .from(int: value) } - + func encode(_ value: Int8) throws { storage = .int8(value) } - + func encode(_ value: Int16) throws { storage = .int16(value) } - + func encode(_ value: Int32) throws { storage = .int32(value) } - + func encode(_ value: Int64) throws { storage = .int64(value) } - + func encode(_ value: UInt) throws { - #if arch(arm) || arch(i386) - storage = .uInt32(UInt32(value)) - #else - storage = .uInt64(UInt64(value)) - #endif + storage = .from(uInt: value) } - + func encode(_ value: UInt8) throws { storage = .uInt8(value) } - + func encode(_ value: UInt16) throws { storage = .uInt16(value) } - + func encode(_ value: UInt32) throws { storage = .uInt32(value) } - + func encode(_ value: UInt64) throws { storage = .uInt64(value) } - + func encode(_ value: Float) throws { storage = .float32(value) } - + func encode(_ value: Double) throws { storage = .float64(value) } - + func encode(_ value: String) throws { - guard let data = value.data(using: .utf8) else {throw Error.stringNotConvertibleToUTF8} - switch data.count { - case 1..<32: - storage = .fixString(data) - case 32..<256: - storage = .string8(data) - case 256..<65536: - storage = .string16(data) - default: - storage = .string32(data) - } + storage = try .from(string: value) } - + func encode(_ value: T) throws { - throw Error.notImplemented + throw MsgPackEncodingError.notImplemented + } +} + +class MsgPackKeyedEncodingContainer: MessagePackEncodingContainer, KeyedEncodingContainerProtocol { + var userInfo = [CodingUserInfoKey : Any]() + + var storage = [String: MessagePackEncodingContainer]() + + override func getFormat() throws -> Format { + return try Format.from(keyValuePairs: storage.map { + (try Format.from(string: $0.key), try $0.value.getFormat()) + }) + } + + func encodeNil(forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .nil) + } + + func encode(_ value: Bool, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .boolean(value) ) + } + + func encode(_ value: Int, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .from(int: value) ) + } + + func encode(_ value: Int8, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .int8(value) ) + } + + func encode(_ value: Int16, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .int16(value) ) + } + + func encode(_ value: Int32, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .int32(value) ) + } + + func encode(_ value: Int64, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .int64(value) ) + } + + func encode(_ value: UInt, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .from(uInt: value) ) + } + + func encode(_ value: UInt8, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .uInt8(value) ) + } + + func encode(_ value: UInt16, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .uInt16(value) ) + } + + func encode(_ value: UInt32, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .uInt32(value) ) + } + + func encode(_ value: UInt64, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .uInt64(value) ) + } + + func encode(_ value: Float, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .float32(value) ) + } + + func encode(_ value: Double, forKey key: K) throws { + storage[key.stringValue] = MsgPackSingleValueEncodingContainer(with: .float64(value) ) + } + + func encode(_ value: String, forKey key: K) throws { + storage[key.stringValue] = try MsgPackSingleValueEncodingContainer(with: .from(string: value) ) + } + + func encode(_ value: T, forKey key: K) throws where T : Encodable { + throw MsgPackEncodingError.notImplemented + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer where NestedKey : CodingKey { + let keyedContainer = MsgPackKeyedEncodingContainer() + storage[key.stringValue] = keyedContainer + return KeyedEncodingContainer(keyedContainer) + } + + func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { + preconditionFailure("not implemented") + } + + func superEncoder() -> Swift.Encoder { + preconditionFailure("not implemented") + } + + func superEncoder(forKey key: K) -> Swift.Encoder { + preconditionFailure("not implemented") } } diff --git a/MsgPack/Format.swift b/MsgPack/Format.swift index 1d61b1e..d06b24a 100644 --- a/MsgPack/Format.swift +++ b/MsgPack/Format.swift @@ -32,10 +32,21 @@ enum Format { case float64(Double) case fixString(Data) - case string8(Data) - case string16(Data) - case string32(Data) + case string8 (Data) + case string16 (Data) + case string32 (Data) + + case fixArray([Format]) + case array16 ([Format]) + case array32 ([Format]) + case fixMap([(key: Format, value: Format)]) + case map16 ([(key: Format, value: Format)]) + case map32 ([(key: Format, value: Format)]) +} + + +extension Format { func appendTo(data: inout Data) { switch self { @@ -59,117 +70,136 @@ enum Format { data.append(value) case .uInt16(let value): var newData = Data(count: 3) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xCD - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt16.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xCD + newData.write(value: value.bigEndian, offset: 1) data.append(newData) case .uInt32(let value): var newData = Data(count: 5) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xCE - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt32.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xCE + newData.write(value: value.bigEndian, offset: 1) data.append(newData) case .uInt64(let value): var newData = Data(count: 9) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xCF - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt64.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xCF + newData.write(value: value.bigEndian, offset: 1) data.append(newData) // MARK: Signed integers case .int8(let value): var newData = Data(count: 2) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xD1 - byteContainer.advanced(by: 1).withMemoryRebound(to: Int8.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xD0 + newData.write(value: value.bigEndian, offset: 1) data.append(newData) case .int16(let value): var newData = Data(count: 3) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xD2 - byteContainer.advanced(by: 1).withMemoryRebound(to: Int16.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xD1 + newData.write(value: value.bigEndian, offset: 1) data.append(newData) case .int32(let value): var newData = Data(count: 5) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xD2 - byteContainer.advanced(by: 1).withMemoryRebound(to: Int32.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xD2 + newData.write(value: value.bigEndian, offset: 1) data.append(newData) case .int64(let value): var newData = Data(count: 9) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xD3 - byteContainer.advanced(by: 1).withMemoryRebound(to: Int64.self, capacity: 1) { - $0.pointee = value.bigEndian - } - }) + newData[0] = 0xD3 + newData.write(value: value.bigEndian, offset: 1) data.append(newData) // MARK: Floats case .float32(let value): var newData = Data(count: 5) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xCA - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt32.self, capacity: 1) { - $0.pointee = value.bitPattern.bigEndian - } - }) + newData[0] = 0xCA + newData.write(value: value.bitPattern.bigEndian, offset: 1) data.append(newData) case .float64(let value): var newData = Data(count: 9) - newData.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xCB - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt64.self, capacity: 1) { - $0.pointee = value.bitPattern.bigEndian - } - }) + newData[0] = 0xCB + newData.write(value: value.bitPattern.bigEndian, offset: 1) data.append(newData) // MARK: Strings - case .fixString(let value): - data.append(UInt8(value.count) & 0b00011111 | 0b10100000) - data.append(value) - case .string8(let value): - data.append(contentsOf: [0xD9, UInt8(value.count)]) - data.append(value) - case .string16(let value): + case .fixString(let utf8Data): + precondition(utf8Data.count < 32, "fix strings cannot contain more than 31 bytes") + data.append( UInt8(utf8Data.count) | 0b10100000) + data.append(utf8Data) + case .string8(let utf8Data): + data.append(contentsOf: [0xD9, UInt8(utf8Data.count)]) + data.append(utf8Data) + case .string16(let utf8Data): var prefix = Data(count: 3) - prefix.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xDA - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt16.self, capacity: 1) { - $0.pointee = UInt16(value.count).bigEndian - } - }) + prefix[0] = 0xDA + prefix.write(value: UInt16(utf8Data.count).bigEndian, offset: 1) data.append(prefix) - data.append(value) - case .string32(let value): + data.append(utf8Data) + case .string32(let utf8Data): var prefix = Data(count: 5) - prefix.withUnsafeMutableBytes({ (byteContainer: UnsafeMutablePointer) -> Void in - byteContainer.pointee = 0xDB - byteContainer.advanced(by: 1).withMemoryRebound(to: UInt32.self, capacity: 1) { - $0.pointee = UInt32(value.count).bigEndian - } - }) + prefix[0] = 0xDB + prefix.write(value: UInt32(utf8Data.count).bigEndian, offset: 1) data.append(prefix) - data.append(value) + data.append(utf8Data) + + // MARK: Arrays + case .fixArray(let array): + precondition(array.count < 16, "fix arrays cannot contain more than 15 elements") + data.append( UInt8(array.count) | 0b10010000) + for element in array { + element.appendTo(data: &data) + } + case .array16(let array): + var prefix = Data(count: 3) + prefix[0] = 0xDC + prefix.write(value: UInt16(array.count).bigEndian, offset: 1) + data.append(prefix) + for element in array { + element.appendTo(data: &data) + } + case .array32(let array): + var prefix = Data(count: 5) + prefix[0] = 0xDD + prefix.write(value: UInt32(array.count).bigEndian, offset: 1) + data.append(prefix) + for element in array { + element.appendTo(data: &data) + } + + // MARK: Maps + case .fixMap(let pairs): + precondition(pairs.count < 16, "fix maps cannot contain more than 15 key-value pairs") + data.append( UInt8(pairs.count) | 0b10000000) + for (key, value) in pairs { + key.appendTo(data: &data) + value.appendTo(data: &data) + } + case .map16(let pairs): + var prefix = Data(count: 3) + prefix[0] = 0xDE + prefix.write(value: UInt16(pairs.count).bigEndian, offset: 1) + data.append(prefix) + for (key, value) in pairs { + key.appendTo(data: &data) + value.appendTo(data: &data) + } + case .map32(let pairs): + var prefix = Data(count: 5) + prefix[0] = 0xDE + prefix.write(value: UInt32(pairs.count).bigEndian, offset: 1) + data.append(prefix) + for (key, value) in pairs { + key.appendTo(data: &data) + value.appendTo(data: &data) + } + } + } +} + + +extension Data { + mutating func write(value: T, offset: Int) { + withUnsafeMutableBytes {(byteContainer: UnsafeMutablePointer) -> Void in + byteContainer.advanced(by: offset).withMemoryRebound(to: T.self, capacity: 1) { + $0.pointee = value + } } } } diff --git a/MsgPackTests/MsgPackTests.swift b/MsgPackTests/MsgPackTests.swift index f81d99f..c68a612 100644 --- a/MsgPackTests/MsgPackTests.swift +++ b/MsgPackTests/MsgPackTests.swift @@ -10,20 +10,20 @@ import XCTest @testable import MsgPack class MsgPackTests: XCTestCase { - - var encoder: MsgPack.Encoder! - - override func setUp() { - super.setUp() - - encoder = Encoder() - } - // override func tearDown() { // // Put teardown code here. This method is called after the invocation of each test method in the class. // super.tearDown() // } - + + var encoder: MsgPack.Encoder! + + override func setUp() { + super.setUp() + + encoder = Encoder() + } + + func testEncodeTrue() { do { let data = try encoder.encode(true) @@ -99,7 +99,7 @@ class MsgPackTests: XCTestCase { XCTFail(error.localizedDescription) } } - + func testPerformanceOf2MilionUInt32Encodings() { self.measure { for _ in 0 ..< 2000000 { @@ -107,5 +107,27 @@ class MsgPackTests: XCTestCase { } } } + +// var encoder = MsgPack.JSONEncoder2() +// +// struct Obj: Encodable { +// let i: Int +// let p: Person +// } +// +// struct Person: Encodable { +// let name: String +// let friend: Friend +// } +// +// struct Friend: Encodable { +// let count: Double +// } +// +// func testDinges() { +// try! encoder.encode( +// Obj(i: 3, p: Person(name: "Dinges", friend: Friend(count: 8))) +// ) +// } } diff --git a/Playground.playground/Contents.swift b/Playground.playground/Contents.swift index bae2cb5..f887485 100644 --- a/Playground.playground/Contents.swift +++ b/Playground.playground/Contents.swift @@ -5,23 +5,27 @@ import Foundation let encoder = Encoder() -let integerData = try encoder.encode(0x0102030405060708) -String(integerData[0], radix: 16) +try encoder.encode(0x0102030405060708) -integerData[1] -integerData[2] -integerData[3] -integerData[4] -integerData[5] -integerData[6] -integerData[7] -integerData[8] +try encoder.encode("Hello") -let doubleData = try encoder.encode("Hello there 123456789012345678901") -String(doubleData[0], radix: 16) +try encoder.encode("😇") -String(doubleData[1], radix: 16) -String(doubleData[2], radix: 16) -String(doubleData[3], radix: 16) -String(doubleData[4], radix: 16) -String(doubleData[5], radix: 16) +var n: Int? +try encoder.encode(n) + +struct Position: Encodable { + let x: Int8 + let y: Int8 +} + +struct Circle: Encodable { + let center: Position + let radius: UInt +} + +do { + try encoder.encode(Circle(center: Position(x: -1, y: 2), radius: 50)).forEach { print(String($0, radix: 16)) } +} catch { + error +}