Skip to content

Commit 301d177

Browse files
committed
Use inits instead of as methods (swiftlang#448)
Use inits instead of as methods Add ARO tests
1 parent 802e07f commit 301d177

File tree

7 files changed

+444
-167
lines changed

7 files changed

+444
-167
lines changed

Sources/RegexBuilder/DSL.swift

+2-6
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,14 @@ extension _BuiltinRegexComponent {
4141
extension String: RegexComponent {
4242
public typealias Output = Substring
4343

44-
public var regex: Regex<Output> {
45-
.init(node: .quotedLiteral(self))
46-
}
44+
public var regex: Regex<Output> { .init(verbatim: self) }
4745
}
4846

4947
@available(SwiftStdlib 5.7, *)
5048
extension Substring: RegexComponent {
5149
public typealias Output = Substring
5250

53-
public var regex: Regex<Output> {
54-
.init(node: .quotedLiteral(String(self)))
55-
}
51+
public var regex: Regex<Output> { String(self).regex }
5652
}
5753

5854
@available(SwiftStdlib 5.7, *)

Sources/_StringProcessing/Capture.swift

+1-1
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

+149-135
Original file line numberDiff line numberDiff line change
@@ -11,157 +11,74 @@
1111

1212
@_implementationOnly import _RegexParser
1313

14-
@available(SwiftStdlib 5.7, *)
15-
extension Regex where Output == AnyRegexOutput {
16-
/// Parses and compiles a regular expression, resulting in an existentially-typed capture list.
17-
///
18-
/// - Parameter pattern: The regular expression.
19-
public init(_ pattern: String) throws {
20-
self.init(ast: try parse(pattern, .semantic, .traditional))
21-
}
22-
}
23-
24-
@available(SwiftStdlib 5.7, *)
25-
extension Regex {
26-
/// Parses and compiles a regular expression.
27-
///
28-
/// - Parameter pattern: The regular expression.
29-
/// - Parameter as: The desired type for the output.
30-
public init(
31-
_ pattern: String,
32-
as: Output.Type = Output.self
33-
) throws {
34-
self.init(ast: try parse(pattern, .semantic, .traditional))
35-
}
36-
}
37-
38-
@available(SwiftStdlib 5.7, *)
39-
extension Regex.Match where Output == AnyRegexOutput {
40-
/// Accesses the whole match using the `.0` syntax.
41-
public subscript(
42-
dynamicMember keyPath: KeyPath<(Substring, _doNotUse: ()), Substring>
43-
) -> Substring {
44-
anyRegexOutput.input[range]
45-
}
46-
47-
public subscript(name: String) -> AnyRegexOutput.Element? {
48-
anyRegexOutput.first {
49-
$0.name == name
50-
}
51-
}
52-
}
53-
5414
/// A type-erased regex output.
5515
@available(SwiftStdlib 5.7, *)
5616
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-
}
17+
internal let input: String
18+
internal let _elements: [ElementRepresentation]
7919
}
8020

