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 , * )
5616public 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 , * )
8222extension 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 , * )
13447extension 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 , * )
193110extension 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 , * )
202120extension 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 , * )
217149extension 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 , * )
227178extension 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