Using Regex in Switch Statements with Swift

switchboard
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.")
}