8121
@available(SwiftStdlib 5.7, *)
8222
extension AnyRegexOutput {
83-
/// Creates a type-erased regex output from an existing output.
23+
/// Creates a type-erased regex output from an existing match.
8424
///
85-
/// Use this initializer to fit a regex with strongly typed captures into the
86-
/// use site of a dynamic regex, like one that was created from a string.
25+
/// Use this initializer to fit a strongly-typed regex match into the
26+
/// use site of a type-erased regex output.
8727
public init<Output>(_ match: Regex<Output>.Match) {
8828
self = match.anyRegexOutput
8929
}
9030

91-
/// Returns a typed output by converting the underlying value to the specified
92-
/// type.
31+
/// Returns a strongly-typed output by converting type-erased values to the specified type.
9332
///
9433
/// - Parameter type: The expected output type.
9534
/// - Returns: The output, if the underlying value can be converted to the
9635
/// output type; otherwise `nil`.
97-
public func `as`<Output>(_ type: Output.Type = Output.self) -> Output? {
36+
public func extractValues<Output>(
37+
as type: Output.Type = Output.self
38+
) -> Output? {
9839
let elements = map {
9940
$0.existentialOutputComponent(from: input[...])
10041
}
10142
return TypeConstruction.tuple(of: elements) as? Output
10243
}
10344
}
10445

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-
13346
@available(SwiftStdlib 5.7, *)
13447
extension AnyRegexOutput: RandomAccessCollection {
48+
/// An individual type-erased output value.
13549
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-
}
146-
50+
internal let representation: ElementRepresentation
51+
internal let input: String
52+
14753
/// The range over which a value was captured. `nil` for no-capture.
14854
public var range: Range<String.Index>? {
14955
representation.bounds
15056
}
151-
152-
var referenceID: ReferenceID? {
153-
representation.referenceID
154-
}
155-
57+
15658
/// The slice of the input over which a value was captured. `nil` for no-capture.
15759
public var substring: Substring? {
15860
range.map { input[$0] }
15961
}
16062

16163
/// The captured value, `nil` for no-capture
16264
public var value: Any? {
65+
// FIXME: Should this return the substring for default-typed
66+
// values?
16367
representation.value
16468
}
69+
70+
/// The name of this capture, if it has one, otherwise `nil`.
71+
public var name: String? {
72+
representation.name
73+
}
74+
75+
// TODO: Consider making API, and figure out how
76+
// DSL and this would work together...
77+
/// Whether this capture is considered optional by the regex. I.e.,
78+
/// whether it is inside an alternation or zero-or-n quantification.
79+
var isOptional: Bool {
80+
representation.optionalDepth != 0
81+
}
16582
}
16683

