diff --git a/Package.swift b/Package.swift index 526d1d0e1..6304cc76d 100644 --- a/Package.swift +++ b/Package.swift @@ -100,6 +100,13 @@ let package = Package( "_RegexParser", "_StringProcessing" ]), + .executableTarget( + name: "RegexTester", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + "_RegexParser", + "_StringProcessing" + ]), // MARK: Exercises .target( diff --git a/Sources/RegexBuilder/Algorithms.swift b/Sources/RegexBuilder/Algorithms.swift index 88916879b..f809e85f5 100644 --- a/Sources/RegexBuilder/Algorithms.swift +++ b/Sources/RegexBuilder/Algorithms.swift @@ -23,9 +23,9 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Parameter content: A closure that returns a regex to match against. /// - Returns: The match if there is one, or `nil` if none. @available(SwiftStdlib 5.7, *) - public func wholeMatch( - @RegexComponentBuilder of content: () -> R - ) -> Regex.Match? { + public func wholeMatch( + @RegexComponentBuilder of content: () -> some RegexComponent + ) -> Regex.Match? { wholeMatch(of: content()) } @@ -35,9 +35,9 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Parameter content: A closure that returns a regex to match against. /// - Returns: The match if there is one, or `nil` if none. @available(SwiftStdlib 5.7, *) - public func prefixMatch( - @RegexComponentBuilder of content: () -> R - ) -> Regex.Match? { + public func prefixMatch( + @RegexComponentBuilder of content: () -> some RegexComponent + ) -> Regex.Match? { prefixMatch(of: content()) } @@ -49,8 +49,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: `true` if the regex returned by `content` matched anywhere in /// this collection, otherwise `false`. @available(SwiftStdlib 5.7, *) - public func contains( - @RegexComponentBuilder _ content: () -> R + public func contains( + @RegexComponentBuilder _ content: () -> some RegexComponent ) -> Bool { contains(content()) } @@ -63,8 +63,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// match of if the regex returned by `content`. Returns `nil` if no match /// for the regex is found. @available(SwiftStdlib 5.7, *) - public func firstRange( - @RegexComponentBuilder of content: () -> R + public func firstRange( + @RegexComponentBuilder of content: () -> some RegexComponent ) -> Range? { firstRange(of: content()) } @@ -78,8 +78,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// `content`. Returns an empty collection if no match for the regex /// is found. @available(SwiftStdlib 5.7, *) - public func ranges( - @RegexComponentBuilder of content: () -> R + public func ranges( + @RegexComponentBuilder of content: () -> some RegexComponent ) -> [Range] { ranges(of: content()) } @@ -99,10 +99,10 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: A collection of substrings, split from this collection's /// elements. @available(SwiftStdlib 5.7, *) - public func split( + public func split( maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true, - @RegexComponentBuilder separator: () -> R + @RegexComponentBuilder separator: () -> some RegexComponent ) -> [SubSequence] { split(separator: separator(), maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) } @@ -115,8 +115,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: `true` if the initial elements of this collection match /// regex returned by `content`; otherwise, `false`. @available(SwiftStdlib 5.7, *) - public func starts( - @RegexComponentBuilder with content: () -> R + public func starts( + @RegexComponentBuilder with content: () -> some RegexComponent ) -> Bool { starts(with: content()) } @@ -132,8 +132,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// the start of the collection, the entire contents of this collection /// are returned. @available(SwiftStdlib 5.7, *) - public func trimmingPrefix( - @RegexComponentBuilder _ content: () -> R + public func trimmingPrefix( + @RegexComponentBuilder _ content: () -> some RegexComponent ) -> SubSequence { trimmingPrefix(content()) } @@ -145,9 +145,9 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: The first match for the regex created by `content` in this /// collection, or `nil` if no match is found. @available(SwiftStdlib 5.7, *) - public func firstMatch( - @RegexComponentBuilder of content: () -> R - ) -> Regex.Match? { + public func firstMatch( + @RegexComponentBuilder of content: () -> some RegexComponent + ) -> Regex.Match? { firstMatch(of: content()) } @@ -159,9 +159,9 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: A collection of matches for the regex returned by `content`. /// If no matches are found, the returned collection is empty. @available(SwiftStdlib 5.7, *) - public func matches( - @RegexComponentBuilder of content: () -> R - ) -> [Regex.Match] { + public func matches( + @RegexComponentBuilder of content: () -> some RegexComponent + ) -> [Regex.Match] { matches(of: content()) } } @@ -175,8 +175,8 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// - Parameter content: A closure that returns the regex to search for /// at the start of this collection. @available(SwiftStdlib 5.7, *) - public mutating func trimPrefix( - @RegexComponentBuilder _ content: () -> R + public mutating func trimPrefix( + @RegexComponentBuilder _ content: () -> some RegexComponent ) { trimPrefix(content()) } @@ -196,11 +196,11 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// - Returns: A new collection in which all matches for regex in `subrange` /// are replaced by `replacement`, using `content` to create the regex. @available(SwiftStdlib 5.7, *) - public func replacing( + public func replacing( with replacement: Replacement, subrange: Range, maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R + @RegexComponentBuilder content: () -> some RegexComponent ) -> Self where Replacement.Element == Element { replacing(content(), with: replacement, subrange: subrange, maxReplacements: maxReplacements) } @@ -218,10 +218,10 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// - Returns: A new collection in which all matches for regex in `subrange` /// are replaced by `replacement`, using `content` to create the regex. @available(SwiftStdlib 5.7, *) - public func replacing( + public func replacing( with replacement: Replacement, maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R + @RegexComponentBuilder content: () -> some RegexComponent ) -> Self where Replacement.Element == Element { replacing(content(), with: replacement, maxReplacements: maxReplacements) } @@ -237,10 +237,10 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// - content: A closure that returns the collection to search for /// and replace. @available(SwiftStdlib 5.7, *) - public mutating func replace( + public mutating func replace( with replacement: Replacement, maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R + @RegexComponentBuilder content: () -> some RegexComponent ) where Replacement.Element == Element { replace(content(), with: replacement, maxReplacements: maxReplacements) } @@ -262,11 +262,11 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// are replaced by the result of calling `replacement`, where regex /// is the result of calling `content`. @available(SwiftStdlib 5.7, *) - public func replacing( + public func replacing( subrange: Range, maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R, - with replacement: (Regex.Match) throws -> Replacement + @RegexComponentBuilder content: () -> some RegexComponent, + with replacement: (Regex.Match) throws -> Replacement ) rethrows -> Self where Replacement.Element == Element { try replacing(content(), subrange: subrange, maxReplacements: maxReplacements, with: replacement) } @@ -286,10 +286,10 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// are replaced by the result of calling `replacement`, where regex is /// the result of calling `content`. @available(SwiftStdlib 5.7, *) - public func replacing( + public func replacing( maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R, - with replacement: (Regex.Match) throws -> Replacement + @RegexComponentBuilder content: () -> some RegexComponent, + with replacement: (Regex.Match) throws -> Replacement ) rethrows -> Self where Replacement.Element == Element { try replacing(content(), maxReplacements: maxReplacements, with: replacement) } @@ -305,10 +305,10 @@ where Self: BidirectionalCollection, SubSequence == Substring { /// - replacement: A closure that receives the full match information, /// including captures, and returns a replacement collection. @available(SwiftStdlib 5.7, *) - public mutating func replace( + public mutating func replace( maxReplacements: Int = .max, - @RegexComponentBuilder content: () -> R, - with replacement: (Regex.Match) throws -> Replacement + @RegexComponentBuilder content: () -> some RegexComponent, + with replacement: (Regex.Match) throws -> Replacement ) rethrows where Replacement.Element == Element { try replace(content(), maxReplacements: maxReplacements, with: replacement) } diff --git a/Sources/RegexTester/RegexTester.swift b/Sources/RegexTester/RegexTester.swift new file mode 100644 index 000000000..970e47160 --- /dev/null +++ b/Sources/RegexTester/RegexTester.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import _RegexParser +import _StringProcessing + +@main +@available(macOS 9999, *) +struct RegexTester: ParsableCommand { + typealias MatchFunctionType = (String) throws -> Regex.Match? + + @Argument(help: "The regex pattern to test.") + var pattern: String + + @Argument(help: "One or more input strings to test against .") + var inputs: [String] + + @Flag( + name: [.customShort("p"), .customLong("partial")], + help: "Allow partial matches.") + var allowPartialMatch: Bool = false + + mutating func run() throws { + print("Using pattern \(pattern.halfWidthCornerQuoted)") + let regex = try Regex(pattern) + + for input in inputs { + print("Input \(input.halfWidthCornerQuoted)") + + let matchFunc: MatchFunctionType = allowPartialMatch + ? regex.firstMatch(in:) + : regex.wholeMatch(in:) + + if let result = try matchFunc(input) { + print(" matched: \(result.0.halfWidthCornerQuoted)") + } else { + print(" no match") + } + } + } +} diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift b/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift index 020ea8208..e481597f8 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift @@ -73,7 +73,7 @@ extension BidirectionalCollection where SubSequence == Substring { /// `false`. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public func contains(_ regex: R) -> Bool { + public func contains(_ regex: some RegexComponent) -> Bool { _contains(RegexConsumer(regex)) } } diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift b/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift index 30c2fac92..696f36eca 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift @@ -77,7 +77,7 @@ extension BidirectionalCollection where SubSequence == Substring { /// Returns `nil` if `regex` is not found. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public func firstRange(of regex: R) -> Range? { + public func firstRange(of regex: some RegexComponent) -> Range? { _firstRange(of: RegexConsumer(regex)) } diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift b/Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift index 578c499d1..36285d7cc 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift @@ -253,8 +253,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// `regex`. Returns an empty collection if `regex` is not found. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public func ranges( - of regex: R + public func ranges( + of regex: some RegexComponent ) -> [Range] { Array(_ranges(of: regex)) } diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift b/Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift index 5c2bc035f..ccc0962d5 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift @@ -188,8 +188,8 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// - Returns: A new collection in which all occurrences of subsequence /// matching `regex` in `subrange` are replaced by `replacement`. @available(SwiftStdlib 5.7, *) - public func replacing( - _ regex: R, + public func replacing( + _ regex: some RegexComponent, with replacement: Replacement, subrange: Range, maxReplacements: Int = .max @@ -212,8 +212,8 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// matching `regex` are replaced by `replacement`. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public func replacing( - _ regex: R, + public func replacing( + _ regex: some RegexComponent, with replacement: Replacement, maxReplacements: Int = .max ) -> Self where Replacement.Element == Element { @@ -232,8 +232,8 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// - maxReplacements: A number specifying how many occurrences of the /// sequence matching `regex` to replace. Default is `Int.max`. @available(SwiftStdlib 5.7, *) - public mutating func replace( - _ regex: R, + public mutating func replace( + _ regex: some RegexComponent, with replacement: Replacement, maxReplacements: Int = .max ) where Replacement.Element == Element { diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/Split.swift b/Sources/_StringProcessing/Algorithms/Algorithms/Split.swift index 16cc47c39..44364df71 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/Split.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/Split.swift @@ -426,8 +426,8 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: A collection of substrings, split from this collection's /// elements. @_disfavoredOverload - public func split( - separator: R, + public func split( + separator: some RegexComponent, maxSplits: Int = .max, omittingEmptySubsequences: Bool = true ) -> [SubSequence] { diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/StartsWith.swift b/Sources/_StringProcessing/Algorithms/Algorithms/StartsWith.swift index c8aaf9126..694651f3a 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/StartsWith.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/StartsWith.swift @@ -55,7 +55,7 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Parameter regex: A regex to compare to this sequence. /// - Returns: `true` if the initial elements of the sequence matches the /// beginning of `regex`; otherwise, `false`. - public func starts(with regex: R) -> Bool { + public func starts(with regex: some RegexComponent) -> Bool { _starts(with: RegexConsumer(regex)) } diff --git a/Sources/_StringProcessing/Algorithms/Algorithms/Trim.swift b/Sources/_StringProcessing/Algorithms/Algorithms/Trim.swift index 16a3cb207..37f7513db 100644 --- a/Sources/_StringProcessing/Algorithms/Algorithms/Trim.swift +++ b/Sources/_StringProcessing/Algorithms/Algorithms/Trim.swift @@ -291,7 +291,7 @@ extension BidirectionalCollection where SubSequence == Substring { /// `prefix` from the start. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public func trimmingPrefix(_ regex: R) -> SubSequence { + public func trimmingPrefix(_ regex: some RegexComponent) -> SubSequence { _trimmingPrefix(RegexConsumer(regex)) } @@ -313,7 +313,7 @@ extension RangeReplaceableCollection /// - Parameter regex: The regex to remove from this collection. @_disfavoredOverload @available(SwiftStdlib 5.7, *) - public mutating func trimPrefix(_ regex: R) { + public mutating func trimPrefix(_ regex: some RegexComponent) { _trimPrefix(RegexConsumer(regex)) } diff --git a/Sources/_StringProcessing/Algorithms/Matching/FirstMatch.swift b/Sources/_StringProcessing/Algorithms/Matching/FirstMatch.swift index 2b6dd1704..c8c6867f4 100644 --- a/Sources/_StringProcessing/Algorithms/Matching/FirstMatch.swift +++ b/Sources/_StringProcessing/Algorithms/Matching/FirstMatch.swift @@ -58,9 +58,9 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Returns: The first match of `regex` in the collection, or `nil` if /// there isn't a match. @available(SwiftStdlib 5.7, *) - public func firstMatch( - of r: R - ) -> Regex.Match? { + public func firstMatch( + of r: some RegexComponent + ) -> Regex.Match? { let slice = self[...] return try? r.regex.firstMatch(in: slice) } diff --git a/Sources/_StringProcessing/Algorithms/Matching/MatchReplace.swift b/Sources/_StringProcessing/Algorithms/Matching/MatchReplace.swift index 38224e30f..6bc9b4d77 100644 --- a/Sources/_StringProcessing/Algorithms/Matching/MatchReplace.swift +++ b/Sources/_StringProcessing/Algorithms/Matching/MatchReplace.swift @@ -126,11 +126,11 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// - Returns: A new collection in which all occurrences of subsequence /// matching `regex` are replaced by `replacement`. @available(SwiftStdlib 5.7, *) - public func replacing( - _ regex: R, + public func replacing( + _ regex: some RegexComponent, subrange: Range, maxReplacements: Int = .max, - with replacement: (Regex.Match) throws -> Replacement + with replacement: (Regex.Match) throws -> Replacement ) rethrows -> Self where Replacement.Element == Element { precondition(maxReplacements >= 0) @@ -162,10 +162,10 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// - Returns: A new collection in which all occurrences of subsequence /// matching `regex` are replaced by `replacement`. @available(SwiftStdlib 5.7, *) - public func replacing( - _ regex: R, + public func replacing( + _ regex: some RegexComponent, maxReplacements: Int = .max, - with replacement: (Regex.Match) throws -> Replacement + with replacement: (Regex.Match) throws -> Replacement ) rethrows -> Self where Replacement.Element == Element { try replacing( regex, @@ -183,10 +183,10 @@ extension RangeReplaceableCollection where SubSequence == Substring { /// - replacement: A closure that receives the full match information, /// including captures, and returns a replacement collection. @available(SwiftStdlib 5.7, *) - public mutating func replace( - _ regex: R, + public mutating func replace( + _ regex: some RegexComponent, maxReplacements: Int = .max, - with replacement: (Regex.Match) throws -> Replacement + with replacement: (Regex.Match) throws -> Replacement ) rethrows where Replacement.Element == Element { self = try replacing( regex, diff --git a/Sources/_StringProcessing/Algorithms/Matching/Matches.swift b/Sources/_StringProcessing/Algorithms/Matching/Matches.swift index 293520735..a7cd17779 100644 --- a/Sources/_StringProcessing/Algorithms/Matching/Matches.swift +++ b/Sources/_StringProcessing/Algorithms/Matching/Matches.swift @@ -204,15 +204,15 @@ extension BidirectionalCollection where SubSequence == Substring { /// - Parameter regex: The regex to search for. /// - Returns: A collection of matches of `regex`. @available(SwiftStdlib 5.7, *) - public func matches( - of r: R - ) -> [Regex.Match] { + public func matches( + of r: some RegexComponent + ) -> [Regex.Match] { let slice = self[...] var start = self.startIndex let end = self.endIndex let regex = r.regex - var result = [Regex.Match]() + var result = [Regex.Match]() while start <= end { guard let match = try? regex._firstMatch( slice.base, in: start.. { associatedtype RegexOutput var regex: Regex { get } }