Skip to content

Commit fcc304d

Browse files
authored
Add enum parsing (#5)
This PR adds the macro of generating declarative parsing for enums
1 parent 952826e commit fcc304d

34 files changed

+2822
-988
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,27 @@ concurrency:
2020
group: ${{ github.workflow }}-${{ github.ref }}
2121
cancel-in-progress: true
2222
jobs:
23+
lint:
24+
name: Lint & Format Check
25+
runs-on: macOS-26
26+
env:
27+
DEVELOPER_DIR: "/Applications/Xcode_26.0.app/Contents/Developer"
28+
steps:
29+
- uses: actions/checkout@v4
30+
- name: Swift Version
31+
run: xcrun swift --version
32+
- name: Install SwiftLint
33+
run: brew install swiftlint --quiet
34+
- name: Install swift-format
35+
run: brew install swiftformat --quiet
36+
- name: SwiftLint
37+
run: swiftlint --strict --config .swiftlint.yml .
38+
- name: Swift Format Check
39+
run: swiftformat --lint-only --config .swiftformat.conf . --strict
2340
test:
2441
name: ${{ matrix.name }}
2542
runs-on: ${{ matrix.runsOn }}
43+
needs: lint
2644
env:
2745
DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
2846
strategy:
@@ -35,19 +53,8 @@ jobs:
3553
- uses: actions/checkout@v4
3654
- name: Swift Version
3755
run: xcrun swift --version
38-
- name: Install SwiftLint
39-
run: brew install swiftlint --quiet
40-
- name: Install swift-format
41-
run: |
42-
brew install swiftformat --quiet
4356
- name: Install xcbeautify
44-
run: |
45-
brew install xcbeautify --quiet
46-
- name: SwiftLint
47-
run: swiftlint --strict --config .swiftlint.yml .
48-
- name: Swift Format Check
49-
run: |
50-
swiftformat --lint-only --config .swiftformat.conf . --strict
57+
run: brew install xcbeautify --quiet
5158
- name: Run Tests
5259
run: |
5360
set -o pipefail && \

Package.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ let package = Package(
4040
.target(
4141
name: "BinaryParseKitCommons",
4242
dependencies: [.product(name: "BinaryParsing", package: "swift-binary-parsing")],
43-
swiftSettings: [.enableExperimentalFeature("LifetimeDependence"), .strictMemorySafety()],
43+
swiftSettings: [
44+
.enableExperimentalFeature("LifetimeDependence"),
45+
.enableExperimentalFeature("Lifetimes"),
46+
.strictMemorySafety(),
47+
],
4448
),
4549
.target(name: "BinaryParseKit", dependencies: ["BinaryParseKitMacros"], swiftSettings: [
4650
.enableExperimentalFeature("LifetimeDependence"),
51+
.enableExperimentalFeature("Lifetimes"),
4752
.strictMemorySafety(),
4853
]),
4954
.executableTarget(
@@ -55,6 +60,7 @@ let package = Package(
5560
],
5661
swiftSettings: [
5762
.enableExperimentalFeature("LifetimeDependence"),
63+
.enableExperimentalFeature("Lifetimes"),
5864
.strictMemorySafety(),
5965
],
6066
),

Sources/BinaryParseKit/BinaryParseKit.swift

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// The Swift Programming Language
22
// https://docs.swift.org/swift-book
33

4-
import BinaryParseKitCommons
4+
@_exported public import BinaryParseKitCommons
55
public import BinaryParsing
66

77
// MARK: - Skip Parsing
@@ -32,7 +32,7 @@ public import BinaryParsing
3232
@attached(peer)
3333
public macro skip(byteCount: ByteCount, because: String) = #externalMacro(
3434
module: "BinaryParseKitMacros",
35-
type: "SkipParsingMacro",
35+
type: "EmptyPeerMacro",
3636
)
3737

3838
// MARK: - Field Parsing
@@ -53,7 +53,7 @@ public macro skip(byteCount: ByteCount, because: String) = #externalMacro(
5353
/// }
5454
/// ```
5555
@attached(peer)
56-
public macro parse() = #externalMacro(module: "BinaryParseKitMacros", type: "ByteParsingMacro")
56+
public macro parse() = #externalMacro(module: "BinaryParseKitMacros", type: "EmptyPeerMacro")
5757

5858
/// Parses a field with a specific endianness.
5959
///
@@ -76,7 +76,7 @@ public macro parse() = #externalMacro(module: "BinaryParseKitMacros", type: "Byt
7676
/// }
7777
/// ```
7878
@attached(peer)
79-
public macro parse(endianness: Endianness) = #externalMacro(module: "BinaryParseKitMacros", type: "ByteParsingMacro")
79+
public macro parse(endianness: Endianness) = #externalMacro(module: "BinaryParseKitMacros", type: "EmptyPeerMacro")
8080

8181
/// Parses a field with a specific byte count.
8282
///
@@ -101,7 +101,7 @@ public macro parse(endianness: Endianness) = #externalMacro(module: "BinaryParse
101101
@attached(peer)
102102
public macro parse(byteCount: ByteCount) = #externalMacro(
103103
module: "BinaryParseKitMacros",
104-
type: "ByteParsingMacro",
104+
type: "EmptyPeerMacro",
105105
)
106106

