The more I am doing in Swift, the more I am finding pattern matching with the switch statement useful. However, I have been dissappointed in it's lack of support for Regular Expressions. Recently, I ran into a case where I needed to derive some logical meaning from the format of a string. So, I started looking for an elegant way to codify a series of mutually exclusive pattern matches.
I ran across this post from NSHipster. By combining their Regex struct with the Swift pattern matching operator, I was able to write the following:
switch(someinputstring){
case Regex(pattern: "^4[0-9]{12}(?:[0-9]{3})?$"):
println("It's a Visa card")
case Regex(pattern: "^3[47][0-9]{13}$"):
println("It's American Express")
case Regex(pattern: "^5[1-5][0-9]{14}$"):
println("It's a MasterCard")
case Regex(pattern: "^6(?:011|5[0-9]{2})[0-9]{12}$"):
println("It's a Discover card")
default:
println("ERROR: Unknown card type")
}
Here's How
First, create a structure to wrap up and simplify NSRegularExpression:
//from NSHipster - http://nshipster.com/swift-literal-convertible/
struct Regex {
let pattern: String
let options: NSRegularExpressionOptions!
private var matcher: NSRegularExpression {
return NSRegularExpression(pattern: self.pattern, options: self.options, error: nil)
}
init(pattern: String, options: NSRegularExpressionOptions = nil) {
self.pattern = pattern
self.options = options
}
func match(string: String, options: NSMatchingOptions = nil) -> Bool {
return self.matcher.numberOfMatchesInString(string, options: options, range: NSMakeRange(0, string.utf16Count)) != 0
}
}
Then add a simple protocol for matching regular expressions and extend string to implement it.
protocol RegularExpressionMatchable {
func match(regex: Regex) -> Bool
}
extension String: RegularExpressionMatchable {
func match(regex: Regex) -> Bool {
return regex.match(self)
}
}
Finally, overload the Swift pattern matching operator to handle a Regex and a RegularExpressionsMatchable.
func ~=<T: RegularExpressionMatchable>(pattern: Regex, matchable: T) -> Bool {
return matchable.match(pattern)
}
With these pieces in place, you can write switch statements like:
switch(someinputstring){
case Regex(pattern: "^4[0-9]{12}(?:[0-9]{3})?$"):
println("It's a Visa card")
case Regex(pattern: "^3[47][0-9]{13}$"):
println("It's American Express")
case Regex(pattern: "^5[1-5][0-9]{14}$"):
println("It's a MasterCard")
case Regex(pattern: "^6(?:011|5[0-9]{2})[0-9]{12}$"):
println("It's a Discover card")
default:
println("ERROR: Unknown card type")
}
Or,
switch(inputstring){
case Regex("^clemson(\\su.+)?$", options: NSRegularExpressionOptions.CaseInsensitive):
println("GREAT School!! Awesome Team!")
case Regex("^(u.+)?south carolina$", options: NSRegularExpressionOptions.CaseInsensitive):
println("I'm sorry.")
default:
println("Bless your heart.")
}