Skip to content

Commit a6079e6

Browse files
committed
Add error tests
1 parent f597b8c commit a6079e6

File tree

10 files changed

+369
-35
lines changed

10 files changed

+369
-35
lines changed

Sources/BinaryParseKit/BinaryParseKit.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,27 @@ public macro match(bytes: [UInt8]) = #externalMacro(
306306
module: "BinaryParseKitMacros",
307307
type: "EmptyPeerMacro",
308308
)
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/EnumParseUtils.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@
66
//
77
import BinaryParsing
88

9+
/// Matches the given bytes in the input parser span.
910
/// - Warning: This function is used to `@parseEnum` macro and should not be used directly.
1011
@inlinable
11-
public func __match(_ bytes: [UInt8], in input: inout BinaryParsing.ParserSpan) -> Bool {
12-
// FIXME: right now, it's all `matchAndTake` semantic, implement a peak on ParserSpan??
13-
(try? input.atomically { span in
14-
let toMatchSpan = try span.sliceSpan(byteCount: bytes.count)
15-
let toMatch: [UInt8] = unsafe toMatchSpan.withUnsafeBytes(Array.init)
16-
if toMatch == bytes {
17-
return true
18-
} else {
19-
throw BinaryParserKitError.failedToParse("Expected bytes \(bytes), found \(toMatch)")
20-
}
21-
}) ?? false
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
2223
}
2324

2425
/// - Warning: This function is used to `@parse` macro and should not be used directly.

