Skip to content

Commit 2f293c1

Browse files
committed
Introduce One
One is a lightweight component that allows the use of the leading dot syntax to reference `RegexComponent` static members such as character classes as a non-first expression in a regex builder block. --- Before: ```swift Regex { .digit // works today but brittle; inserting anything above this line will break this OneOrMore(.whitespace) .word // ❌ error: 'OneOrMore' has no member named 'word' (because this is parsed as a member reference on the preceeding expression) } ``` After: ```swift Regex { One(.digit) // recommended even though `.digit` works today OneOrMore(.whitespace) One(.word) } // ✅ ``` In a follow-up patch, we will propose adding an additional protocol inheriting from `RegexComponent` that will ban the use of the leading dot syntax even on the first line of `Regex { ... }`, as this will enforce the recommended style (use of `One`), and prevent surprises when the user inserts a pattern above the leading dot line.
1 parent 08536cb commit 2f293c1

File tree

2 files changed

+20
-11
lines changed

2 files changed

+20
-11
lines changed

Sources/RegexBuilder/DSL.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,19 @@ extension DSLTree.Node {
127127
}
128128
}
129129

130+
/// A regex component that matches exactly one occurrence of its underlying
131+
/// component.
132+
@available(SwiftStdlib 5.7, *)
133+
public struct One<Output>: RegexComponent {
134+
public var regex: Regex<Output>
135+
136+
public init<Component: RegexComponent>(
137+
_ component: Component
138+
) where Component.RegexOutput == Output {
139+
self.regex = component.regex
140+
}
141+
}
142+
130143
@available(SwiftStdlib 5.7, *)
131144
public struct OneOrMore<Output>: _BuiltinRegexComponent {
132145
public var regex: Regex<Output>

Tests/RegexBuilderTests/RegexDSLTests.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class RegexDSLTests: XCTestCase {
6666
("a c", ("a c", " ", "c")),
6767
matchType: (Substring, Substring, Substring).self, ==)
6868
{
69-
.any
69+
One(.any)
7070
Capture(.whitespace) // Substring
7171
Capture("c") // Substring
7272
}
@@ -344,7 +344,7 @@ class RegexDSLTests: XCTestCase {
344344
matchType: (Substring, Substring).self, ==)
345345
{
346346
OneOrMore(.reluctant) {
347-
.word
347+
One(.word)
348348
}.repetitionBehavior(.possessive)
349349
Capture(.digit)
350350
ZeroOrMore(.any)
@@ -604,13 +604,13 @@ class RegexDSLTests: XCTestCase {
604604
func testUnicodeScalarPostProcessing() throws {
605605
let spaces = Regex {
606606
ZeroOrMore {
607-
.whitespace
607+
One(.whitespace)
608608
}
609609
}
610610

611611
let unicodeScalar = Regex {
612612
OneOrMore {
613-
.hexDigit
613+
One(.hexDigit)
614614
}
615615
spaces
616616
}
@@ -626,14 +626,10 @@ class RegexDSLTests: XCTestCase {
626626
spaces
627627

628628
Capture {
629-
OneOrMore {
630-
.word
631-
}
629+
OneOrMore(.word)
632630
}
633631

634-
ZeroOrMore {
635-
.any
636-
}
632+
ZeroOrMore(.any)
637633
}
638634

639635
// Assert the inferred capture type.
@@ -830,7 +826,7 @@ class RegexDSLTests: XCTestCase {
830826
let a = Reference(Substring.self)
831827
ChoiceOf<(Substring, Substring?)> {
832828
Regex {
833-
.word
829+
One(.word)
834830
a
835831
}
836832
Regex {

0 commit comments

Comments
 (0)