Skip to content

Commit 787c870

Browse files
committed
Add error tests
1 parent f597b8c commit 787c870

File tree

13 files changed

+728
-137
lines changed

13 files changed

+728
-137
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: 89 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
1313

1414
private var workEnum: EnumDeclSyntax?
1515
private var currentParseMacroVisitor: StructFieldVisitor<C>?
16-
private var currentCaseElement: EnumCaseElementSyntax?
16+
private var currentCaseElements: EnumCaseElementListSyntax?
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) {
@@ -33,30 +35,16 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
3335
return .visitChildren
3436
}
3537

36-
override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind {
37-
// has multiple declaration in the same `case`, reject
38-
guard node.elements.count <= 1 else {
39-
errors.append(.init(node: node, message: ParseEnumMacroError.onlyOneEnumDeclarationForEachCase))
40-
return .skipChildren
41-
}
42-
43-
return .visitChildren
44-
}
45-
4638
override func visit(_ node: AttributeListSyntax) -> SyntaxVisitorContinueKind {
4739
currentParseMacroVisitor = StructFieldVisitor(context: context)
4840
currentParseMacroVisitor?.walk(node)
49-
do {
50-
try currentParseMacroVisitor?.validate()
51-
} catch {
52-
errors.append(.init(node: node, message: error))
53-
}
41+
currentParseMacroVisitor?.validate(errors: &errors)
5442

5543
return .skipChildren
5644
}
5745

58-
override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
59-
currentCaseElement = node
46+
override func visit(_ node: EnumCaseElementListSyntax) -> SyntaxVisitorContinueKind {
47+
currentCaseElements = node
6048
return .skipChildren
6149
}
6250

