Skip to content

Commit

Permalink
Merge pull request #172 from PureSwift/feature/macros
Browse files Browse the repository at this point in the history
Add macros
  • Loading branch information
colemancda authored Jan 10, 2025
2 parents 8664a06 + 4868c42 commit 69abc31
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 50 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/swift-arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ jobs:
- name: Swift Version
run: swift --version
- name: Build Bluetooth
run: swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target Bluetooth
run: SWIFTPM_ENABLE_MACROS=0 swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target Bluetooth
- name: Build BluetoothGAP
run: swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target BluetoothGAP
run: SWIFTPM_ENABLE_MACROS=0 swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target BluetoothGAP
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ playground.xcworkspace
#
Packages/
Package.pins
Package.resolved
.build/
Bluetooth.xcodeproj/*
.swiftpm
Expand Down
59 changes: 49 additions & 10 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
// swift-tools-version:6.0
import PackageDescription
import CompilerPluginSupport
import class Foundation.ProcessInfo

// get environment variables
let environment = ProcessInfo.processInfo.environment
let dynamicLibrary = environment["SWIFT_BUILD_DYNAMIC_LIBRARY"] != nil
let generateCode = environment["SWIFTPM_ENABLE_PLUGINS"] != nil
let buildDocs = environment["BUILDING_FOR_DOCUMENTATION_GENERATION"] != nil
let enableMacros = environment["SWIFTPM_ENABLE_MACROS"] != "0" // enabled by default

// force building as dynamic library
let libraryType: PackageDescription.Product.Library.LibraryType? = dynamicLibrary ? .dynamic : nil

var package = Package(
name: "Bluetooth",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.watchOS(.v6),
.tvOS(.v13),
],
products: [
.library(
name: "Bluetooth",
Expand All @@ -37,30 +45,26 @@ var package = Package(
],
targets: [
.target(
name: "Bluetooth",
swiftSettings: [.swiftLanguageMode(.v6)]
name: "Bluetooth"
),
.target(
name: "BluetoothGAP",
dependencies: [
"Bluetooth"
],
swiftSettings: [.swiftLanguageMode(.v6)]
]
),
.target(
name: "BluetoothGATT",
dependencies: [
"Bluetooth",
],
swiftSettings: [.swiftLanguageMode(.v6)]
]
),
.target(
name: "BluetoothHCI",
dependencies: [
"Bluetooth",
"BluetoothGAP"
],
swiftSettings: [.swiftLanguageMode(.v6)]
]
),
.testTarget(
name: "BluetoothTests",
Expand All @@ -84,12 +88,17 @@ var package = Package(
]
)

// SwiftPM command plugins are only supported by Swift version 5.6 and later.
// SwiftPM plugins

if buildDocs {
package.dependencies += [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(
url: "https://github.com/apple/swift-docc-plugin",
from: "1.0.0"
)
]
}

if generateCode {
for (index, _) in package.targets.enumerated() {
package.targets[index].swiftSettings = [
Expand All @@ -116,3 +125,33 @@ if generateCode {
"GenerateBluetoothDefinitions"
]
}

if enableMacros {
package.targets[0].swiftSettings = [
.define("SWIFTPM_ENABLE_MACROS")
]
package.dependencies += [
.package(
url: "https://github.com/swiftlang/swift-syntax.git",
from: "600.0.1"
)
]
package.targets += [
.macro(
name: "BluetoothMacros",
dependencies: [
.product(
name: "SwiftSyntaxMacros",
package: "swift-syntax"
),
.product(
name: "SwiftCompilerPlugin",
package: "swift-syntax"
)
]
)
]
package.targets[0].dependencies += [
"BluetoothMacros"
]
}
7 changes: 7 additions & 0 deletions Sources/Bluetooth/Address.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,10 @@ extension BluetoothAddress: DataConvertible {
#if !hasFeature(Embedded)
extension BluetoothAddress: Codable { }
#endif

// MARK: - Macro

#if !hasFeature(Embedded) && SWIFTPM_ENABLE_MACROS
@freestanding(expression)
public macro BluetoothAddress(_ string: String) -> BluetoothAddress = #externalMacro(module: "BluetoothMacros", type: "BluetoothAddressMacro")
#endif
7 changes: 7 additions & 0 deletions Sources/Bluetooth/BluetoothUUID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,10 @@ public extension CBUUID {
}

#endif

// MARK: - Macro

#if !hasFeature(Embedded) && SWIFTPM_ENABLE_MACROS
@freestanding(expression)
public macro BluetoothUUID(_ string: String) -> BluetoothUUID = #externalMacro(module: "BluetoothMacros", type: "BluetoothUUIDMacro")
#endif
77 changes: 77 additions & 0 deletions Sources/BluetoothMacros/BluetoothAddress.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// Address.swift
// Bluetooth
//
// Created by Alsey Coleman Miller on 1/9/25.
//

import Foundation
import SwiftSyntaxMacros
import SwiftSyntax

struct BluetoothAddressMacro: ExpressionMacro {

static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws(Error) -> ExprSyntax {

guard let argument = node.arguments.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first else {
throw .requiresStaticStringLiteral
}

guard let _ = Self.parse(literalSegment.content.text) else {
throw .invalidString("\(argument)")
}

return "BluetoothAddress(rawValue: \(argument))!"
}

/// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`).
static func parse<S: StringProtocol>(_ rawValue: S) -> ByteValue? {

// verify string length
let characters = rawValue.utf8
guard characters.count == 17,
let separator = ":".utf8.first
else { return nil }

var bytes: ByteValue = (0, 0, 0, 0, 0, 0)

let components = characters.split(whereSeparator: { $0 == separator })

guard components.count == 6
else { return nil }

for (index, subsequence) in components.enumerated() {

guard subsequence.count == 2,
let byte = UInt8(hexadecimal: subsequence)
else { return nil }

withUnsafeMutablePointer(to: &bytes) {
$0.withMemoryRebound(to: UInt8.self, capacity: 6) {
$0.advanced(by: index).pointee = byte
}
}
}

return bytes
}
}

// MARK: - Supporting Types

extension BluetoothAddressMacro {

/// Raw Bluetooth Address 6 byte (48 bit) value.
typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)

enum Error: Swift.Error {
case requiresStaticStringLiteral
case invalidString(String)
}
}
61 changes: 61 additions & 0 deletions Sources/BluetoothMacros/BluetoothUUID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// BluetoothUUID.swift
// Bluetooth
//
// Created by Alsey Coleman Miller on 1/9/25.
//

import Foundation
import SwiftSyntaxMacros
import SwiftSyntax

struct BluetoothUUIDMacro: ExpressionMacro {

static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws(Error) -> ExprSyntax {

guard let argument = node.arguments.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first else {
throw .requiresStaticStringLiteral
}

guard validate(literalSegment.content.text) else {
throw .invalidString("\(argument)")
}

return "BluetoothUUID(rawValue: \(argument))!"
}

static func validate(_ rawValue: String) -> Bool {
switch rawValue.utf8.count {
case 4:
guard let _ = UInt16(hexadecimal: rawValue)
else { return false }
return true
case 8:
guard let _ = UInt32(hexadecimal: rawValue)
else { return false }
return true
case 36:
guard let _ = UUID(uuidString: rawValue)
else { return false }
return true
default:
return false
}
}
}

// MARK: - Supporting Types

extension BluetoothUUIDMacro {

enum Error: Swift.Error {
case requiresStaticStringLiteral
case invalidString(String)
}
}
Loading

0 comments on commit 69abc31

Please sign in to comment.