Skip to content

Instantly share code, notes, and snippets.

@SintraWorks
Forked from erica/buggywuggy.swift
Last active January 6, 2018 11:14
Show Gist options
  • Save SintraWorks/2c8a3928ae3a8c92f7ddc93633585dc6 to your computer and use it in GitHub Desktop.
Save SintraWorks/2c8a3928ae3a8c92f7ddc93633585dc6 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>()
private(set) var regex: NSRegularExpression
public var options: NSRegularExpression.Options {
get { return Regex.regexCache.object(forKey: "\(self.regex.pattern)" as NSString)!.options }
/// - Note: If the cached regex already sports these
/// options, there is no need to update.
set(options) {
if self.regex.options == options {
//print("not changing options") // uncomment to see cache operation
return
}
//print("Recreating pattern: \"\(self.regex.pattern)\" with options: \(options.rawValue)") // uncomment to see cache operation
self.regex = try! NSRegularExpression(pattern: self.regex.pattern, options: options)
}
}
/// Initializes a `Regex` instance that defaults to
/// "no options". Update as needed for case or
/// diacritical insensitivity using the 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 = []) {
// 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 then 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 `[]`.
public static func match(_ pattern: String, options: NSRegularExpression.Options? = nil) -> Regex {
if let regex = Regex.regexCache.object(forKey: "\(pattern)" as NSString) {
if let requestedOptions = options {
//print("matching, with options: \(String(describing: requestedOptions))") // uncomment to see cache operation
regex.options = requestedOptions
return regex
} else {
//print("matching, reusing pattern: \"\(pattern)\" options: \(String(describing: regex.regex.options))") // uncomment to see cache operation
return regex
}
} else {
let options: NSRegularExpression.Options = options ?? []
//print("matching, creating with pattern: \"\(pattern)\" options: \(String(describing: options))") // 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")
}
print("")
let pattern = "sailor"
let regex = Regex(pattern, options: [.caseInsensitive])
print(Regex.match(pattern) ~= "sailor") // true
print(Regex.match(pattern) ~= "Sailor") // true
print(Regex.match(pattern) ~= "SAILOR") // true
print("")
regex.options = []
print(Regex.match(pattern) ~= "sailor") // true
print(Regex.match(pattern) ~= "Sailor") // false
print(Regex.match(pattern) ~= "SAILOR") // false
print("")
print(Regex.match(pattern, options: [.caseInsensitive]) ~= "sailor") // true
print(Regex.match(pattern) ~= "Sailor") // true
print(Regex.match(pattern) ~= "SAILOR") // true
print("")
Regex.regexCache.object(forKey: pattern as NSString)?.options = [.caseInsensitive]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment