Skip to content

Commit 9801855

Browse files
authored
Add tests for AnyRegexOutput (#371)
* Add tests for AnyRegexOutput Fix a few missing areas of functionality while we're at it
1 parent c44efeb commit 9801855

File tree

3 files changed

+173
-43
lines changed

3 files changed

+173
-43
lines changed

Sources/_StringProcessing/Regex/AnyRegexOutput.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public struct AnyRegexOutput {
6262
/// The depth of `Optioals`s wrapping the underlying value. For example,
6363
/// `Substring` has optional depth `0`, and `Int??` has optional depth `2`.
6464
let optionalDepth: Int
65+
6566
/// The bounds of the output element.
6667
let bounds: Range<String.Index>?
6768
}
@@ -90,7 +91,7 @@ extension AnyRegexOutput {
9091
/// - Parameter type: The expected output type.
9192
/// - Returns: The output, if the underlying value can be converted to the
9293
/// output type; otherwise `nil`.
93-
public func `as`<Output>(_ type: Output.Type) -> Output? {
94+
public func `as`<Output>(_ type: Output.Type = Output.self) -> Output? {
9495
let elements = _elements.map {
9596
StructuredCapture(
9697
optionalCount: $0.optionalDepth,
@@ -206,23 +207,30 @@ extension Regex.Match where Output == AnyRegexOutput {
206207
/// - Parameter type: The expected output type.
207208
/// - Returns: A match generic over the output type, if the underlying values
208209
/// can be converted to the output type; otherwise, `nil`.
209-
public func `as`<Output>(_ type: Output.Type) -> Regex<Output>.Match? {
210+
public func `as`<Output>(
211+
_ type: Output.Type = Output.self
212+
) -> Regex<Output>.Match? {
210213
fatalError("FIXME: Not implemented")
211214
}
212215
}
213216

214217
@available(SwiftStdlib 5.7, *)
215-
extension Regex where Output == AnyRegexOutput {
218+
extension Regex {
216219
/// Returns whether a named-capture with `name` exists
217220
public func contains(captureNamed name: String) -> Bool {
218-
fatalError("FIXME: not implemented")
221+
program.tree.root._captureList.captures.contains(where: {
222+
$0.name == name
223+
})
219224
}
225+
}
220226

227+
@available(SwiftStdlib 5.7, *)
228+
extension Regex where Output == AnyRegexOutput {
221229
/// Creates a type-erased regex from an existing regex.
222230
///
223231
/// Use this initializer to fit a regex with strongly typed captures into the
224232
/// use site of a dynamic regex, i.e. one that was created from a string.
225-
public init<Output>(_ match: Regex<Output>) {
233+
public init<Output>(_ regex: Regex<Output>) {
226234
fatalError("FIXME: Not implemented")
227235
}
228236

@@ -231,7 +239,9 @@ extension Regex where Output == AnyRegexOutput {
231239
/// - Parameter type: The expected output type.
232240
/// - Returns: A regex generic over the output type if the underlying types can be converted.
233241
/// Returns `nil` otherwise.
234-
public func `as`<Output>(_ type: Output.Type) -> Regex<Output>? {
242+
public func `as`<Output>(
243+
_ type: Output.Type = Output.self
244+
) -> Regex<Output>? {
235245
fatalError("FIXME: Not implemented")
236246
}
237247
}

Tests/RegexBuilderTests/RegexDSLTests.swift

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -742,43 +742,6 @@ class RegexDSLTests: XCTestCase {
742742
}
743743
}
744744

745-
func testDynamicCaptures() throws {
746-
do {
747-
let regex = try Regex("aabcc.")
748-
let line = "aabccd"
749-
let match = try XCTUnwrap(line.wholeMatch(of: regex))
750-
XCTAssertEqual(match.0, line[...])
751-
let output = match.output
752-
XCTAssertEqual(output[0].substring, line[...])
753-
}
754-
do {
755-
let regex = try Regex(
756-
#"""
757-
(?<lower>[0-9A-F]+)(?:\.\.(?<upper>[0-9A-F]+))?\s+;\s+(?<desc>\w+).*
758-
"""#)
759-
let line = """
760-
A6F0..A6F1 ; Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM \
761-
COMBINING MARK TUKWENTIS
762-
"""
763-
let match = try XCTUnwrap(line.wholeMatch(of: regex))
764-
XCTAssertEqual(match.0, line[...])
765-
let output = match.output
766-
XCTAssertEqual(output[0].substring, line[...])
767-
XCTAssertTrue(output[1].substring == "A6F0")
768-
XCTAssertTrue(output["lower"]?.substring == "A6F0")
769-
XCTAssertTrue(output[2].substring == "A6F1")
770-
XCTAssertTrue(output["upper"]?.substring == "A6F1")
771-
XCTAssertTrue(output[3].substring == "Extend")
772-
XCTAssertTrue(output["desc"]?.substring == "Extend")
773-
let typedOutput = try XCTUnwrap(output.as(
774-
(Substring, lower: Substring, upper: Substring?, Substring).self))
775-
XCTAssertEqual(typedOutput.0, line[...])
776-
XCTAssertTrue(typedOutput.lower == "A6F0")
777-
XCTAssertTrue(typedOutput.upper == "A6F1")
778-
XCTAssertTrue(typedOutput.3 == "Extend")
779-
}
780-
}
781-
782745
func testBackreference() throws {
783746
try _testDSLCaptures(
784747
("abc#41#42abcabcabc", ("abc#41#42abcabcabc", "abc", 42, "abc", nil)),
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
2+
import _StringProcessing
3+
import XCTest
4+
5+
// Test that our existential capture and concrete captures are
6+
// the same
7+
private func checkSame(
8+
_ aro: AnyRegexOutput,
9+
_ concrete: (Substring, fieldA: Substring, fieldB: Substring)
10+
) {
11+
XCTAssertEqual(aro[0].substring, concrete.0)
12+
13+
XCTAssertEqual(aro["fieldA"]!.substring, concrete.1)
14+
XCTAssertEqual(aro["fieldA"]!.substring, concrete.fieldA)
15+
16+
XCTAssertEqual(aro[1].substring, concrete.1)
17+
18+
XCTAssertEqual(aro["fieldB"]!.substring, concrete.2)
19+
XCTAssertEqual(aro["fieldB"]!.substring, concrete.fieldB)
20+
21+
XCTAssertEqual(aro[2].substring, concrete.2)
22+
23+
}
24+
private func checkSame(
25+
_ aro: Regex<AnyRegexOutput>.Match,
26+
_ concrete: Regex<(Substring, fieldA: Substring, fieldB: Substring)>.Match
27+
) {
28+
checkSame(aro.output, concrete.output)
29+
30+
XCTAssertEqual(aro.0, concrete.0)
31+
XCTAssertEqual(aro[0].substring, concrete.0)
32+
33+
XCTAssertEqual(aro["fieldA"]!.substring, concrete.1)
34+
XCTAssertEqual(aro["fieldA"]!.substring, concrete.fieldA)
35+
XCTAssertEqual(aro[1].substring, concrete.1)
36+
37+
XCTAssertEqual(aro["fieldB"]!.substring, concrete.2)
38+
XCTAssertEqual(aro["fieldB"]!.substring, concrete.fieldB)
39+
XCTAssertEqual(aro[2].substring, concrete.2)
40+
}
41+
private func checkSame(
42+
_ aro: Regex<AnyRegexOutput>,
43+
_ concrete: Regex<(Substring, fieldA: Substring, fieldB: Substring)>
44+
) {
45+
XCTAssertEqual(
46+
aro.contains(captureNamed: "fieldA"),
47+
concrete.contains(captureNamed: "fieldA"))
48+
XCTAssertEqual(
49+
aro.contains(captureNamed: "fieldB"),
50+
concrete.contains(captureNamed: "fieldB"))
51+
XCTAssertEqual(
52+
aro.contains(captureNamed: "notAField"),
53+
concrete.contains(captureNamed: "notAField"))
54+
}
55+
56+
extension RegexTests {
57+
func testAnyRegexOutput() {
58+
let regex = try! Regex(#"""
59+
(?x)
60+
(?<fieldA> [^,]*)
61+
,
62+
(?<fieldB> [^,]*)
63+
"""#)
64+
65+
let match = "abc,def".wholeMatch(of: regex)!
66+
XCTAssertEqual(match.0, "abc,def")
67+
XCTAssertEqual(match[0].substring, "abc,def")
68+
69+
XCTAssertEqual(match["fieldA"]!.substring, "abc")
70+
XCTAssertEqual(match.output["fieldA"]!.substring, "abc")
71+
XCTAssertEqual(match[1].substring, "abc")
72+
73+
XCTAssertEqual(match["fieldB"]!.substring, "def")
74+
XCTAssertEqual(match.output["fieldB"]!.substring, "def")
75+
XCTAssertEqual(match[2].substring, "def")
76+
77+
XCTAssertNil(match["notACapture"])
78+
XCTAssertNil(match.output["notACapture"])
79+
XCTAssertEqual(match.count, 3)
80+
81+
XCTAssert(regex.contains(captureNamed: "fieldA"))
82+
XCTAssert(regex.contains(captureNamed: "fieldB"))
83+
XCTAssertFalse(regex.contains(captureNamed: "notAField"))
84+
85+
// MARK: Check equivalence with concrete
86+
87+
let regexConcrete:
88+
Regex<(Substring, fieldA: Substring, fieldB: Substring)>
89+
= try! Regex(#"""
90+
(?x)
91+
(?<fieldA> [^,]*)
92+
,
93+
(?<fieldB> [^,]*)
94+
"""#)
95+
checkSame(regex, regexConcrete)
96+
97+
let matchConcrete = "abc,def".wholeMatch(of: regexConcrete)!
98+
checkSame(match, matchConcrete)
99+
100+
let output = match.output
101+
let concreteOutput = matchConcrete.output
102+
checkSame(output, concreteOutput)
103+
104+
// TODO: ARO init from concrete match tuple
105+
106+
let concreteOutputCasted = output.as(
107+
(Substring, fieldA: Substring, fieldB: Substring).self
108+
)!
109+
checkSame(output, concreteOutputCasted)
110+
111+
var concreteOutputCopy = concreteOutput
112+
concreteOutputCopy = output.as()!
113+
checkSame(output, concreteOutputCopy)
114+
115+
// TODO: Regex<ARO>.Match: init from tuple match and as to tuple match
116+
117+
// TODO: Regex<ARO>: init from tuple regex and as cast to tuple regex
118+
119+
}
120+
121+
func testDynamicCaptures() throws {
122+
do {
123+
let regex = try Regex("aabcc.")
124+
let line = "aabccd"
125+
let match = try XCTUnwrap(line.wholeMatch(of: regex))
126+
XCTAssertEqual(match.0, line[...])
127+
let output = match.output
128+
XCTAssertEqual(output[0].substring, line[...])
129+
}
130+
do {
131+
let regex = try Regex(
132+
#"""
133+
(?<lower>[0-9A-F]+)(?:\.\.(?<upper>[0-9A-F]+))?\s+;\s+(?<desc>\w+).*
134+
"""#)
135+
let line = """
136+
A6F0..A6F1 ; Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM \
137+
COMBINING MARK TUKWENTIS
138+
"""
139+
let match = try XCTUnwrap(line.wholeMatch(of: regex))
140+
XCTAssertEqual(match.0, line[...])
141+
let output = match.output
142+
XCTAssertEqual(output[0].substring, line[...])
143+
XCTAssertTrue(output[1].substring == "A6F0")
144+
XCTAssertTrue(output["lower"]?.substring == "A6F0")
145+
XCTAssertTrue(output[2].substring == "A6F1")
146+
XCTAssertTrue(output["upper"]?.substring == "A6F1")
147+
XCTAssertTrue(output[3].substring == "Extend")
148+
XCTAssertTrue(output["desc"]?.substring == "Extend")
149+
let typedOutput = try XCTUnwrap(output.as(
150+
(Substring, lower: Substring, upper: Substring?, Substring).self))
151+
XCTAssertEqual(typedOutput.0, line[...])
152+
XCTAssertTrue(typedOutput.lower == "A6F0")
153+
XCTAssertTrue(typedOutput.upper == "A6F1")
154+
XCTAssertTrue(typedOutput.3 == "Extend")
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)