Skip to content

Commit f0a4590

Browse files
authored
Merge pull request #400 from hamishknight/main-merge
2 parents 7886514 + f37fc9b commit f0a4590

39 files changed

+2727
-649
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.DS_Store
22

3+
# The current toolchain is dumping files in the package root, rude
4+
*.emit-module.*
5+
36
# Xcode
47
#
58
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

Documentation/Evolution/ProposalOverview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
## Regex Type and Overview
55

6+
- [Second review](https://forums.swift.org/t/se-0350-second-review-regex-type-and-overview/56886)
67
- [Proposal](https://github.com/apple/swift-evolution/blob/main/proposals/0350-regex-type-overview.md), [Thread](https://forums.swift.org/t/se-0350-regex-type-and-overview/56530)
78
- [Pitch thread](https://forums.swift.org/t/pitch-regex-type-and-overview/56029)
89

Package.swift

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ let availabilityDefinition = PackageDescription.SwiftSetting.unsafeFlags([
1010
#"SwiftStdlib 5.7:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999"#,
1111
])
1212

13+
let stdlibSettings: [PackageDescription.SwiftSetting] = [
14+
.unsafeFlags(["-enable-library-evolution"]),
15+
.unsafeFlags(["-Xfrontend", "-disable-implicit-concurrency-module-import"]),
16+
.unsafeFlags(["-Xfrontend", "-disable-implicit-string-processing-module-import"]),
17+
availabilityDefinition
18+
]
19+
1320
let package = Package(
1421
name: "swift-experimental-string-processing",
1522
products: [
@@ -36,10 +43,7 @@ let package = Package(
3643
.target(
3744
name: "_RegexParser",
3845
dependencies: [],
39-
swiftSettings: [
40-
.unsafeFlags(["-enable-library-evolution"]),
41-
availabilityDefinition
42-
]),
46+
swiftSettings: stdlibSettings),
4347
.testTarget(
4448
name: "MatchingEngineTests",
4549
dependencies: [
@@ -51,29 +55,21 @@ let package = Package(
5155
.target(
5256
name: "_StringProcessing",
5357
dependencies: ["_RegexParser", "_CUnicode"],
54-
swiftSettings: [
55-
.unsafeFlags(["-enable-library-evolution"]),
56-
availabilityDefinition
57-
]),
58+
swiftSettings: stdlibSettings),
5859
.target(
5960
name: "RegexBuilder",
6061
dependencies: ["_StringProcessing", "_RegexParser"],
61-
swiftSettings: [
62-
.unsafeFlags(["-enable-library-evolution"]),
63-
.unsafeFlags(["-Xfrontend", "-enable-experimental-pairwise-build-block"]),
64-
availabilityDefinition
65-
]),
62+
swiftSettings: stdlibSettings),
6663
.testTarget(
6764
name: "RegexTests",
6865
dependencies: ["_StringProcessing"],
6966
swiftSettings: [
70-
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])
67+
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"]),
7168
]),
7269
.testTarget(
7370
name: "RegexBuilderTests",
7471
dependencies: ["_StringProcessing", "RegexBuilder"],
7572
swiftSettings: [
76-
.unsafeFlags(["-Xfrontend", "-enable-experimental-pairwise-build-block"]),
7773
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])
7874
]),
7975
.testTarget(
@@ -102,7 +98,6 @@ let package = Package(
10298
name: "Exercises",
10399
dependencies: ["_RegexParser", "_StringProcessing", "RegexBuilder"],
104100
swiftSettings: [
105-
.unsafeFlags(["-Xfrontend", "-enable-experimental-pairwise-build-block"]),
106101
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])
107102
]),
108103
.testTarget(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ See [Declarative String Processing Overview][decl-string]
88

99
## Requirements
1010

11-
- [Swift Trunk Development Snapshot](https://www.swift.org/download/#snapshots) DEVELOPMENT-SNAPSHOT-2022-03-09 or later.
11+
- [Swift Trunk Development Snapshot](https://www.swift.org/download/#snapshots) DEVELOPMENT-SNAPSHOT-2022-04-20 or later.
1212

1313
## Trying it out
1414

Sources/PatternConverter/PatternConverter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct PatternConverter: ParsableCommand {
5050
print("Converting '\(delim)\(regex)\(delim)'")
5151

5252
let ast = try _RegexParser.parse(
53-
regex,
53+
regex, .semantic,
5454
experimentalSyntax ? .experimental : .traditional)
5555

5656
// Show rendered source ranges

Sources/RegexBuilder/Anchor.swift

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
@_implementationOnly import _RegexParser
1313
@_spi(RegexBuilder) import _StringProcessing
1414

15+
/// A regex component that matches a specific condition at a particular position
16+
/// in an input string.
17+
///
18+
/// You can use anchors to guarantee that a match only occurs at certain points
19+
/// in an input string, such as at the beginning of the string or at the end of
20+
/// a line.
1521
@available(SwiftStdlib 5.7, *)
1622
public struct Anchor {
1723
internal enum Kind {
@@ -53,14 +59,24 @@ extension Anchor: RegexComponent {
5359

5460
@available(SwiftStdlib 5.7, *)
5561
extension Anchor {
62+
/// An anchor that matches at the start of the input string.
63+
///
64+
/// This anchor is equivalent to `\A` in regex syntax.
5665
public static var startOfSubject: Anchor {
5766
Anchor(kind: .startOfSubject)
5867
}
59-
68+
69+
/// An anchor that matches at the end of the input string or at the end of
70+
/// the line immediately before the the end of the string.
71+
///
72+
/// This anchor is equivalent to `\Z` in regex syntax.
6073
public static var endOfSubjectBeforeNewline: Anchor {
6174
Anchor(kind: .endOfSubjectBeforeNewline)
6275
}
63-
76+
77+
/// An anchor that matches at the end of the input string.
78+
///
79+
/// This anchor is equivalent to `\z` in regex syntax.
6480
public static var endOfSubject: Anchor {
6581
Anchor(kind: .endOfSubject)
6682
}
@@ -70,33 +86,67 @@ extension Anchor {
7086
// Anchor(kind: resetStartOfMatch)
7187
// }
7288

89+
/// An anchor that matches at the first position of a match in the input
90+
/// string.
7391
public static var firstMatchingPositionInSubject: Anchor {
7492
Anchor(kind: .firstMatchingPositionInSubject)
7593
}
7694

95+
/// An anchor that matches at a grapheme cluster boundary.
96+
///
97+
/// This anchor is equivalent to `\y` in regex syntax.
7798
public static var textSegmentBoundary: Anchor {
7899
Anchor(kind: .textSegmentBoundary)
79100
}
80101

102+
/// An anchor that matches at the start of a line, including the start of
103+
/// the input string.
104+
///
105+
/// This anchor is equivalent to `^` in regex syntax when the `m` option
106+
/// has been enabled or `anchorsMatchLineEndings(true)` has been called.
81107
public static var startOfLine: Anchor {
82108
Anchor(kind: .startOfLine)
83109
}
84110

111+
/// An anchor that matches at the end of a line, including at the end of
112+
/// the input string.
113+
///
114+
/// This anchor is equivalent to `$` in regex syntax when the `m` option
115+
/// has been enabled or `anchorsMatchLineEndings(true)` has been called.
85116
public static var endOfLine: Anchor {
86117
Anchor(kind: .endOfLine)
87118
}
88119

120+
/// An anchor that matches at a word boundary.
121+
///
122+
/// Word boundaries are identified using the Unicode default word boundary
123+
/// algorithm by default. To specify a different word boundary algorithm,
124+
/// see the `RegexComponent.wordBoundaryKind(_:)` method.
125+
///
126+
/// This anchor is equivalent to `\b` in regex syntax.
89127
public static var wordBoundary: Anchor {
90128
Anchor(kind: .wordBoundary)
91129
}
92130

131+
/// The inverse of this anchor, which matches at every position that this
132+
/// anchor does not.
133+
///
134+
/// For the `wordBoundary` and `textSegmentBoundary` anchors, the inverted
135+
/// version corresponds to `\B` and `\Y`, respectively.
93136
public var inverted: Anchor {
94137
var result = self
95138
result.isInverted.toggle()
96139
return result
97140
}
98141
}
99142

143+
/// A regex component that allows a match to continue only if its contents
144+
/// match at the given location.
145+
///
146+
/// A lookahead is a zero-length assertion that its included regex matches at
147+
/// a particular position. Lookaheads do not advance the overall matching
148+
/// position in the input string — once a lookahead succeeds, matching continues
149+
/// in the regex from the same position.
100150
@available(SwiftStdlib 5.7, *)
101151
public struct Lookahead<Output>: _BuiltinRegexComponent {
102152
public var regex: Regex<Output>
@@ -105,19 +155,48 @@ public struct Lookahead<Output>: _BuiltinRegexComponent {
105155
self.regex = regex
106156
}
107157

158+
/// Creates a lookahead from the given regex component.
108159
public init<R: RegexComponent>(
109-
_ component: R,
110-
negative: Bool = false
160+
_ component: R
111161
) where R.RegexOutput == Output {
112-
self.init(node: .nonCapturingGroup(
113-
negative ? .negativeLookahead : .lookahead, component.regex.root))
162+
self.init(node: .nonCapturingGroup(.lookahead, component.regex.root))
114163
}
164+
165+
/// Creates a lookahead from the regex generated by the given builder closure.
166+
public init<R: RegexComponent>(
167+
@RegexComponentBuilder _ component: () -> R
168+
) where R.RegexOutput == Output {
169+
self.init(node: .nonCapturingGroup(.lookahead, component().regex.root))
170+
}
171+
}
115172

173+
/// A regex component that allows a match to continue only if its contents
174+
/// do not match at the given location.
175+
///
176+
/// A negative lookahead is a zero-length assertion that its included regex
177+
/// does not match at a particular position. Lookaheads do not advance the
178+
/// overall matching position in the input string — once a lookahead succeeds,
179+
/// matching continues in the regex from the same position.
180+
@available(SwiftStdlib 5.7, *)
181+
public struct NegativeLookahead<Output>: _BuiltinRegexComponent {
182+
public var regex: Regex<Output>
183+
184+
init(_ regex: Regex<Output>) {
185+
self.regex = regex
186+
}
187+
188+
/// Creates a negative lookahead from the given regex component.
189+
public init<R: RegexComponent>(
190+
_ component: R
191+
) where R.RegexOutput == Output {
192+
self.init(node: .nonCapturingGroup(.negativeLookahead, component.regex.root))
193+
}
194+
195+
/// Creates a negative lookahead from the regex generated by the given builder
196+
/// closure.
116197
public init<R: RegexComponent>(
117-
negative: Bool = false,
118198
@RegexComponentBuilder _ component: () -> R
119199
) where R.RegexOutput == Output {
120-
self.init(node: .nonCapturingGroup(
121-
negative ? .negativeLookahead : .lookahead, component().regex.root))
200+
self.init(node: .nonCapturingGroup(.negativeLookahead, component().regex.root))
122201
}
123202
}

Sources/_RegexParser/Regex/AST/AST.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ extension AST.Node {
125125
switch self {
126126
case .atom(let a):
127127
return a.isQuantifiable
128-
case .group, .conditional, .customCharacterClass, .absentFunction:
128+
case .group(let g):
129+
return g.isQuantifiable
130+
case .conditional, .customCharacterClass, .absentFunction:
129131
return true
130132
case .alternation, .concatenation, .quantification, .quote, .trivia,
131133
.empty:

0 commit comments

Comments
 (0)