Skip to content

Commit 7e1ab7d

Browse files
committed
Unify Match and AnyRegexOutput
1 parent 3f54941 commit 7e1ab7d

File tree

5 files changed

+75
-70
lines changed

5 files changed

+75
-70
lines changed

Sources/_StringProcessing/Capture.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,30 @@ func constructExistentialOutputComponent(
6161
return underlying
6262
}
6363

64-
extension StructuredCapture {
64+
@available(SwiftStdlib 5.7, *)
65+
extension AnyRegexOutput.Element {
6566
func existentialOutputComponent(
6667
from input: Substring
6768
) -> Any {
6869
constructExistentialOutputComponent(
6970
from: input,
70-
in: storedCapture?.range,
71-
value: storedCapture?.value,
72-
optionalCount: optionalCount)
71+
in: range,
72+
value: value,
73+
optionalCount: optionalDepth
74+
)
7375
}
7476

7577
func slice(from input: String) -> Substring? {
76-
guard let r = storedCapture?.range else { return nil }
78+
guard let r = range else { return nil }
7779
return input[r]
7880
}
7981
}
8082

81-
extension Sequence where Element == StructuredCapture {
83+
@available(SwiftStdlib 5.7, *)
84+
extension Sequence where Element == AnyRegexOutput.Element {
8285
// FIXME: This is a stop gap where we still slice the input
8386
// and traffic through existentials
87+
@available(SwiftStdlib 5.7, *)
8488
func existentialOutput(
8589
from input: Substring
8690
) -> Any {

Sources/_StringProcessing/Executor.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,19 @@ struct Executor {
5555
} else {
5656
value = nil
5757
}
58-
59-
return .init(
58+
59+
let anyRegexOutput = AnyRegexOutput(
6060
input: input,
61+
namedCaptureOffsets: capList.namedCaptureOffsets,
62+
elements: caps
63+
)
64+
65+
return .init(
66+
anyRegexOutput: anyRegexOutput,
6167
range: range,
62-
rawCaptures: caps,
6368
referencedCaptureOffsets: capList.referencedCaptureOffsets,
64-
namedCaptureOffsets: capList.namedCaptureOffsets,
65-
value: value)
69+
value: value
70+
)
6671
}
6772

6873
@available(SwiftStdlib 5.7, *)

Sources/_StringProcessing/Regex/AnyRegexOutput.swift

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ extension Regex.Match where Output == AnyRegexOutput {
4141
public subscript(
4242
dynamicMember keyPath: KeyPath<(Substring, _doNotUse: ()), Substring>
4343
) -> Substring {
44-
input[range]
44+
anyRegexOutput.input[range]
4545
}
4646

4747
public subscript(name: String) -> AnyRegexOutput.Element? {
48-
namedCaptureOffsets[name].map { self[$0 + 1] }
48+
anyRegexOutput.namedCaptureOffsets[name].map { self[$0 + 1] }
4949
}
5050
}
5151

@@ -54,17 +54,19 @@ extension Regex.Match where Output == AnyRegexOutput {
5454
public struct AnyRegexOutput {
5555
let input: String
5656
let namedCaptureOffsets: [String: Int]
57-
fileprivate let _elements: [ElementRepresentation]
57+
let _elements: [ElementRepresentation]
5858

5959
/// The underlying representation of the element of a type-erased regex
6060
/// output.
61-
fileprivate struct ElementRepresentation {
61+
internal struct ElementRepresentation {
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
6565

6666
/// The bounds of the output element.
6767
let bounds: Range<String.Index>?
68+
/// If the output vaule is strongly typed, then this will be set.
69+
var value: Any? = nil
6870
}
6971
}
7072

@@ -75,14 +77,7 @@ extension AnyRegexOutput {
7577
/// Use this initializer to fit a regex with strongly typed captures into the
7678
/// use site of a dynamic regex, like one that was created from a string.
7779
public init<Output>(_ match: Regex<Output>.Match) {
78-
// Note: We use type equality instead of `match.output as? ...` to prevent
79-
// unexpected optional flattening.
80-
if Output.self == AnyRegexOutput.self {
81-
self = match.output as! AnyRegexOutput
82-
return
83-
}
84-
fatalError("FIXME: Not implemented")
85-
// self.init(input: match.input, _elements: <elements of output tuple>)
80+
self = match.anyRegexOutput
8681
}
8782

8883
/// Returns a typed output by converting the underlying value to the specified
@@ -92,11 +87,8 @@ extension AnyRegexOutput {
9287
/// - Returns: The output, if the underlying value can be converted to the
9388
/// output type; otherwise `nil`.
9489
public func `as`<Output>(_ type: Output.Type = Output.self) -> Output? {
95-
let elements = _elements.map {
96-
StructuredCapture(
97-
optionalCount: $0.optionalDepth,
98-
storedCapture: .init(range: $0.bounds)
99-
).existentialOutputComponent(from: input[...])
90+
let elements = map {
91+
$0.existentialOutputComponent(from: input[...])
10092
}
10193
return TypeConstruction.tuple(of: elements) as? Output
10294
}
@@ -110,7 +102,8 @@ extension AnyRegexOutput {
110102
self.init(
111103
input: input,
112104
namedCaptureOffsets: namedCaptureOffsets,
113-
_elements: elements.map(ElementRepresentation.init))
105+
_elements: elements.map(ElementRepresentation.init)
106+
)
114107
}
115108
}
116109

@@ -119,7 +112,9 @@ extension AnyRegexOutput.ElementRepresentation {
119112
init(_ element: StructuredCapture) {
120113
self.init(
121114
optionalDepth: element.optionalCount,
122-
bounds: element.storedCapture.flatMap(\.range))
115+
bounds: element.storedCapture.flatMap(\.range),
116+
value: element.storedCapture.flatMap(\.value)
117+
)
123118
}
124119

125120
func value(forInput input: String) -> Any {
@@ -142,6 +137,10 @@ extension AnyRegexOutput: RandomAccessCollection {
142137
public struct Element {
143138
fileprivate let representation: ElementRepresentation
144139
let input: String
140+
141+
var optionalDepth: Int {
142+
representation.optionalDepth
143+
}
145144

146145
/// The range over which a value was captured. `nil` for no-capture.
147146
public var range: Range<String.Index>? {
@@ -155,7 +154,7 @@ extension AnyRegexOutput: RandomAccessCollection {
155154

156155
/// The captured value, `nil` for no-capture
157156
public var value: Any? {
158-
fatalError()
157+
representation.value
159158
}
160159
}
161160

@@ -198,19 +197,12 @@ extension Regex.Match where Output == AnyRegexOutput {
198197
/// Use this initializer to fit a regex match with strongly typed captures into the
199198
/// use site of a dynamic regex match, like one that was created from a string.
200199
public init<Output>(_ match: Regex<Output>.Match) {
201-
fatalError("FIXME: Not implemented")
202-
}
203-
204-
/// Returns a typed match by converting the underlying values to the specified
205-
/// types.
206-
///
207-
/// - Parameter type: The expected output type.
208-
/// - Returns: A match generic over the output type, if the underlying values
209-
/// can be converted to the output type; otherwise, `nil`.
210-
public func `as`<Output>(
211-
_ type: Output.Type = Output.self
212-
) -> Regex<Output>.Match? {
213-
fatalError("FIXME: Not implemented")
200+
self.init(
201+
anyRegexOutput: match.anyRegexOutput,
202+
range: match.range,
203+
referencedCaptureOffsets: match.referencedCaptureOffsets,
204+
value: match.value
205+
)
214206
}
215207
}
216208

Sources/_StringProcessing/Regex/Match.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,13 @@ extension Regex {
1717
/// providing direct access to captures.
1818
@dynamicMemberLookup
1919
public struct Match {
20-
let input: String
20+
let anyRegexOutput: AnyRegexOutput
2121

2222
/// The range of the overall match.
2323
public let range: Range<String.Index>
2424

25-
let rawCaptures: [StructuredCapture]
26-
2725
let referencedCaptureOffsets: [ReferenceID: Int]
2826

29-
let namedCaptureOffsets: [String: Int]
30-
3127
let value: Any?
3228
}
3329
}
@@ -37,18 +33,23 @@ extension Regex.Match {
3733
/// The output produced from the match operation.
3834
public var output: Output {
3935
if Output.self == AnyRegexOutput.self {
40-
let wholeMatchAsCapture = StructuredCapture(
41-
optionalCount: 0,
42-
storedCapture: StoredCapture(range: range, value: nil))
36+
let wholeMatchCapture = AnyRegexOutput.ElementRepresentation(
37+
optionalDepth: 0,
38+
bounds: range,
39+
value: nil
40+
)
41+
4342
let output = AnyRegexOutput(
44-
input: input,
45-
namedCaptureOffsets: namedCaptureOffsets,
46-
elements: [wholeMatchAsCapture] + rawCaptures)
43+
input: anyRegexOutput.input,
44+
namedCaptureOffsets: anyRegexOutput.namedCaptureOffsets,
45+
_elements: [wholeMatchCapture] + anyRegexOutput._elements
46+
)
47+
4748
return output as! Output
4849
} else if Output.self == Substring.self {
4950
// FIXME: Plumb whole match (`.0`) through the matching engine.
50-
return input[range] as! Output
51-
} else if rawCaptures.isEmpty, value != nil {
51+
return anyRegexOutput.input[range] as! Output
52+
} else if anyRegexOutput.isEmpty, value != nil {
5253
// FIXME: This is a workaround for whole-match values not
5354
// being modeled as part of captures. We might want to
5455
// switch to a model where results are alongside captures
@@ -57,7 +58,9 @@ extension Regex.Match {
5758
guard value == nil else {
5859
fatalError("FIXME: what would this mean?")
5960
}
60-
let typeErasedMatch = rawCaptures.existentialOutput(from: input[range])
61+
let typeErasedMatch = anyRegexOutput.existentialOutput(
62+
from: anyRegexOutput.input[range]
63+
)
6164
return typeErasedMatch as! Output
6265
}
6366
}
@@ -81,8 +84,9 @@ extension Regex.Match {
8184
preconditionFailure(
8285
"Reference did not capture any match in the regex")
8386
}
84-
return rawCaptures[offset].existentialOutputComponent(from: input[...])
85-
as! Capture
87+
return anyRegexOutput[offset].existentialOutputComponent(
88+
from: anyRegexOutput.input[...]
89+
) as! Capture
8690
}
8791
}
8892

Tests/RegexTests/CaptureTests.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,20 @@ extension CaptureList {
4848
}
4949
}
5050

51-
extension StructuredCapture {
51+
extension AnyRegexOutput.Element {
5252
func formatStringCapture(input: String) -> String {
53-
var res = String(repeating: "some(", count: someCount)
54-
if let r = self.storedCapture?.range {
53+
var res = String(repeating: "some(", count: optionalDepth)
54+
if let r = range {
5555
res += input[r]
5656
} else {
5757
res += "none"
5858
}
59-
res += String(repeating: ")", count: someCount)
59+
res += String(repeating: ")", count: optionalDepth)
6060
return res
6161
}
6262
}
6363

64-
extension Sequence where Element == StructuredCapture {
64+
extension AnyRegexOutput {
6565
func formatStringCaptures(input: String) -> String {
6666
var res = "["
6767
res += self.map {
@@ -111,13 +111,13 @@ extension StringCapture: CustomStringConvertible {
111111

112112
extension StringCapture {
113113
func isEqual(
114-
to structCap: StructuredCapture,
114+
to structCap: AnyRegexOutput.Element,
115115
in input: String
116116
) -> Bool {
117-
guard optionalCount == structCap.optionalCount else {
117+
guard optionalCount == structCap.optionalDepth else {
118118
return false
119119
}
120-
guard let r = structCap.storedCapture?.range else {
120+
guard let r = structCap.range else {
121121
return contents == nil
122122
}
123123
guard let s = contents else {
@@ -194,7 +194,7 @@ func captureTest(
194194
return
195195
}
196196

197-
let caps = result.rawCaptures
197+
let caps = result.anyRegexOutput
198198
guard caps.count == output.count else {
199199
XCTFail("""
200200
Mismatch capture count:
@@ -205,7 +205,7 @@ func captureTest(
205205
""")
206206
continue
207207
}
208-
208+
209209
guard output.elementsEqual(caps, by: {
210210
$0.isEqual(to: $1, in: input)
211211
}) else {

0 commit comments

Comments
 (0)