@@ -71,7 +59,7 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
7159
guard node.belongsTo(workEnum) else {
7260
return
7361
}
74-
guard let currentCaseElement else {
62+
guard let currentCaseElements, !currentCaseElements.isEmpty else {
7563
errors.append(.init(
7664
node: node,
7765
message: ParseEnumMacroError.unexpectedError(description: "No enum case declaration"),
@@ -98,33 +86,43 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
9886
return
9987
}
10088

101-
let caseParameters = currentCaseElement.parameterClause?.parameters ?? []
89+
if matchAction.matchPolicy == .matchDefault {
90+
if hasMatchDefault {
91+
errors.append(
92+
.init(
93+
node: node,
94+
message: ParseEnumMacroError.onlyOneMatchDefaultAllowed,
95+
),
96+
)
97+
return
98+
}
10299

103-
let enumParseActions: [EnumParseAction]
104-
do {
105-
enumParseActions = try currentParseMacroVisitor.parseActions.convertToEnumParseAction(with: caseParameters)
106-
} catch {
107-
errors.append(.init(node: caseParameters, message: error))
108-
return
100+
hasMatchDefault = true
101+
} else {
102+
if hasMatchDefault {
103+
errors.append(
104+
.init(
105+
node: node,
106+
message: ParseEnumMacroError.defaultCaseMustBeLast,
107+
),
108+
)
109+
}
109110
}
110111

111-
if caseParseInfo.last?.matchAction.matchPolicy == .matchDefault {
112-
errors.append(
112+
for currentCaseElement in currentCaseElements {
113+
let enumParseActions = currentParseMacroVisitor.parseActions.convertToEnumParseAction(
114+
with: currentCaseElement,
115+
errors: &errors,
116+
)
117+
118+
caseParseInfo.append(
113119
.init(
114-
node: node,
115-
message: ParseEnumMacroError.defaultCaseMustBeLast,
120+
matchAction: matchAction,
121+
parseActions: enumParseActions,
122+
caseElementName: currentCaseElement.name,
116123
),
117124
)
118-
return
119125
}
120-
121-
caseParseInfo.append(
122-
.init(
123-
matchAction: matchAction,
124-
parseActions: enumParseActions,
125-
caseElementName: currentCaseElement.name,
126-
),
127-
)
128126
}
129127

130128
override func visitPost(_ node: EnumDeclSyntax) {
@@ -139,11 +137,11 @@ class ParseEnumCase<C: MacroExpansionContext>: SyntaxVisitor {
139137
}
140138

141139
func validate() throws {
142-
for error in errors {
143-
context.diagnose(error)
144-
}
145-
146140
if !errors.isEmpty {
141+
for error in errors {
142+
context.diagnose(error)
143+
}
144+
147145
throw ParseEnumMacroError.unexpectedError(description: "Enum macro parsing encountered errors")
148146
}
149147
}
@@ -167,8 +165,11 @@ private extension EnumCaseDeclSyntax {
167165

168166
private extension [StructParseAction] {
169167
func convertToEnumParseAction(
170-
with parameters: EnumCaseParameterListSyntax,
171-
) throws(ParseEnumMacroError) -> [EnumParseAction] {
168+
with enumCase: EnumCaseElementSyntax,
169+
errors: inout [Diagnostic],
170+
) -> [EnumParseAction] {
171+
let arguments = enumCase.parameterClause?.parameters ?? []
172+
172173
var result: [EnumParseAction] = []
173174
var parseActionIndex = 0
174175

@@ -177,30 +178,68 @@ private extension [StructParseAction] {
177178
parseActionIndex += 1
178179
}
179180

180-
for parameter in parameters {
181+
for argument in arguments {
181182
while parseActionIndex < count, case let .skip(skipInfo) = self[parseActionIndex] {
182183
addAction(.skip(skipInfo))
183184
}
184185

185186
guard parseActionIndex < count else {
186-
throw ParseEnumMacroError.parameterParseNumberNotMatch
187+
errors.append(
188+
.init(
189+
node: argument,
190+
message: ParseEnumMacroError.caseArgumentsMoreThanMacros,
191+
),
192+
)
193+
break
187194
}
188195

189196
guard case let .parse(parseInfo) = self[parseActionIndex] else {
190-
throw ParseEnumMacroError.unexpectedError(description: "countered skip action")
197+
fatalError("countered skip action")
191198
}
192199

193200
addAction(
194201
.parse(
195202
.init(
196203
parseInfo: parseInfo,
197-
firstName: parameter.firstName,
198-
type: parameter.type,
204+
firstName: argument.firstName,
205+
type: argument.type,
199206
),
200207
),
201208
)
202209
}
203210

211+
if parseActionIndex != count {
212+
while parseActionIndex < count {
213+
let parseAction = self[parseActionIndex]
214+
215+
errors.append(
216+
.init(
217+
node: parseAction.source,
218+
message: ParseEnumMacroError.macrosMoreThanCaseArguments,
219+
notes: [.init(
220+
node: Syntax(enumCase),
221+
message: MacrosMoreThanCaseArgumentsNote(enumCase: enumCase.description),
222+
)],
223+
),
224+
)
225+
226+
parseActionIndex += 1
227+
}
228+
}
229+
204230
return result
205231
}
206232
}
233+
234+
struct MacrosMoreThanCaseArgumentsNote: NoteMessage {
235+
let enumCase: String
236+
237+
var message: String {
238+
"The enum case `\(enumCase)` has less associated values than parse/skip macros."
239+
}
240+
241+
let noteID: SwiftDiagnostics.MessageID = .init(
242+
domain: "observer.universe.BinaryParseKit.MoreThanCaseArgumentsNote",
243+
id: "macrosMoreThanCaseArguments",
244+
)
245+
}

Sources/BinaryParseKitMacros/Macros/ParseEnum/ParseEnumMacroError.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,27 @@ import SwiftDiagnostics
88

99
enum ParseEnumMacroError: Error, DiagnosticMessage {
1010
case onlyEnumsAreSupported
11-
case onlyOneEnumDeclarationForEachCase
12-
case parameterParseNumberNotMatch
13-
case matchMustProceedParse
11+
case caseArgumentsMoreThanMacros
12+
case macrosMoreThanCaseArguments
13+
case matchMustProceedParseAndSkip
1414
case missingCaseMatchMacro
1515
case defaultCaseMustBeLast
16+
case onlyOneMatchDefaultAllowed
17+
case matchDefaultShouldBeLast
1618
case unexpectedError(description: String)
1719

1820
var message: String {
1921
switch self {
2022
case .onlyEnumsAreSupported: "Only enums are supported by this macro."
21-
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."
23+
case .caseArgumentsMoreThanMacros:
24+
"The associated values in the enum case exceed the number of parse/skip macros."
25+
case .macrosMoreThanCaseArguments:
26+
"There are more parse/skip macros than the number of cases in the enum."
27+
case .matchMustProceedParseAndSkip: "The `match` macro must proceed all `parse` and `skip` macro."
2528
case .missingCaseMatchMacro: "A `case` declaration must has a `match` macro."
2629
case .defaultCaseMustBeLast: "The `matchDefault` case must be the last case in the enum."
30+
case .onlyOneMatchDefaultAllowed: "Only one `matchDefault` case is allowed in a enum."
31+
case .matchDefaultShouldBeLast: "The `matchDefault` case should be the last case in the enum."
2732
case let .unexpectedError(description: description):
2833
"Unexpected error: \(description)"
2934
}

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
}

0 commit comments

Comments
 (0)