Skip to content

Instantly share code, notes, and snippets.

@erica
Last active December 18, 2022 05:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/2763b4b3d77ad597a7dc566ec1bd1226 to your computer and use it in GitHub Desktop.
Save erica/2763b4b3d77ad597a7dc566ec1bd1226 to your computer and use it in GitHub Desktop.
import Foundation
/// Provides NSRegularExpression pattern matching against strings
/// in `switch` expressions
///
/// Regular expressions are expensive to construct. The built-in
/// class cache stores already-constructed pattern instances using
/// the pattern string (coerced to `NSString`) as its keys. Modify
/// matching options at the `match(_, options:)` call-site if needed.
///
/// - Note: This type is implemented as a class as `NSCache`
/// is not compatible with Swift structures. Its keys, which
/// must also be `AnyObject`, are coerced from `String` to
/// `NSString`.
public class Regex {
/// Store (expensive) existing `Regex` instances
public static let regexCache = NSCache<NSString, Regex>()
public let regex: NSRegularExpression
public var options: NSRegularExpression.Options = []
/// Initializes a `Regex` instance that defaults to
/// "no options". Update as needed for case or
/// diacritical insensitivity using publicly modifiable
/// `options` property.
///
/// - parameter regexPattern: A regular expression pattern string
/// - parameter options: Regular expression matching options. (See `NSRegularExpression.Options`)
public init(_ regexPattern: String, options: NSRegularExpression.Options = []) {
self.options = options
// Fail loudly if valid regular expression cannot
// be constructed
self.regex = try! NSRegularExpression(pattern: regexPattern, options: options)
Regex.regexCache.setObject(self, forKey: regexPattern as NSString)
}
/// Create or retrieve a `Regex` instance from the the
/// class cache. The instance can than be used with `~=` to
/// match against a string.
///
/// - parameter pattern: A regular expression pattern string.
/// - parameter options: Regular expression matching options. (See `NSRegularExpression.Options`). Defaults to `[]`.
///
/// - Note: Existing options are re-used unless they are
/// overwritten by a non-empty set. To update options
/// to `[]`, access them directly from the cached version
/// outside the `match` function.
public static func match(_ pattern: String, options: NSRegularExpression.Options = []) -> Regex {
if let regex = Regex.regexCache.object(forKey: pattern as NSString) {
// print("Reusing") // uncomment to see cache operation
// Apply option-reuse policy
if options != [] {
regex.options = options
}
return regex
} else {
// print("Creating") // uncomment to see cache operation
let regex = Regex(pattern, options: options)
Regex.regexCache.setObject(regex, forKey: pattern as NSString)
return regex
}
}
/// Extends pattern matching to use the pattern and
/// options stored in the `Regex` matcher
public static func ~= (
lhs: Regex,
rhs: String
) -> Bool {
let range = NSRange(location: 0, length: rhs.utf16.count)
if let _ = lhs.regex.firstMatch(in: rhs, range: range) { return true }
return false
}
}
// For example
let str = "Hello, playground"
str ~= "Hello" // false
str ~= "Hello, playground" // true
Regex.match("H.*o") ~= str // true
Regex.match("H.*o") ~= "Out of luck" // false
// matches
switch str {
case Regex.match("H.*o"): print("Hello to you!")
default: print("Nope")
}
// does not match
switch "Out of luck" {
case Regex.match("H.*o"): print("Hello to you!")
default: print("Nope")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment