From 2f293c1a9492f61cc50d22902ed50d839c397eea Mon Sep 17 00:00:00 2001 From: Richard Wei Date: Wed, 11 May 2022 23:06:51 -0700 Subject: [PATCH] Introduce `One` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Sources/RegexBuilder/DSL.swift | 13 +++++++++++++ Tests/RegexBuilderTests/RegexDSLTests.swift | 18 +++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Sources/RegexBuilder/DSL.swift b/Sources/RegexBuilder/DSL.swift index 4020e2035..57ddf18e6 100644 --- a/Sources/RegexBuilder/DSL.swift +++ b/Sources/RegexBuilder/DSL.swift @@ -127,6 +127,19 @@ extension DSLTree.Node { } } +/// A regex component that matches exactly one occurrence of its underlying +/// component. +@available(SwiftStdlib 5.7, *) +public struct One: RegexComponent { + public var regex: Regex + + public init( + _ component: Component + ) where Component.RegexOutput == Output { + self.regex = component.regex + } +} + @available(SwiftStdlib 5.7, *) public struct OneOrMore: _BuiltinRegexComponent { public var regex: Regex diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index 10bc4ee35..b67570894 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -66,7 +66,7 @@ class RegexDSLTests: XCTestCase { ("a c", ("a c", " ", "c")), matchType: (Substring, Substring, Substring).self, ==) { - .any + One(.any) Capture(.whitespace) // Substring Capture("c") // Substring } @@ -344,7 +344,7 @@ class RegexDSLTests: XCTestCase { matchType: (Substring, Substring).self, ==) { OneOrMore(.reluctant) { - .word + One(.word) }.repetitionBehavior(.possessive) Capture(.digit) ZeroOrMore(.any) @@ -604,13 +604,13 @@ class RegexDSLTests: XCTestCase { func testUnicodeScalarPostProcessing() throws { let spaces = Regex { ZeroOrMore { - .whitespace + One(.whitespace) } } let unicodeScalar = Regex { OneOrMore { - .hexDigit + One(.hexDigit) } spaces } @@ -626,14 +626,10 @@ class RegexDSLTests: XCTestCase { spaces Capture { - OneOrMore { - .word - } + OneOrMore(.word) } - ZeroOrMore { - .any - } + ZeroOrMore(.any) } // Assert the inferred capture type. @@ -830,7 +826,7 @@ class RegexDSLTests: XCTestCase { let a = Reference(Substring.self) ChoiceOf<(Substring, Substring?)> { Regex { - .word + One(.word) a } Regex {