How to create a Swift Regex that outputs a custom type?

Viewed 45

In the WWDC videos, it was shown that you can do something like this with Captures/TryCaptures in the Regex Builder:

let regex = Regex {
  // ...

  TryCapture {
    OneOrMore(.digit)
  } transform: {
    Int($0)
  }

  // ...
}

And the output of the Regex will be type safe. The Regex will output an Int for that group, instead of a Substring like it normally does.

However, what I would like to do is to change the entire output type of the whole Regex, like applying a transform: at the end of the Regex closure. For example, to parse a line containing the name, age and date of birth of a person:

John (30) 1992-09-22

I would like to do something like:

// this doesn't work and is just for illustration - there is no such Regex.init
let regex = Regex {
    Capture(/\w+/)
    " ("
    TryCapture(/\d+/) { Int($0) }
    ") "
    Capture(.iso8601Date(timeZone: .gmt))
} transform: { (_, name, age, dob) in
    Person(name: String(name), age: age, dob: dob)
}

And I would expect regex be of type Regex<Person>, and not Regex<(Substring, Substring, Int, Date)>. That is, someString.wholeMatch(of: regex).output would be a string, not a tuple.

I'm basically just trying to reduce the occurrence of tuples, because I find it very inconvenient to work with them, especially unnamed ones. Since RegexComponent is parameterised by the unconstrained RegexOutput type, and there are built-in types where RegexOutput is Date and Decimal, surely doing this for arbitrary types using regex is not impossible, right?

My attempt was:

struct Person {
    let name: String
    let age: Int
    let dob: Date
}
let line = "John (30) 1992-09-22"
let regex = Regex {
    Capture {
        Capture(/\w+/)
        " ("
        TryCapture(/\d+/) { Int($0) }
        ") "
        Capture(.iso8601Date(timeZone: .gmt))
    } transform: { (_, name, age, dob) in
        Person(name: String(name), age: age, dob: dob)
    }
}
line.wholeMatch(of: regex)

but this crashed at runtime, giving the message:

Could not cast value of type 'Swift.Substring' (0x7ff865e3ead8) to '(Swift.Substring, Swift.Substring, Swift.Int, Foundation.Date)' (0x7ff863f2e660).

Another attempt of mine using CustomConsumingRegexComponent is shown here in this answer, but that has quite a large caveat, namely that it doesn't backtrack properly.

How can I create a Regex that outputs my own type?

0 Answers
Related