diff --git a/Sources/BitWiser/DSL/ByteArrayBuilder.swift b/Sources/BitWiser/DSL/ByteArrayBuilder.swift new file mode 100644 index 0000000..dcf323c --- /dev/null +++ b/Sources/BitWiser/DSL/ByteArrayBuilder.swift @@ -0,0 +1,44 @@ +// +// ByteArrayBuilder.swift +// +// +// Created by Andrea Finollo on 30/01/22. +// + +import Foundation + + +/// `ByteArrayBuilder` is a `@resultBuilder` to create sequence of `Byte` in a DSL style +@resultBuilder +public enum ByteArrayBuilder { + + public static func buildBlock(_ components: [Byte]...) -> [Byte] { + components.flatMap { $0 } + } + + public static func buildExpression(_ expression: [Byte]) -> [Byte] { + expression + } + + public static func buildExpression(_ expression: Byte) -> [Byte] { + [expression] + } + + public static func buildOptional(_ component: [Byte]?) -> [Byte] { + component ?? [] + } + + public static func buildEither(first component: [Byte]) -> [Byte] { + component + } + + public static func buildEither(second component: [Byte]) -> [Byte] { + component + } + + public static func buildArray(_ components: [[Byte]]) -> [Byte] { + return components.flatMap { $0 } + } + + +} diff --git a/Sources/BitWiser/DSL/DataConvertibleBuilder.swift b/Sources/BitWiser/DSL/DataConvertibleBuilder.swift new file mode 100644 index 0000000..9d97912 --- /dev/null +++ b/Sources/BitWiser/DSL/DataConvertibleBuilder.swift @@ -0,0 +1,55 @@ +// +// DataConvertibleBuilder.swift +// +// +// Created by Andrea Finollo on 04/02/22. +// + +import Foundation + +/// `DataConvertibleBuilder` is a `@resultBuilder` to create `Data` in a DSL style +@resultBuilder +public enum DataConvertibleBuilder { + + public static func buildBlock(_ components: DataConvertible...) -> Data { + return components.reduce(Data()) { partialResult, value in + var mutBuffer = partialResult + mutBuffer.append(value.data) + return mutBuffer + } + } + + public static func buildExpression(_ expression: [DataConvertible]) -> Data { + return expression.reduce(Data()) { partialResult, value in + var mutBuffer = partialResult + mutBuffer.append(value.data) + return mutBuffer + } + } + + public static func buildExpression(_ expression: DataConvertible) -> Data { + expression.data + } + + public static func buildOptional(_ component: DataConvertible?) -> Data { + return component?.data ?? Data() + } + + public static func buildEither(first component: DataConvertible) -> Data { + return component.data + } + + public static func buildEither(second component: DataConvertible) -> Data { + return component.data + } + + public static func buildArray(_ components: [DataConvertible]) -> Data { + return components + .reduce(Data()) { partialResult, value in + var mutBuffer = partialResult + mutBuffer.append(value.data) + return mutBuffer + } + } + +} diff --git a/Sources/BitWiser/DataRepresentable/DataRepresentable.swift b/Sources/BitWiser/DataRepresentable/DataRepresentable.swift new file mode 100644 index 0000000..fd6020a --- /dev/null +++ b/Sources/BitWiser/DataRepresentable/DataRepresentable.swift @@ -0,0 +1,18 @@ +// +// DataRepresentable.swift +// +// +// Created by Andrea Finollo on 04/02/22. +// + +import Foundation + +public protocol ExpressibleByData { + init?(data: Data) +} + +public protocol DataConvertible { + var data: Data { get } +} + +public typealias DataRepresentable = ExpressibleByData & DataConvertible diff --git a/Sources/BitWiser/Extension/Extension+Data.swift b/Sources/BitWiser/Extension/Extension+Data.swift index 48b6fd3..4a8e462 100644 --- a/Sources/BitWiser/Extension/Extension+Data.swift +++ b/Sources/BitWiser/Extension/Extension+Data.swift @@ -18,6 +18,17 @@ extension Data: ByteRepresentable { } } +extension Data : DataConvertible { + + public init?(data: Data) { + self.init(data) + } + + public var data: Data { + return self + } +} + public extension Data { /// Option about how to encode the hex string representation @@ -44,3 +55,23 @@ public extension Data { }.joined(separator: padding) } } + +public extension Data { + + /// Initialize a `Data` with a `@DataConvertibleBuilder`. + /// + /// Data { + /// [Byte(0x00)] + /// Byte(0x01) + /// 0x02 + /// "\u{03}" + /// CustomObject() + /// } + /// + /// - parameter representables: A DSL closure with `DataRepresentable`s. Object passed in the closure must conform to `DataRepresentable` protocol. + /// - Note: Objects passed in the closure can have different `Data` lenght. + /// - Important: Always start from the LSB. + init(@DataConvertibleBuilder _ representables: () -> Data) { + self.init(representables()) + } +} diff --git a/Sources/BitWiser/Extension/Extension+Numeric.swift b/Sources/BitWiser/Extension/Extension+Numeric.swift index 76381ad..64ed1e5 100644 --- a/Sources/BitWiser/Extension/Extension+Numeric.swift +++ b/Sources/BitWiser/Extension/Extension+Numeric.swift @@ -7,6 +7,34 @@ import Foundation +extension DataConvertible where Self: ExpressibleByIntegerLiteral{ + public init?(data: Data) { + var value: Self = 0 + guard data.count == MemoryLayout.size(ofValue: value) else { return nil } + _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} ) + self = value + } + + public var data: Data { + return withUnsafeBytes(of: self) { Data($0) } + } +} + +extension Int8: DataConvertible { } +extension Int16: DataConvertible { } +extension Int32: DataConvertible { } +extension Int64: DataConvertible { } +extension Int: DataConvertible { } + +extension Float: DataConvertible { } +extension Double: DataConvertible { } + +extension UInt8: DataConvertible { } +extension UInt16: DataConvertible { } +extension UInt32: DataConvertible { } +extension UInt64: DataConvertible { } +extension UInt: DataConvertible { } + // MARK: - Signed Numeric extension Int16: ByteConvertible { @@ -103,3 +131,20 @@ extension Array where Element == Byte { return or } } + +public extension Array where Element == Byte { + /// Initialize a `[Byte]`Array with a `@ByteArrayBuilder`. + /// + /// Array { + /// [Byte(0x00)] + /// Byte(0x01) + /// 0x02 + /// [UInt8(0x03)] + /// } + /// + /// - parameter builder: A DSL closure with `Byte`s. + /// - Important: Always start from the LSB + init(@ByteArrayBuilder _ builder: () -> [Byte]) { + self.init(builder()) + } +} diff --git a/Sources/BitWiser/Extension/Extension+String.swift b/Sources/BitWiser/Extension/Extension+String.swift index 211d350..0010108 100644 --- a/Sources/BitWiser/Extension/Extension+String.swift +++ b/Sources/BitWiser/Extension/Extension+String.swift @@ -7,6 +7,18 @@ import Foundation +extension String: DataConvertible { + + public init?(data: Data) { + self.init(data: data, encoding: .utf8) + } + + public var data: Data { + // Note: a conversion to UTF-8 cannot fail. + return Data(self.utf8) + } +} + public extension String { /// Given a hex string it returns a Data /// - Returns: a `Data` value diff --git a/Tests/BitWiserTests/ByteTests.swift b/Tests/BitWiserTests/ByteTests.swift index fb45d8d..7462f25 100644 --- a/Tests/BitWiserTests/ByteTests.swift +++ b/Tests/BitWiserTests/ByteTests.swift @@ -16,7 +16,7 @@ class ByteTests: XCTestCase { var hexDescription = value.hexDescription XCTAssertTrue(binDescription == "00000000") XCTAssertTrue(hexDescription == "00") - + value = 0b11111111 binDescription = value.binaryDescription hexDescription = value.hexDescription diff --git a/Tests/BitWiserTests/DSLTests.swift b/Tests/BitWiserTests/DSLTests.swift new file mode 100644 index 0000000..e2c9917 --- /dev/null +++ b/Tests/BitWiserTests/DSLTests.swift @@ -0,0 +1,296 @@ +// +// DSLTests.swift +// +// +// Created by Andrea Finollo on 30/01/22. +// + +import XCTest +@testable import BitWiser + +class DSLTests: XCTestCase { + + struct CustomData: DataConvertible { + var data: Data { + withUnsafeBytes(of: UInt8(6)) { Data($0) } + } + } + + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicBytes() -> [Byte] { + Byte(0x00) + Byte(0x01) + Byte(0x02) + Byte(0x03) + UInt8(0x04) + } + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicByteArrayOfBytes() -> [Byte] { + [Byte(0x00)] + [Byte(0x01)] + [Byte(0x02)] + [Byte(0x03)] + [UInt8(0x04)] + } + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicMixedByteArrayOfBytes() -> [Byte] { + [Byte(0x00)] + Byte(0x01) + 0x02 + [UInt8(0x03)] + [UInt8](repeating: 0x04, count: 1) + } + + var byteArrayClause = true + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfClause() -> [Byte] { + [Byte(0x00)] + Byte(0x01) + 0x02 + [UInt8(0x03)] + if byteArrayClause { + [UInt8](repeating: 0x04, count: 1) + } + } + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfElseClause() -> [Byte] { + [Byte(0x00)] + Byte(0x01) + 0x02 + [UInt8(0x03)] + if byteArrayClause { + [UInt8](repeating: 0x04, count: 1) + } else { + 0x04 + 0x05 + } + } + + @ByteArrayBuilder func buildArrayOfBytesFromVaridicMixedByteArrayOfBytesLoop() -> [Byte] { + [Byte(0x00)] + Byte(0x01) + 0x02 + [UInt8(0x03)] + for value in [0x04, 0x05] { + Byte(value) + } + } + + func testArrayOfBytesFromVaridicBytes() throws { + let value = buildArrayOfBytesFromVaridicBytes() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testArrayOfBytesFromVaridicByteArrayOfBytes() throws { + let value = buildArrayOfBytesFromVaridicByteArrayOfBytes() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testArrayOfBytesFromVaridicMixedByteArrayOfBytes() throws { + let value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytes() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testArrayOfBytesFromVaridicMixedByteArrayOfBytesIfClause() throws { + byteArrayClause = true + var value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + byteArrayClause = false + value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testArrayOfBytesFromVaridicMixedByteArrayOfBytesIfElseClause() throws { + byteArrayClause = true + var value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfElseClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + byteArrayClause = false + value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytesIfElseClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + } + + func testArrayOfBytesFromVaridicMixedByteArrayOfBytesLoop() throws { + let value = buildArrayOfBytesFromVaridicMixedByteArrayOfBytesLoop() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testDataConversion() throws { + let value = UInt16(0b1100_1100_1010_1010) + let bytes = value.bytes + let dataFromValue = Data(bytes: bytes) + + // Must use UInt8 to define the length + let dslData = Data { + Byte(0b1010_1010) + Byte(0b1100_1100) + } + + let dslByteArray = Array { + 0b1010_1010 + 0b1100_1100 + } + XCTAssertEqual(dslByteArray, bytes) + XCTAssertEqual(dslData, dataFromValue) + } + + var dataClause = true + + @DataConvertibleBuilder func buildDataFromVaridicData() -> Data { + UInt8(0) + UInt8(1) + UInt8(2) + UInt8(3) + UInt8(4) + } + + @DataConvertibleBuilder func buildDataFromVaridicDataArrayOfData() -> Data { + [UInt8(0)] + [UInt8(1)] + [UInt8(2)] + [UInt8(3)] + [UInt8(4)] + } + + @DataConvertibleBuilder func buildDataFromVaridicMixedData() -> Data { + [UInt8(0)] + UInt8(1) + Int8(2) + "\u{03}" + Int16(1284) + CustomData() + } + + @DataConvertibleBuilder func buildDataFromVaridicMixedDataIfClause() -> Data { + [UInt8(0)] + UInt8(1) + Int8(2) + "\u{03}" + Int16(1284) + if dataClause { + CustomData() + } + } + + @DataConvertibleBuilder func buildDataFromVaridicMixedDataIfElseClause() -> Data { + [UInt8(0)] + UInt8(1) + Int8(2) + "\u{03}" + Int16(1284) + if dataClause { + CustomData() + } else { + UInt8(6) + UInt8(7) + } + } + + @DataConvertibleBuilder func buildDataFromVaridicMixedDataLoop() -> Data { + [UInt8(0)] + UInt8(1) + Int8(2) + "\u{03}" + Int16(1284) + for value in [UInt8(6), UInt8(7)] { + value + } + } + + func testDataFromVaridicData() throws { + let value = buildDataFromVaridicData() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testDataFromVaridicDataArrayOfData() throws { + let value = buildDataFromVaridicDataArrayOfData() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testDataFromVaridicMixedData() throws { + let value = buildDataFromVaridicMixedData() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testDataFromVaridicMixedDataIfClause() throws { + dataClause = true + + var value = buildDataFromVaridicMixedDataIfClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + dataClause = false + + value = buildDataFromVaridicMixedDataIfClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + + func testDataFromVaridicMixedDataIfElseClause() throws { + dataClause = true + + var value = buildDataFromVaridicMixedDataIfElseClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + dataClause = false + + value = buildDataFromVaridicMixedDataIfElseClause() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + + } + + func testDataFromVaridicMixedDataLoop() throws { + let value = buildDataFromVaridicMixedDataLoop() + value.enumerated().forEach { (index, value) in + let uIndex = UInt8(index) + XCTAssertEqual(uIndex, value) + } + } + +} +