107107
/// Parses a field with a byte count determined by another field's value.
@@ -128,7 +128,7 @@ public macro parse(byteCount: ByteCount) = #externalMacro(
128128
@attached(peer)
129129
public macro parse<R, V: BinaryInteger>(byteCountOf: KeyPath<R, V>) = #externalMacro(
130130
module: "BinaryParseKitMacros",
131-
type: "ByteParsingMacro",
131+
type: "EmptyPeerMacro",
132132
)
133133

134134
/// Parses a field with both specific byte count and endianness.
@@ -153,7 +153,7 @@ public macro parse<R, V: BinaryInteger>(byteCountOf: KeyPath<R, V>) = #externalM
153153
@attached(peer)
154154
public macro parse(byteCount: ByteCount, endianness: Endianness) = #externalMacro(
155155
module: "BinaryParseKitMacros",
156-
type: "ByteParsingMacro",
156+
type: "EmptyPeerMacro",
157157
)
158158

159159
/// Parses a field with byte count from another field and specific endianness.
@@ -182,7 +182,7 @@ public macro parse(byteCount: ByteCount, endianness: Endianness) = #externalMacr
182182
@attached(peer)
183183
public macro parse<R, V: BinaryInteger>(byteCountOf: KeyPath<R, V>, endianness: Endianness) = #externalMacro(
184184
module: "BinaryParseKitMacros",
185-
type: "ByteParsingMacro",
185+
type: "EmptyPeerMacro",
186186
)
187187

188188
/// Parses all remaining bytes in the data stream.
@@ -208,7 +208,7 @@ public macro parse<R, V: BinaryInteger>(byteCountOf: KeyPath<R, V>, endianness:
208208
@attached(peer)
209209
public macro parseRest() = #externalMacro(
210210
module: "BinaryParseKitMacros",
211-
type: "ByteParsingMacro",
211+
type: "EmptyPeerMacro",
212212
)
213213

214214
/// Parses all remaining bytes with a specific endianness.
@@ -235,7 +235,7 @@ public macro parseRest() = #externalMacro(
235235
@attached(peer)
236236
public macro parseRest(endianness: Endianness) = #externalMacro(
237237
module: "BinaryParseKitMacros",
238-
type: "ByteParsingMacro",
238+
type: "EmptyPeerMacro",
239239
)
240240

