99//
1010//===----------------------------------------------------------------------===//
1111
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-
1912import XCTest
2013import _StringProcessing
21-
2214import RegexBuilder
2315
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+
2420private struct Transaction : Hashable {
2521 enum Kind : Hashable {
2622 case credit
@@ -140,17 +136,19 @@ private func processWithRuntimeDynamicRegex(
140136) -> Transaction ? {
141137 // FIXME: Shouldn't this init throw?
142138 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+ }
143149
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)
154152}
155153
156154@available ( macOS 12 . 0 , * )
@@ -239,7 +237,8 @@ extension RegexDSLTests {
239237 XCTAssertEqual (
240238 referenceOutput, processWithNSRegularExpression ( line) )
241239
242- _ = processWithRuntimeDynamicRegex ( line)
240+ XCTAssertEqual (
241+ referenceOutput, processWithRuntimeDynamicRegex ( line) )
243242
244243 // Static run-time regex
245244 XCTAssertEqual (
@@ -256,12 +255,104 @@ extension RegexDSLTests {
256255 XCTFail ( )
257256 continue
258257 }
259-
260258 }
261-
262259 }
263-
264260}
265261
266262#endif
267263
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