Skip to content

Commit 2fa9464

Browse files
committed
Use inits instead of as methods
1 parent 88dc9dd commit 2fa9464

File tree

5 files changed

+126
-116
lines changed

5 files changed

+126
-116
lines changed

Sources/_StringProcessing/Capture.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ extension AnyRegexOutput.Element {
4949
from: input,
5050
in: range,
5151
value: value,
52-
optionalCount: optionalDepth
52+
optionalCount: representation.optionalDepth
5353
)
5454
}
5555

Sources/_StringProcessing/Regex/AnyRegexOutput.swift

Lines changed: 89 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ extension Regex.Match where Output == AnyRegexOutput {
4444
anyRegexOutput.input[range]
4545
}
4646

47+
/// Access a capture by name. Returns `nil` if there's no capture with that name.
4748
public subscript(name: String) -> AnyRegexOutput.Element? {
4849
anyRegexOutput.first {
4950
$0.name == name
@@ -54,28 +55,8 @@ extension Regex.Match where Output == AnyRegexOutput {
5455
/// A type-erased regex output.
5556
@available(SwiftStdlib 5.7, *)
5657
public struct AnyRegexOutput {
57-
let input: String
58-
let _elements: [ElementRepresentation]
59-
60-
/// The underlying representation of the element of a type-erased regex
61-
/// output.
62-
internal struct ElementRepresentation {
63-
/// The depth of `Optioals`s wrapping the underlying value. For example,
64-
/// `Substring` has optional depth `0`, and `Int??` has optional depth `2`.
65-
let optionalDepth: Int
66-
67-
/// The bounds of the output element.
68-
let bounds: Range<String.Index>?
69-
70-
/// The name of the capture.
71-
var name: String? = nil
72-
73-
/// The capture reference this element refers to.
74-
var referenceID: ReferenceID? = nil
75-
76-
/// If the output vaule is strongly typed, then this will be set.
77-
var value: Any? = nil
78-
}
58+
internal let input: String
59+
internal let _elements: [ElementRepresentation]
7960
}
8061

8162
@available(SwiftStdlib 5.7, *)
@@ -88,80 +69,57 @@ extension AnyRegexOutput {
8869
self = match.anyRegexOutput
8970
}
9071

91-
/// Returns a typed output by converting the underlying value to the specified
92-
/// type.
72+
/// Returns a typed output by converting dynamic values to the specified type.
9373
///
9474
/// - Parameter type: The expected output type.
9575
/// - Returns: The output, if the underlying value can be converted to the
9676
/// output type; otherwise `nil`.
97-
public func `as`<Output>(_ type: Output.Type = Output.self) -> Output? {
77+
public func extractValues<Output>(
78+
as type: Output.Type = Output.self
79+
) -> Output? {
9880
let elements = map {
9981
$0.existentialOutputComponent(from: input[...])
10082
}
10183
return TypeConstruction.tuple(of: elements) as? Output
10284
}
10385
}
10486

105-
@available(SwiftStdlib 5.7, *)
106-
extension AnyRegexOutput {
107-
internal init(input: String, elements: [ElementRepresentation]) {
108-
self.init(
109-
input: input,
110-
_elements: elements
111-
)
112-
}
113-
}
114-
115-
@available(SwiftStdlib 5.7, *)
116-
extension AnyRegexOutput.ElementRepresentation {
117-
func value(forInput input: String) -> Any {
118-
// Ok for now because `existentialMatchComponent`
119-
// wont slice the input if there's no range to slice with
120-
//
121-
// FIXME: This is ugly :-/
122-
let input = bounds.map { input[$0] } ?? ""
123-
124-
return constructExistentialOutputComponent(
125-
from: input,
126-
in: bounds,
127-
value: nil,
128-
optionalCount: optionalDepth
129-
)
130-
}
131-
}
132-
13387
@available(SwiftStdlib 5.7, *)
13488
extension AnyRegexOutput: RandomAccessCollection {
89+
/// An individual output value.
13590
public struct Element {
136-
fileprivate let representation: ElementRepresentation
137-
let input: String
138-
139-
var optionalDepth: Int {
140-
representation.optionalDepth
141-
}
142-
143-
var name: String? {
144-
representation.name
145-
}
91+
internal let representation: ElementRepresentation
92+
internal let input: String
14693

14794
/// The range over which a value was captured. `nil` for no-capture.
14895
public var range: Range<String.Index>? {
14996
representation.bounds
15097
}
151-
152-
var referenceID: ReferenceID? {
153-
representation.referenceID
154-
}
155-
98+
15699
/// The slice of the input over which a value was captured. `nil` for no-capture.
157100
public var substring: Substring? {
158101
range.map { input[$0] }
159102
}
160103

161104
/// The captured value, `nil` for no-capture
105+
/// TODO: clarify whether this is for non-default captured values, i.e. not the
106+
/// default Substring but more like a Date or an explicit capture
162107
public var value: Any? {
163108
representation.value
164109
}
110+
111+
/// The name of this capture, if it has one, otherwise `nil`.
112+
public var name: String? {
113+
representation.name
114+
}
115+
116+
// TODO: Consider making API, and figure out how
117+
// DSL and this would work together...
118+
/// Whether this capture is considered optional by the regex. I.e.,
119+
/// whether it is inside an alternation or zero-or-n quantification.
120+
var isOptional: Bool {
121+
representation.optionalDepth != 0
122+
}
165123
}
166124

167125
public var startIndex: Int {
@@ -191,6 +149,8 @@ extension AnyRegexOutput: RandomAccessCollection {
191149

192150
@available(SwiftStdlib 5.7, *)
193151
extension AnyRegexOutput {
152+
/// Lookup a capture by name. Returns `nil` if no capture
153+
/// with that name was present in the Regex.
194154
public subscript(name: String) -> Element? {
195155
first {
196156
$0.name == name
@@ -213,16 +173,6 @@ extension Regex.Match where Output == AnyRegexOutput {
213173
}
214174
}
215175

216-
@available(SwiftStdlib 5.7, *)
217-
extension Regex {
218-
/// Returns whether a named-capture with `name` exists
219-
public func contains(captureNamed name: String) -> Bool {
220-
program.tree.root._captureList.captures.contains(where: {
221-
$0.name == name
222-
})
223-
}
224-
}
225-
226176
@available(SwiftStdlib 5.7, *)
227177
extension Regex where Output == AnyRegexOutput {
228178
/// Creates a type-erased regex from an existing regex.
@@ -232,21 +182,70 @@ extension Regex where Output == AnyRegexOutput {
232182
public init<Output>(_ regex: Regex<Output>) {
233183
self.init(node: regex.root)
234184
}
185+
}
235186

236-
/// Returns a typed regex by converting the underlying types.
187+
@available(SwiftStdlib 5.7, *)
188+
extension Regex {
189+
/// Creates a strongly-typed regex from a dynamic regex.
237190
///
238-
/// - Parameter type: The expected output type.
239-
/// - Returns: A regex generic over the output type if the underlying types can be converted.
240-
/// Returns `nil` otherwise.
241-
public func `as`<Output>(
242-
_ type: Output.Type = Output.self
243-
) -> Regex<Output>? {
244-
let result = Regex<Output>(node: root)
245-
246-
guard result._verifyType() else {
191+
/// Use this initializer to create a strongly typed regex from
192+
/// one that was created from a string.
193+
public init?(_ dynamic: Regex<AnyRegexOutput>) {
194+
self.init(node: dynamic.root)
195+
guard self._verifyType() else {
247196
return nil
248197
}
249-
250-
return result
198+
}
199+
200+
/// Returns whether a named-capture with `name` exists
201+
public func contains(captureNamed name: String) -> Bool {
202+
program.tree.root._captureList.captures.contains(where: {
203+
$0.name == name
204+
})
205+
}
206+
}
207+
208+
@available(SwiftStdlib 5.7, *)
209+
extension AnyRegexOutput {
210+
/// The underlying representation of the element of a type-erased regex
211+
/// output.
212+
internal struct ElementRepresentation {
213+
/// The depth of `Optioals`s wrapping the underlying value. For example,
214+
/// `Substring` has optional depth `0`, and `Int??` has optional depth `2`.
215+
let optionalDepth: Int
216+
217+
/// The bounds of the output element.
218+
let bounds: Range<String.Index>?
219+
220+
/// The name of the capture.
221+
var name: String? = nil
222+
223+
/// The capture reference this element refers to.
224+
var referenceID: ReferenceID? = nil
225+
226+
/// If the output vaule is strongly typed, then this will be set.
227+
var value: Any? = nil
228+
}
229+
230+
internal init(input: String, elements: [ElementRepresentation]) {
231+
self.init(input: input, _elements: elements)
232+
}
233+
}
234+
235+
@available(SwiftStdlib 5.7, *)
236+
extension AnyRegexOutput.ElementRepresentation {
237+
fileprivate func value(forInput input: String) -> Any {
238+
// Ok for now because `existentialMatchComponent`
239+
// wont slice the input if there's no range to slice with
240+
//
241+
// FIXME: This is ugly :-/
242+
let input = bounds.map { input[$0] } ?? ""
243+
244+
return constructExistentialOutputComponent(
245+
from: input,
246+
in: bounds,
247+
value: nil,
248+
optionalCount: optionalDepth
249+
)
251250
}
252251
}

Sources/_StringProcessing/Regex/Match.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ extension Regex.Match {
3838

3939
let output = AnyRegexOutput(
4040
input: anyRegexOutput.input,
41-
_elements: [wholeMatchCapture] + anyRegexOutput._elements
41+
elements: [wholeMatchCapture] + anyRegexOutput._elements
4242
)
4343

4444
return output as! Output
@@ -77,7 +77,7 @@ extension Regex.Match {
7777
@_spi(RegexBuilder)
7878
public subscript<Capture>(_ id: ReferenceID) -> Capture {
7979
guard let element = anyRegexOutput.first(
80-
where: { $0.referenceID == id }
80+
where: { $0.representation.referenceID == id }
8181
) else {
8282
preconditionFailure("Reference did not capture any match in the regex")
8383
}

Tests/RegexTests/AnyRegexOutputTests.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ extension RegexTests {
103103

104104
// TODO: ARO init from concrete match tuple
105105

106-
let concreteOutputCasted = output.as(
107-
(Substring, fieldA: Substring, fieldB: Substring).self
106+
let concreteOutputCasted = output.extractValues(
107+
as: (Substring, fieldA: Substring, fieldB: Substring).self
108108
)!
109109
checkSame(output, concreteOutputCasted)
110110

111111
var concreteOutputCopy = concreteOutput
112-
concreteOutputCopy = output.as()!
112+
concreteOutputCopy = output.extractValues()!
113113
checkSame(output, concreteOutputCopy)
114114

115115
// TODO: Regex<ARO>.Match: init from tuple match and as to tuple match
@@ -146,12 +146,23 @@ extension RegexTests {
146146
XCTAssertTrue(output["upper"]?.substring == "A6F1")
147147
XCTAssertTrue(output[3].substring == "Extend")
148148
XCTAssertTrue(output["desc"]?.substring == "Extend")
149-
let typedOutput = try XCTUnwrap(output.as(
150-
(Substring, lower: Substring, upper: Substring?, Substring).self))
149+
let typedOutput = try XCTUnwrap(
150+
output.extractValues(
151+
as: (Substring, lower: Substring, upper: Substring?, Substring).self))
151152
XCTAssertEqual(typedOutput.0, line[...])
152153
XCTAssertTrue(typedOutput.lower == "A6F0")
153154
XCTAssertTrue(typedOutput.upper == "A6F1")
154155
XCTAssertTrue(typedOutput.3 == "Extend")
156+
157+
// Extracting as different argument labels is allowed
158+
let typedOutput2 = try XCTUnwrap(
159+
output.extractValues(
160+
as: (Substring, first: Substring, Substring?, third: Substring).self))
161+
XCTAssertEqual(typedOutput2.0, line[...])
162+
XCTAssertTrue(typedOutput2.first == "A6F0")
163+
XCTAssertTrue(typedOutput2.2 == "A6F1")
164+
XCTAssertTrue(typedOutput2.third == "Extend")
165+
155166
}
156167
}
157168
}

Tests/RegexTests/CaptureTests.swift

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ extension CaptureList {
5858

5959
extension AnyRegexOutput.Element {
6060
func formatStringCapture(input: String) -> String {
61-
var res = String(repeating: "some(", count: optionalDepth)
61+
var res = String(repeating: "some(", count: representation.optionalDepth)
6262
if let r = range {
6363
res += input[r]
6464
} else {
6565
res += "none"
6666
}
67-
res += String(repeating: ")", count: optionalDepth)
67+
res += String(repeating: ")", count: representation.optionalDepth)
6868
return res
6969
}
7070
}
@@ -122,7 +122,7 @@ extension StringCapture {
122122
to structCap: AnyRegexOutput.Element,
123123
in input: String
124124
) -> Bool {
125-
guard optionalCount == structCap.optionalDepth else {
125+
guard optionalCount == structCap.representation.optionalDepth else {
126126
return false
127127
}
128128
guard let r = structCap.range else {
@@ -462,29 +462,29 @@ extension RegexTests {
462462

463463
func testTypeVerification() throws {
464464
let opaque1 = try Regex("abc")
465-
_ = try XCTUnwrap(opaque1.as(Substring.self))
466-
XCTAssertNil(opaque1.as((Substring, Substring).self))
467-
XCTAssertNil(opaque1.as(Int.self))
465+
_ = try XCTUnwrap(Regex<Substring>(opaque1))
466+
XCTAssertNil(Regex<(Substring, Substring)>(opaque1))
467+
XCTAssertNil(Regex<Int>(opaque1))
468468

469469
let opaque2 = try Regex("(abc)")
470-
_ = try XCTUnwrap(opaque2.as((Substring, Substring).self))
471-
XCTAssertNil(opaque2.as(Substring.self))
472-
XCTAssertNil(opaque2.as((Substring, Int).self))
470+
_ = try XCTUnwrap(Regex<(Substring, Substring)>(opaque2))
471+
XCTAssertNil(Regex<Substring>(opaque2))
472+
XCTAssertNil(Regex<(Substring, Int)>(opaque2))
473473

474474
let opaque3 = try Regex("(?<someLabel>abc)")
475-
_ = try XCTUnwrap(opaque3.as((Substring, someLabel: Substring).self))
476-
XCTAssertNil(opaque3.as((Substring, Substring).self))
477-
XCTAssertNil(opaque3.as(Substring.self))
475+
_ = try XCTUnwrap(Regex<(Substring, someLabel: Substring)>(opaque3))
476+
XCTAssertNil(Regex<(Substring, Substring)>(opaque3))
477+
XCTAssertNil(Regex<Substring>(opaque3))
478478

479479
let opaque4 = try Regex("(?<somethingHere>abc)?")
480-
_ = try XCTUnwrap(opaque4.as((Substring, somethingHere: Substring?).self))
481-
XCTAssertNil(opaque4.as((Substring, somethignHere: Substring).self))
482-
XCTAssertNil(opaque4.as((Substring, Substring?).self))
480+
_ = try XCTUnwrap(Regex<(Substring, somethingHere: Substring?)>(opaque4))
481+
XCTAssertNil(Regex<(Substring, somethignHere: Substring)>(opaque4))
482+
XCTAssertNil(Regex<(Substring, Substring?)>(opaque4))
483483

484484
let opaque5 = try Regex("((a)?bc)?")
485-
_ = try XCTUnwrap(opaque5.as((Substring, Substring?, Substring??).self))
486-
XCTAssertNil(opaque5.as((Substring, somethingHere: Substring?, here: Substring??).self))
487-
XCTAssertNil(opaque5.as((Substring, Substring?, Substring?).self))
485+
_ = try XCTUnwrap(Regex<(Substring, Substring?, Substring??)>(opaque5))
486+
XCTAssertNil(Regex<(Substring, somethingHere: Substring?, here: Substring??)>(opaque5))
487+
XCTAssertNil(Regex<(Substring, Substring?, Substring?)>(opaque5))
488488
}
489489
}
490490

0 commit comments

Comments
 (0)