Sources/BinaryParseKitMacros/Macros/ParseEnum/ConsructParseEnumMacro.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public struct ConstructEnumParseMacro: ExtensionMacro {
4848
try IfExprSyntax(
4949
"if \(raw: Constants.UtilityFunctions.matchBytes)(\(toBeMatched), in: &span)",
5050
) {
51+
if caseParseInfo.matchAction.matchPolicy == .matchAndTake {
52+
"try input.seek(toRelativeOffset: \(toBeMatched).count)"
53+
}
54+
5155
var arguments: OrderedDictionary<TokenSyntax, EnumCaseParameterParseInfo> = [:]
5256

5357
for parseAction in caseParseInfo.parseActions {

Sources/BinaryParseKitMacros/Macros/ParseEnum/ParseEnumCase.swift

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
1717
private var caseParseInfo: [EnumCaseParseInfo] = []
1818
private(set) var parsedInfo: EnumParseInfo?
1919

20+
private var hasMatchDefault = false
21+
2022
private var errors: [Diagnostic] = []
2123

2224
init(context: C) {
@@ -98,26 +100,42 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
98100
return
99101
}
100102

103+
if matchAction.matchPolicy == .matchDefault {
104+
if hasMatchDefault {
105+
errors.append(
106+
.init(
107+
node: node,
108+
message: ParseEnumMacroError.onlyOneMatchDefaultAllowed,
109+
),
110+
)
111+
return
112+
}
113+
114+
hasMatchDefault = true
115+
} else {
116+
if hasMatchDefault {
117+
errors.append(
118+
.init(
119+
node: node,
120+
message: ParseEnumMacroError.defaultCaseMustBeLast,
121+
),
122+
)
123+
}
124+
}
125+
101126
let caseParameters = currentCaseElement.parameterClause?.parameters ?? []
102127

103128
let enumParseActions: [EnumParseAction]
104129
do {
105-
enumParseActions = try currentParseMacroVisitor.parseActions.convertToEnumParseAction(with: caseParameters)
130+
enumParseActions = try currentParseMacroVisitor.parseActions.convertToEnumParseAction(
131+
with: caseParameters,
132+
errors: &errors,
133+
)
106134
} catch {
107135
errors.append(.init(node: caseParameters, message: error))
108136
return
109137
}
110138

111-
if caseParseInfo.last?.matchAction.matchPolicy == .matchDefault {
112-
errors.append(
113-
.init(
114-
node: node,
115-
message: ParseEnumMacroError.defaultCaseMustBeLast,
116-
),
117-
)
118-
return
119-
}
120-
121139
caseParseInfo.append(
122140
.init(
123141
matchAction: matchAction,
@@ -168,6 +186,7 @@ private extension EnumCaseDeclSyntax {
168186
private extension [StructParseAction] {
169187
func convertToEnumParseAction(
170188
with parameters: EnumCaseParameterListSyntax,
189+
errors: inout [Diagnostic],
171190
) throws(ParseEnumMacroError) -> [EnumParseAction] {
172191
var result: [EnumParseAction] = []
173192
var parseActionIndex = 0
@@ -183,7 +202,13 @@ private extension [StructParseAction] {
183202
}
184203

185204
guard parseActionIndex < count else {
186-
throw ParseEnumMacroError.parameterParseNumberNotMatch
205+
errors.append(
206+
.init(
207+
node: parameter,
208+
message: ParseEnumMacroError.caseArgumentsMoreThanMacros,
209+
),
210+
)
211+
break
187212
}
188213

189214
guard case let .parse(parseInfo) = self[parseActionIndex] else {
@@ -201,6 +226,21 @@ private extension [StructParseAction] {
201226
)
202227
}
203228

229+
if parseActionIndex != count {
230+
while parseActionIndex < count {
231+
let parseAction = self[parseActionIndex]
232+
233+
errors.append(
234+
.init(
235+
node: parseAction.source,
236+
message: ParseEnumMacroError.macrosMoreThanCaseArguments,
237+
),
238+
)
239+
240+
parseActionIndex += 1
241+
}
242+
}
243+
204244
return result
205245
}
206246
}

Sources/BinaryParseKitMacros/Macros/ParseEnum/ParseEnumMacroError.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,28 @@ import SwiftDiagnostics
99
enum ParseEnumMacroError: Error, DiagnosticMessage {
1010
case onlyEnumsAreSupported
1111
case onlyOneEnumDeclarationForEachCase
12-
case parameterParseNumberNotMatch
12+
case caseArgumentsMoreThanMacros
13+
case macrosMoreThanCaseArguments
1314
case matchMustProceedParse
1415
case missingCaseMatchMacro
1516
case defaultCaseMustBeLast
17+
case onlyOneMatchDefaultAllowed
18+
case matchDefaultShouldBeLast
1619
case unexpectedError(description: String)
1720

1821
var message: String {
1922
switch self {
2023
case .onlyEnumsAreSupported: "Only enums are supported by this macro."
2124
case .onlyOneEnumDeclarationForEachCase: "Only one enum declaration is allowed for each case."
22-
case .parameterParseNumberNotMatch:
23-
"The number of the parse macros does not match the number of cases in the enum."
24-
case .matchMustProceedParse: "The `match` macro must proceed all `parse` macro."
25+
case .caseArgumentsMoreThanMacros:
26+
"The associated values in the enum case exceed the number of parse/skip macros."
27+
case .macrosMoreThanCaseArguments:
28+
"There are more parse/skip macros than the number of cases in the enum."
29+
case .matchMustProceedParse: "The `match` macro must proceed all `parse` and `skip` macro."
2530
case .missingCaseMatchMacro: "A `case` declaration must has a `match` macro."
2631
case .defaultCaseMustBeLast: "The `matchDefault` case must be the last case in the enum."
32+
case .onlyOneMatchDefaultAllowed: "Only one `matchDefault` case is allowed in a enum."
33+
case .matchDefaultShouldBeLast: "The `matchDefault` case should be the last case in the enum."
2734
case let .unexpectedError(description: description):
2835
"Unexpected error: \(description)"
2936
}

Sources/BinaryParseKitMacros/Macros/ParseSkip/ParseSkipInfo.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import SwiftSyntax
1111
struct ParseSkipInfo {
1212
let byteCount: ByteCount
1313
let reason: ExprSyntax
14+
let source: Syntax
1415

15-
init(byteCount: ByteCount, reason: ExprSyntax) {
16+
init(byteCount: ByteCount, reason: ExprSyntax, source: Syntax) {
1617
self.byteCount = byteCount
1718
self.reason = reason
19+
self.source = source
1820
}
1921

2022
init(from attribute: AttributeSyntax) throws(ParseStructMacroError) {
@@ -46,6 +48,6 @@ struct ParseSkipInfo {
4648
)
4749
}
4850

49-
self.init(byteCount: byteCountValue, reason: reasonArgument.expression)
51+
self.init(byteCount: byteCountValue, reason: reasonArgument.expression, source: Syntax(attribute))
5052
}
5153
}

Sources/BinaryParseKitMacros/Macros/ParseStruct/StructFieldParseInfo.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import SwiftSyntax
1111
import SwiftSyntaxMacros
1212

1313
private class ParseMacroArgVisitor<C: MacroExpansionContext>: SyntaxVisitor {
14+
var source: Syntax?
1415
var byteCountOfArgument: LabeledExprSyntax?
1516
var byteCountArgument: LabeledExprSyntax?
1617
var endiannessArgument: LabeledExprSyntax?
@@ -23,6 +24,11 @@ private class ParseMacroArgVisitor<C: MacroExpansionContext>: SyntaxVisitor {
2324
super.init(viewMode: .sourceAccurate)
2425
}
2526

27+
override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind {
28+
source = Syntax(node)
29+
return .visitChildren
30+
}
31+
2632
override func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind {
2733
switch node.label?.text {
2834
case "byteCountOf":
@@ -38,6 +44,10 @@ private class ParseMacroArgVisitor<C: MacroExpansionContext>: SyntaxVisitor {
3844
}
3945

4046
func toStructFieldParseInfo() throws(ParseStructMacroError) -> StructFieldParseInfo {
47+
guard let source else {
48+
throw ParseStructMacroError.fatalError(message: "Source syntax is missing.")
49+
}
50+
4151
if byteCountOfArgument != nil, byteCountArgument != nil {
4252
throw ParseStructMacroError
4353
.fatalError(message: "Both `byteCountOf` and `byteCount` cannot be specified at the same time.")
@@ -68,7 +78,11 @@ private class ParseMacroArgVisitor<C: MacroExpansionContext>: SyntaxVisitor {
6878
byteCount = .unspecified
6979
}
7080

71-
return .init(byteCount: byteCount, endianness: endiannessArgument?.expression)
81+
return .init(
82+
byteCount: byteCount,
83+
endianness: endiannessArgument?.expression,
84+
source: source,
85+
)
7286
}
7387

7488
func validate() throws(ParseStructMacroError) {
@@ -98,18 +112,20 @@ struct StructFieldParseInfo {
98112
let byteCount: Count
99113
/// The endianness of this field, if specified
100114
let endianness: ExprSyntax?
115+
let source: Syntax
101116

102-
init(byteCount: Count, endianness: ExprSyntax? = nil) {
117+
init(byteCount: Count, endianness: ExprSyntax? = nil, source: Syntax) {
103118
self.byteCount = byteCount
104119
self.endianness = endianness
120+
self.source = source
105121
}
106122

107123
init(
108124
fromParse attribute: borrowing AttributeSyntax,
109125
in context: some MacroExpansionContext,
110126
) throws(ParseStructMacroError) {
111127
if attribute.arguments == nil {
112-
self.init(byteCount: .unspecified, endianness: nil)
128+
self.init(byteCount: .unspecified, endianness: nil, source: Syntax(attribute))
113129
return
114130
}
115131

@@ -123,6 +139,6 @@ struct StructFieldParseInfo {
123139
init(fromParseRest attribute: AttributeSyntax) {
124140
let endiannessArgument: LabeledExprSyntax? = attribute.arguments?.as(LabeledExprListSyntax.self)?.first
125141

126-
self.init(byteCount: .variable, endianness: endiannessArgument?.expression)
142+
self.init(byteCount: .variable, endianness: endiannessArgument?.expression, source: Syntax(attribute))
127143
}
128144
}

Sources/BinaryParseKitMacros/Macros/ParseStruct/StructParseAction.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,18 @@
44
//
55
// Created by Larry Zeng on 7/17/25.
66
//
7+
import SwiftSyntax
78

89
enum StructParseAction {
910
case parse(StructFieldParseInfo)
1011
case skip(ParseSkipInfo)
12+
13+
var source: Syntax {
14+
switch self {
15+
case let .parse(parse):
16+
parse.source
17+
case let .skip(skip):
18+
skip.source
19+
}
20+
}
1121
}

0 commit comments

Comments
 (0)