16784
public var startIndex: Int {
@@ -191,6 +108,7 @@ extension AnyRegexOutput: RandomAccessCollection {
191108

192109
@available(SwiftStdlib 5.7, *)
193110
extension AnyRegexOutput {
111+
/// Access a capture by name. Returns `nil` if no capture with that name was present in the Regex.
194112
public subscript(name: String) -> Element? {
195113
first {
196114
$0.name == name
@@ -200,21 +118,52 @@ extension AnyRegexOutput {
200118

201119
@available(SwiftStdlib 5.7, *)
202120
extension Regex.Match where Output == AnyRegexOutput {
203-
/// Creates a type-erased regex match from an existing match.
121+
/// Accesses the whole match using the `.0` syntax.
122+
public subscript(
123+
dynamicMember keyPath: KeyPath<(Substring, _doNotUse: ()), Substring>
124+
) -> Substring {
125+
anyRegexOutput.input[range]
126+
}
127+
128+
/// Access a capture by name. Returns `nil` if there's no capture with that name.
129+
public subscript(name: String) -> AnyRegexOutput.Element? {
130+
anyRegexOutput.first {
131+
$0.name == name
132+
}
133+
}
134+
}
135+
136+
// MARK: - Run-time regex creation and queries
137+
138+
@available(SwiftStdlib 5.7, *)
139+
extension Regex where Output == AnyRegexOutput {
140+
/// Parses and compiles a regular expression, resulting in a type-erased capture list.
204141
///
205-
/// Use this initializer to fit a regex match with strongly typed captures into the
206-
/// use site of a dynamic regex match, like one that was created from a string.
207-
public init<Output>(_ match: Regex<Output>.Match) {
208-
self.init(
209-
anyRegexOutput: match.anyRegexOutput,
210-
range: match.range,
211-
value: match.value
212-
)
142+
/// - Parameter pattern: The regular expression.
143+
public init(_ pattern: String) throws {
144+
self.init(ast: try parse(pattern, .semantic, .traditional))
213145
}
214146
}
215147

216148
@available(SwiftStdlib 5.7, *)
217149
extension Regex {
150+
/// Parses and compiles a regular expression.
151+
///
152+
/// - Parameter pattern: The regular expression.
153+
/// - Parameter as: The desired type for the output.
154+
public init(
155+
_ pattern: String,
156+
as: Output.Type = Output.self
157+
) throws {
158+
self.init(ast: try parse(pattern, .semantic, .traditional))
159+
}
160+
161+
/// Produces a regex that matches `verbatim` exactly, as though every
162+
/// metacharacter in it was escaped.
163+
public init(verbatim: String) {
164+
self.init(node: .quotedLiteral(verbatim))
165+
}
166+
218167
/// Returns whether a named-capture with `name` exists
219168
public func contains(captureNamed name: String) -> Bool {
220169
program.tree.root._captureList.captures.contains(where: {
@@ -223,30 +172,95 @@ extension Regex {
223172
}
224173
}
225174

175+
// MARK: - Converting to/from ARO
176+
226177
@available(SwiftStdlib 5.7, *)
227178
extension Regex where Output == AnyRegexOutput {
228179
/// Creates a type-erased regex from an existing regex.
229180
///
230-
/// Use this initializer to fit a regex with strongly typed captures into the
231-
/// use site of a dynamic regex, i.e. one that was created from a string.
181+
/// Use this initializer to fit a regex with strongly-typed captures into the
182+
/// use site of a type-erased regex, i.e. one that was created from a string.
232183
public init<Output>(_ regex: Regex<Output>) {
233184
self.init(node: regex.root)
234185
}
186+
}
235187

236-
/// Returns a typed regex by converting the underlying types.
188+
@available(SwiftStdlib 5.7, *)
189+
extension Regex.Match where Output == AnyRegexOutput {
190+
/// Creates a type-erased regex match from an existing match.
237191
///
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 {
192+
/// Use this initializer to fit a regex match with strongly-typed captures into the
193+
/// use site of a type-erased regex match.
194+
public init<Output>(_ match: Regex<Output>.Match) {
195+
self.init(
196+
anyRegexOutput: match.anyRegexOutput,
197+
range: match.range,
198+
value: match.value
199+
)
200+
}
201+
}
202+
203+
@available(SwiftStdlib 5.7, *)
204+
extension Regex {
205+
/// Creates a strongly-typed regex from a type-erased regex.
206+
///
207+
/// Use this initializer to create a strongly-typed regex from
208+
/// one that was created from a string. Returns `nil` if the types
209+
/// don't match.
210+
public init?(
211+
_ erased: Regex<AnyRegexOutput>,
212+
as: Output.Type = Output.self
213+
) {
214+
self.init(node: erased.root)
215+
guard self._verifyType() else {
247216
return nil
248217
}
249-
250-
return result
218+
}
219+
}
220+
221+
// MARK: - Internals
222+
223+
@available(SwiftStdlib 5.7, *)
224+
extension AnyRegexOutput {
225+
/// The underlying representation of the element of a type-erased regex
226+
/// output.
227+
internal struct ElementRepresentation {
228+
/// The depth of `Optioals`s wrapping the underlying value. For example,
229+
/// `Substring` has optional depth `0`, and `Int??` has optional depth `2`.
230+
let optionalDepth: Int
231+
232+
/// The bounds of the output element.
233+
let bounds: Range<String.Index>?
234+
235+
/// The name of the capture.
236+
var name: String? = nil
237+
238+
/// The capture reference this element refers to.
239+
var referenceID: ReferenceID? = nil
240+
241+
/// If the output vaule is strongly typed, then this will be set.
242+
var value: Any? = nil
243+
}
244+
245+
internal init(input: String, elements: [ElementRepresentation]) {
246+
self.init(input: input, _elements: elements)
247+
}
248+
}
249+
250+
@available(SwiftStdlib 5.7, *)
251+
extension AnyRegexOutput.ElementRepresentation {
252+
fileprivate func value(forInput input: String) -> Any {
253+
// Ok for now because `existentialMatchComponent`
254+
// wont slice the input if there's no range to slice with
255+
//
256+
// FIXME: This is ugly :-/
257+
let input = bounds.map { input[$0] } ?? ""
258+
259+
return constructExistentialOutputComponent(
260+
from: input,
261+
in: bounds,
262+
value: nil,
263+
optionalCount: optionalDepth
264+
)
251265
}
252266
}

0 commit comments

Comments
 (0)