Skip to content

Commit 592e377

Browse files
committed
Unify Match and AnyRegexOutput
1 parent aa83aec commit 592e377

File tree

5 files changed

+75
-68
lines changed

5 files changed

+75
-68
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 & 31 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,16 +54,18 @@ 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
/// The bounds of the output element.
6666
let bounds: Range<String.Index>?
67+
/// If the output vaule is strongly typed, then this will be set.
68+
var value: Any? = nil
6769
}
6870
}
6971

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

8782
/// Returns a typed output by converting the underlying value to the specified
@@ -91,11 +86,8 @@ extension AnyRegexOutput {
9186
/// - Returns: The output, if the underlying value can be converted to the
9287
/// output type; otherwise `nil`.
9388
public func `as`<Output>(_ type: Output.Type) -> Output? {
94-
let elements = _elements.map {
95-
StructuredCapture(
96-
optionalCount: $0.optionalDepth,
97-
storedCapture: .init(range: $0.bounds)
98-
).existentialOutputComponent(from: input[...])
89+
let elements = map {
90+
$0.existentialOutputComponent(from: input[...])
9991
}
10092
return TypeConstruction.tuple(of: elements) as? Output
10193
}
@@ -109,7 +101,8 @@ extension AnyRegexOutput {
109101
self.init(
110102
input: input,
111103
namedCaptureOffsets: namedCaptureOffsets,
112-
_elements: elements.map(ElementRepresentation.init))
104+
_elements: elements.map(ElementRepresentation.init)
105+
)
113106
}
114107
}
115108

@@ -118,7 +111,9 @@ extension AnyRegexOutput.ElementRepresentation {
118111
init(_ element: StructuredCapture) {
119112
self.init(
120113
optionalDepth: element.optionalCount,
121-
bounds: element.storedCapture.flatMap(\.range))
114+
bounds: element.storedCapture.flatMap(\.range),
115+
value: element.storedCapture.flatMap(\.value)
116+
)
122117
}
123118

124119
func value(forInput input: String) -> Any {
@@ -141,6 +136,10 @@ extension AnyRegexOutput: RandomAccessCollection {
141136
public struct Element {
142137
fileprivate let representation: ElementRepresentation
143138
let input: String
139+
140+
var optionalDepth: Int {
141+
representation.optionalDepth
142+
}
144143

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

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

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

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)