Skip to content

Commit d0598b7

Browse files
authored
Add example from RegexBuilder proposal as a test (#344)
The "motivation tests" still aren't working on macOS CI
1 parent 2401a58 commit d0598b7

File tree

2 files changed

+145
-22
lines changed

2 files changed

+145
-22
lines changed

Tests/RegexBuilderTests/MotivationTests.swift

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@
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-
1912
import XCTest
2013
import _StringProcessing
21-
2214
import 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+
2420
private 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+
}

Tests/RegexBuilderTests/RegexDSLTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,38 @@ class RegexDSLTests: XCTestCase {
830830
XCTAssertEqual(result[b], 42)
831831
}
832832

833+
do {
834+
let key = Reference(Substring.self)
835+
let value = Reference(Int.self)
836+
let input = " "
837+
let regex = Regex {
838+
Capture(as: key) {
839+
Optionally {
840+
OneOrMore(.word)
841+
}
842+
}
843+
":"
844+
Optionally {
845+
Capture(as: value) {
846+
OneOrMore(.digit)
847+
} transform: { Int($0)! }
848+
}
849+
}
850+
851+
let result1 = try XCTUnwrap("age:123".wholeMatch(of: regex))
852+
XCTAssertEqual(result1[key], "age")
853+
XCTAssertEqual(result1[value], 123)
854+
855+
let result2 = try XCTUnwrap(":567".wholeMatch(of: regex))
856+
XCTAssertEqual(result2[key], "")
857+
XCTAssertEqual(result2[value], 567)
858+
859+
let result3 = try XCTUnwrap("status:".wholeMatch(of: regex))
860+
XCTAssertEqual(result3[key], "status")
861+
// Traps:
862+
// XCTAssertEqual(result3[value], nil)
863+
}
864+
833865
// Post-hoc captured references
834866
// #"(?:\w\1|:(\w):)+"#
835867
try _testDSLCaptures(

0 commit comments

Comments
 (0)