Skip to content

Commit e748aea

Browse files
authored
Add NegativeLookahead and Anchor comments (#372)
1 parent 838bdfe commit e748aea

File tree

2 files changed

+90
-11
lines changed

2 files changed

+90
-11
lines changed

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
}

Tests/RegexBuilderTests/RegexDSLTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class RegexDSLTests: XCTestCase {
115115
{
116116
let disallowedChars = CharacterClass.hexDigit
117117
.symmetricDifference("a"..."z")
118-
Lookahead(disallowedChars, negative: true) // No: 0-9 + g-z
118+
NegativeLookahead(disallowedChars) // No: 0-9 + g-z
119119

120120
OneOrMore(("b"..."g").union("d"..."n")) // b-n
121121

@@ -487,7 +487,7 @@ class RegexDSLTests: XCTestCase {
487487
{
488488
OneOrMore("a")
489489
Lookahead(CharacterClass.digit)
490-
Lookahead("2", negative: true)
490+
NegativeLookahead { "2" }
491491
CharacterClass.word
492492
}
493493
}

0 commit comments

Comments
 (0)