241241
// MARK: - Struct Parsing
@@ -278,3 +278,55 @@ public macro ParseStruct() = #externalMacro(
278278
module: "BinaryParseKitMacros",
279279
type: "ConstructStructParseMacro",
280280
)
281+
282+
// MARK: - Parse Enum
283+
284+
@attached(extension, conformances: BinaryParseKit.Parsable, names: arbitrary)
285+
public macro ParseEnum() = #externalMacro(
286+
module: "BinaryParseKitMacros",
287+
type: "ConstructEnumParseMacro",
288+
)
289+
290+
// MARK: - Enum Case Parsing
291+
292+
@attached(peer)
293+
public macro match() = #externalMacro(
294+
module: "BinaryParseKitMacros",
295+
type: "EmptyPeerMacro",
296+
)
297+
298+
@attached(peer)
299+
public macro match(byte: UInt8) = #externalMacro(
300+
module: "BinaryParseKitMacros",
301+
type: "EmptyPeerMacro",
302+
)
303+
304+
@attached(peer)
305+
public macro match(bytes: [UInt8]) = #externalMacro(
306+
module: "BinaryParseKitMacros",
307+
type: "EmptyPeerMacro",
308+
)
309+
310+
@attached(peer)
311+
public macro matchAndTake() = #externalMacro(
312+
module: "BinaryParseKitMacros",
313+
type: "EmptyPeerMacro",
314+
)
315+
316+
@attached(peer)
317+
public macro matchAndTake(byte: UInt8) = #externalMacro(
318+
module: "BinaryParseKitMacros",
319+
type: "EmptyPeerMacro",
320+
)
321+
322+
@attached(peer)
323+
public macro matchAndTake(bytes: [UInt8]) = #externalMacro(
324+
module: "BinaryParseKitMacros",
325+
type: "EmptyPeerMacro",
326+
)
327+
328+
@attached(peer)
329+
public macro matchDefault() = #externalMacro(
330+
module: "BinaryParseKitMacros",
331+
type: "EmptyPeerMacro",
332+
)

Sources/BinaryParseKit/BinaryParserKitError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
// Created by Larry Zeng on 7/17/25.
66
//
77

8-
enum BinaryParserKitError: Error {
8+
public enum BinaryParserKitError: Error {
99
case failedToParse(String)
1010
}

Sources/BinaryParseKit/BuiltInExtensions.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//
55
// Created by Larry Zeng on 7/16/25.
66
//
7-
import BinaryParseKitCommons
87
import BinaryParsing
98

109
extension UInt8: @retroactive ExpressibleByParsing {}

Sources/BinaryParseKit/CustomExtensions.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//
55
// Created by Larry Zeng on 7/16/25.
66
//
7-
import BinaryParseKitCommons
87
import BinaryParsing
98

109
// MARK: - Floating Point Conformances
@@ -89,6 +88,14 @@ extension Int: EndianParsable {
8988
}
9089
}
9190

91+
// MARK: - MatchableRawValue
92+
93+
public extension MatchableRawRepresentable where Self.RawValue == UInt8 {
94+
func bytesToMatch() -> [UInt8] {
95+
[rawValue]
96+
}
97+
}
98+
9299
// MARK: - RawRepresentable
93100

94101
//
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// EnumParseUtils.swift
3+
// BinaryParseKit
4+
//
5+
// Created by Larry Zeng on 11/7/25.
6+
//
7+
import BinaryParsing
8+
9+
/// Matches the given bytes in the input parser span.
10+
/// - Warning: This function is used to `@parseEnum` macro and should not be used directly.
11+
@inlinable
12+
public func __match(_ bytes: borrowing [UInt8], in input: inout BinaryParsing.ParserSpan) -> Bool {
13+
if bytes.isEmpty { return true }
14+
15+
do {
16+
try input._checkCount(minimum: bytes.count)
17+
} catch {
18+
return false
19+
}
20+
21+
let toMatch = unsafe input.bytes.extracting(first: bytes.count).withUnsafeBytes(Array.init)
22+
return toMatch == bytes
23+
}
24+
25+
/// - Warning: This function is used to `@parse` macro and should not be used directly.
26+
@inlinable
27+
public func __assertParsable(_: (some Parsable).Type) {}
28+
29+
/// - Warning: This function is used to `@parse` macro and should not be used directly.
30+
@inlinable
31+
public func __assertSizedParsable(_: (some SizedParsable).Type) {}
32+
33+
/// - Warning: This function is used to `@parse` macro and should not be used directly.
34+
@inlinable
35+
public func __assertEndianParsable(_: (some EndianParsable).Type) {}
36+
37+
/// - Warning: This function is used to `@parse` macro and should not be used directly.
38+
@inlinable
39+
public func __assertEndianSizedParsable(_: (some EndianSizedParsable).Type) {}

