Как создать Swift Regex, который выводит пользовательский тип?

В видеороликах WWDC было показано, что вы можете сделать что-то подобное с помощьюCaptureс/TryCaptures в построителе регулярных выражений:

      let regex = Regex {
  // ...

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

  // ...
}

И вывод будет безопасным по типу. Будет выведенIntдля этой группы вместоSubstringкак это обычно бывает.

Однако я хотел бы изменить весь тип вывода всего, например, применивtransform:в конце закрытия. Например, чтобы разобрать строку, содержащую имя, возраст и дату рождения человека:

      John (30) 1992-09-22

Я хотел бы сделать что-то вроде:

      // 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)
}

И я ожидалregexбыть типаRegex<Person>, и неRegex<(Substring, Substring, Int, Date)>. То есть,someString.wholeMatch(of: regex).outputбудет строкой, а не кортежем.

Я в основном просто пытаюсь уменьшить встречаемость кортежей, потому что мне очень неудобно с ними работать, особенно безымянными. СRegexComponentпараметризован неограниченным типом, и есть встроенные типы, в которыхRegexOutputявляетсяDateиDecimal, конечно, сделать это для произвольных типов с использованием регулярных выражений не невозможно, верно?

Моя попытка была:

      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)

но это разбилось во время выполнения, выдав сообщение:

Не удалось преобразовать значение типа «Swift.Substring» (0x7ff865e3ead8) в «(Swift.Substring, Swift.Substring, Swift.Int, Foundation.Date)» (0x7ff863f2e660).

Еще одна моя попытка использоватьCustomConsumingRegexComponentпоказано здесь, в этом ответе , но с довольно большим предостережением, а именно с тем, что он не отступает должным образом.

Как я могу создатьRegexкоторый выводит мой собственный тип?

1 ответ

Из того, что я читал/видел в примерах (например, swift-regex), было бы неплохо создать компонент регулярного выражения, аналогичный.word,.digit, но вложенностьcapturesне кажется, что работает легко.

Вот пример запуска на игровой площадке для созданияPerson structпример:

      public static func regexBuilderMatching(string: String = "John (30) 1992-09-22") {

    struct Person: CustomStringConvertible {
        let name: String
        let age: Int
        let dob: Date

        public func dobToFormatterString() -> String {
            let dateFormatter = DateFormatter()
            // 1992-09-22 04:00:00 +0000
            dateFormatter.dateFormat = "yyyy-MM-dd"
            return dateFormatter.string(from: self.dob)
        }
        
        var description: String {
            return "\(name), age: \(age), has dob: \(dobToFormatterString())"
        }
    }

    func dateFromString(dateString: String) -> Date? {
        let formatter = DateFormatter()
        formatter.timeStyle = .none // removes time from date
        formatter.dateStyle = .full
        formatter.dateFormat = "y-MM-d" // 1992-09-22
        return formatter.date(from: dateString)
    }

    let regexWithBasicCapture = Regex {
        /* 1. */ Capture { OneOrMore(.word) }
        /* 2. */ " ("
        /* 3. */ TryCapture { OneOrMore(.digit) }
                    transform: { match in
                        Int(match)
                    }
        /* 4. */ ") "
        /* 5. */ TryCapture { OneOrMore(.iso8601Date(timeZone: .gmt)) }
                    transform: { match in
                        dateFromString(dateString: String(match))
                    }
    }

    let matches = string.matches(of: regexWithBasicCapture)
    for match in matches {
        // shorthand syntax using match output
        // https://developer.apple.com/documentation/swift/regex/match
        let (_, name, age, date) = match.output
        let person = Person(name: String(name), age: age, dob: date)
        print(person)
    }
}

Приведенный выше код выведет:

      John, age: 30, has dob: 1992-09-22
Другие вопросы по тегам