9
9
//
10
10
//===----------------------------------------------------------------------===//
11
11
12
- // FIXME: macOS CI seems to be busted and Linux doesn't have FormatStyle
13
- // So, we disable this file for now
14
-
15
- #if false
16
-
17
- import _MatchingEngine
18
-
19
12
import XCTest
20
13
import _StringProcessing
21
-
22
14
import RegexBuilder
23
15
16
+ // FIXME: macOS CI seems to be busted and Linux doesn't have FormatStyle
17
+ // So, we disable this larger test for now.
18
+ #if false
19
+
24
20
private struct Transaction : Hashable {
25
21
enum Kind : Hashable {
26
22
case credit
@@ -140,17 +136,19 @@ private func processWithRuntimeDynamicRegex(
140
136
) -> Transaction ? {
141
137
// FIXME: Shouldn't this init throw?
142
138
let regex = try ! Regex ( pattern)
139
+ let dateStrat = Date . FormatStyle ( date: . numeric) . parseStrategy
140
+
141
+ guard let result = line. wholeMatch ( of: regex) ? . output,
142
+ let kind = Transaction . Kind ( result [ 1 ] . substring!) ,
143
+ let date = try ? Date ( String ( result [ 2 ] . substring!) , strategy: dateStrat) ,
144
+ let account = result [ 3 ] . substring. map ( String . init) ,
145
+ let amount = try ? Decimal (
146
+ String ( result [ 4 ] . substring!) , format: . currency( code: " USD " ) ) else {
147
+ return nil
148
+ }
143
149
144
- // guard let result = line.match(regex) else { return nil }
145
- //
146
- // // TODO: We should have Regex<DynamicCaptures> or somesuch and `.1`
147
- // // should be the same as `\1`.
148
- // let dynCaps = result.1
149
- //
150
- //
151
- // let kind = Transaction.Kind(result.1.first!.capture as Substring)
152
-
153
- return nil
150
+ return Transaction (
151
+ kind: kind, date: date, account: account, amount: amount)
154
152
}
155
153
156
154
@available ( macOS 12 . 0 , * )
@@ -239,7 +237,8 @@ extension RegexDSLTests {
239
237
XCTAssertEqual (
240
238
referenceOutput, processWithNSRegularExpression ( line) )
241
239
242
- _ = processWithRuntimeDynamicRegex ( line)
240
+ XCTAssertEqual (
241
+ referenceOutput, processWithRuntimeDynamicRegex ( line) )
243
242
244
243
// Static run-time regex
245
244
XCTAssertEqual (
@@ -256,12 +255,104 @@ extension RegexDSLTests {
256
255
XCTFail ( )
257
256
continue
258
257
}
259
-
260
258
}
261
-
262
259
}
263
-
264
260
}
265
261
266
262
#endif
267
263
264
+ extension RegexDSLTests {
265
+ func testProposalExample( ) {
266
+ let statement = """
267
+ CREDIT 04062020 PayPal transfer $4.99
268
+ CREDIT 04032020 Payroll $69.73
269
+ DEBIT 04022020 ACH transfer $38.25
270
+ DEBIT 03242020 IRS tax payment $52249.98
271
+ """
272
+ let expectation : [ ( TransactionKind , Date , Substring , Double ) ] = [
273
+ ( . credit, Date ( mmddyyyy: " 04062020 " ) !, " PayPal transfer " , 4.99 ) ,
274
+ ( . credit, Date ( mmddyyyy: " 04032020 " ) !, " Payroll " , 69.73 ) ,
275
+ ( . debit, Date ( mmddyyyy: " 04022020 " ) !, " ACH transfer " , 38.25 ) ,
276
+ ( . debit, Date ( mmddyyyy: " 03242020 " ) !, " IRS tax payment " , 52249.98 ) ,
277
+ ]
278
+
279
+ enum TransactionKind : String {
280
+ case credit = " CREDIT "
281
+ case debit = " DEBIT "
282
+ }
283
+
284
+ struct Date : Hashable {
285
+ var month : Int
286
+ var day : Int
287
+ var year : Int
288
+
289
+ init ? ( mmddyyyy: String ) {
290
+ guard let ( _, m, d, y) = mmddyyyy. wholeMatch ( of: Regex {
291
+ Capture ( Repeat ( . digit, count: 2 ) , transform: { Int ( $0) ! } )
292
+ Capture ( Repeat ( . digit, count: 2 ) , transform: { Int ( $0) ! } )
293
+ Capture ( Repeat ( . digit, count: 4 ) , transform: { Int ( $0) ! } )
294
+ } ) ? . output else {
295
+ return nil
296
+ }
297
+
298
+ self . month = m
299
+ self . day = d
300
+ self . year = y
301
+ }
302
+ }
303
+
304
+ let statementRegex = Regex {
305
+ // First, lets capture the transaction kind by wrapping our ChoiceOf in a
306
+ // TryCapture because we want
307
+ TryCapture {
308
+ ChoiceOf {
309
+ " CREDIT "
310
+ " DEBIT "
311
+ }
312
+ } transform: {
313
+ TransactionKind ( rawValue: String ( $0) )
314
+ }
315
+
316
+ OneOrMore ( . whitespace)
317
+
318
+ // Next, lets represent our date as 3 separate repeat quantifiers. The first
319
+ // two will require 2 digit characters, and the last will require 4. Then
320
+ // we'll take the entire substring and try to parse a date out.
321
+ TryCapture {
322
+ Repeat ( . digit, count: 2 )
323
+ Repeat ( . digit, count: 2 )
324
+ Repeat ( . digit, count: 4 )
325
+ } transform: {
326
+ Date ( mmddyyyy: String ( $0) )
327
+ }
328
+
329
+ OneOrMore ( . whitespace)
330
+
331
+ // Next, grab the description which can be any combination of word characters,
332
+ // digits, etc.
333
+ Capture {
334
+ OneOrMore ( . any, . reluctant)
335
+ }
336
+
337
+ OneOrMore ( . whitespace)
338
+
339
+ " $ "
340
+
341
+ // Finally, we'll grab one or more digits which will represent the whole
342
+ // dollars, match the decimal point, and finally get 2 digits which will be
343
+ // our cents.
344
+ TryCapture {
345
+ OneOrMore ( . digit)
346
+ " . "
347
+ Repeat ( . digit, count: 2 )
348
+ } transform: {
349
+ Double ( $0)
350
+ }
351
+ }
352
+
353
+ for (i, match) in statement. matches ( of: statementRegex) . enumerated ( ) {
354
+ let ( _, kind, date, description, amount) = match. output
355
+ XCTAssert ( ( kind, date, description, amount) == expectation [ i] )
356
+ }
357
+ }
358
+ }
0 commit comments