Sources/BinaryParseKit/ParsableProtocols.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public protocol EndianParsable {
2626
/// - input: A mutable parser span containing the binary data to parse
2727
/// - endianness: The byte order to use when parsing multi-byte values
2828
/// - Throws: `ThrownParsingError` if parsing fails
29-
@lifetime(&input)
29+
@_lifetime(&input)
3030
init(parsing input: inout ParserSpan, endianness: Endianness) throws(ThrownParsingError)
3131
}
3232

@@ -71,7 +71,7 @@ public protocol EndianSizedParsable {
7171
/// - endianness: The byte order to use when parsing multi-byte values
7272
/// - byteCount: The number of bytes to read from the input
7373
/// - Throws: `ThrownParsingError` if parsing fails
74-
@lifetime(&input)
74+
@_lifetime(&input)
7575
init(parsing input: inout ParserSpan, endianness: Endianness, byteCount: Int) throws(ThrownParsingError)
7676
}
7777

@@ -116,7 +116,7 @@ public protocol SizedParsable {
116116
/// - input: A mutable parser span containing the binary data to parse
117117
/// - byteCount: The number of bytes to read from the input
118118
/// - Throws: `ThrownParsingError` if parsing fails
119-
@lifetime(&input)
119+
@_lifetime(&input)
120120
init(parsing input: inout ParserSpan, byteCount: Int) throws(ThrownParsingError)
121121
}
122122

@@ -142,3 +142,7 @@ public extension SizedParsable {
142142
self = result
143143
}
144144
}
145+
146+
public protocol MatchableRawRepresentable: RawRepresentable {
147+
func bytesToMatch() -> [UInt8]
148+
}

Sources/BinaryParseKitClient/main.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import BinaryParseKit
22
import BinaryParsing
33

44
extension [UInt8]: SizedParsable {
5-
@lifetime(&input)
5+
@_lifetime(&input)
66
public init(parsing input: inout ParserSpan, byteCount: Int) throws(ThrownParsingError) {
77
var subSpan = try input.sliceSpan(byteCount: byteCount)
88
self.init(parsingRemainingBytes: &subSpan)
@@ -44,3 +44,17 @@ print("Version: \(header.version)") // 1
4444
print("File Size: \(header.fileSize)") // 5
4545
print("Content: \(String(bytes: header.content, encoding: .utf8)!)") // "Hello"
4646
print("Footer: \(header.footer)")
47+
48+
@ParseEnum
49+
enum Channel {
50+
@match(byte: 0x00)
51+
case internet
52+
@match(byte: 0x01)
53+
case satellite
54+
@match(byte: 0x02)
55+
case pigeon
56+
}
57+
58+
let channel = try Channel(parsing: [0x02])
59+
60+
print("Channel: \(channel)") // pigeon

Sources/BinaryParseKitMacros/BinaryParseKitMacro.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import SwiftSyntaxMacros
44
@main
55
struct BinaryParseKitPlugin: CompilerPlugin {
66
let providingMacros: [Macro.Type] = [
7-
ByteParsingMacro.self,
8-
SkipParsingMacro.self,
7+
EmptyPeerMacro.self,
98
ConstructStructParseMacro.self,
9+
ConstructEnumParseMacro.self,
1010
]
1111
}

0 commit comments

Comments
 (0)