In the previous regular expression tip, we implemented a =~ operator to do simple text match. Although there is no built-in support for regular expression, there is a similar feature in Swift. That is pattern match.

Conceptually, regular expression is only a subset of pattern match. However, the pattern match support in Swift now is quite primary and simple. It can only handle equality match and range match. There is a pattern match operator in the standard library, which is not so widely known. It is ~=. We can search it in the standard library and find the related APIs below:


func ~=<T : Equatable>(a: T, b: T) -> Bool

func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool

func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Bool

They can accept parameters of which types can be checked equality, types can be compared to nil, and types of a range and some specified value respectively. All methods return a Bool to indicate whether the match is successful or not. Remember something? It is not so obvious at first, but let us see some important use of switch in Swift:

  1. Types can be checked equality:

    let password = "akfuv(3"
    switch password {
        case "akfuv(3": print("Passed")
        default:        print("Failed")
    }
    
  2. Types can be compared to optional value:

    let num: Int? = nil
    switch num {
        case nil: print("No value")
        default:  print("\(num!)")
    }
    
  3. Types of a range. Check some value in the range or not:

    let x = 0.5
    switch x {
        case -1.0...1.0: print("In range")
        default: print("Out of range")
    }
    

That's right! switch statement is using ~= to do a pattern match under the hood. The pattern is set as the left operand for ~= by case. And the switch tells it what is waiting to be matched, as the right operand. This process is done by Swift implicitly. After knowing this, we can rely on the switch to make some fun things. By applying our customized pattern, sometimes we can write cleaner and more logical code. Here I will give an example of using the regular expression as the pattern in switch. So you can draw a basic idea of how to do.

Overriding the ~= operator is always a good start point. Add a new version of ~= and make it accepting an NSRegularExpression object as pattern, to match a String input:

func ~=(pattern: NSRegularExpression, input: String) -> Bool {
    return pattern.numberOfMatchesInString(input,
        options: nil,
        range: NSRange(location: 0, length: count(input))) > 0
}

For convenience, I also add an operator to convert a text string to NSRegularExpression. Of course you could use StringLiteralConvertible as well, but it is related to another tip and here I decide not to use it.

prefix operator ~/ {}

prefix func ~/(pattern: String) -> NSRegularExpression {
    return NSRegularExpression(pattern: pattern, options: nil, error: nil)!
}

It is so simple. Now we can use a regular expression in case, and let it match the string in switch:

let contact = ("http://onevcat.com", "onev@onevcat.com")

let mailRegex: NSRegularExpression
let siteRegex: NSRegularExpression

mailRegex = 
    try ~/"^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
siteRegex = 
    try ~/"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"

switch contact {
    case (siteRegex, mailRegex): print("Valid website and email")
    case (_, mailRegex): print("Valid email only")
    case (siteRegex, _): print("Valid website only")
    default: print("Both invalid")
}

// Output:
// Valid website and email