Как создать компонент регулярного выражения ChoiceOf из перечисления CaseIterable строковых значений?
В настоящее время я использую этот обходной путь, чтобы передать список случаев перечисления вChoiceOf
.
enum Fruit: String, CaseIterable {
case apple = "Apple"
case banana = "Banana"
case strawberry = "Strawberry"
}
let regex = Regex {
ChoiceOf {
try! Regex(Fruit.allCases.map(\.rawValue).joined(separator: "|"))
}
}
Есть ли более элегантный способ сделать это без использования жестко закодированного шаблона регулярного выражения? Что-то вродеChoiceOf(Fruit.allCases)
?
1 ответ
Это тоже своего рода хак, но вы можете увидеть, как работают строители регулярных выражений в предложении эволюции Swift :
Regex {
regex0
regex1
regex2
regex3
}
становится
Regex {
let e0 = RegexComponentBuilder.buildExpression(regex0)
let e1 = RegexComponentBuilder.buildExpression(regex1)
let e2 = RegexComponentBuilder.buildExpression(regex2)
let e3 = RegexComponentBuilder.buildExpression(regex3)
let r0 = RegexComponentBuilder.buildPartialBlock(first: e0)
let r1 = RegexComponentBuilder.buildPartialBlock(accumulated: r0, next: e1)
let r2 = RegexComponentBuilder.buildPartialBlock(accumulated: r1, next: e2)
let r3 = RegexComponentBuilder.buildPartialBlock(accumulated: r2, next: e3)
return r3
}
Скорее, чемRegexComponentBuilder
, мы можем использоватьAlternationBuilder
здесь, чтобы сделатьChoiceOf
. Вы можете видеть, что так, какbuildExpression
иbuildPartialBlock
называются похожи наmap
иreduce
.
let regex = Regex {
let exps = Fruit.allCases.map { AlternationBuilder.buildExpression($0.rawValue) }
// assuming exps is not empty
exps.dropFirst().reduce(AlternationBuilder.buildPartialBlock(first: exps[0])) { acc, next in
AlternationBuilder.buildPartialBlock(accumulated: acc, next: next)
}
}
Мы можем поместить это в расширение:
extension ChoiceOf where RegexOutput == Substring {
init<S: Sequence<String>>(_ components: S) {
let exps = components.map { AlternationBuilder.buildExpression($0) }
guard !exps.isEmpty else {
fatalError("Empty choice!")
}
self = exps.dropFirst().reduce(AlternationBuilder.buildPartialBlock(first: exps[0])) { acc, next in
AlternationBuilder.buildPartialBlock(accumulated: acc, next: next)
}
}
}
Примечательно, что это не работает, когда массив пуст, т.е. когда нет выбора. Вы не можете просто вернуть: . Это нарушает одно из ограничений этого инициализатора. И действительно,Choice { }
все равно не имеет смысла.
Я думаю, что это также является причиной того, что это не поддерживается "из коробки" - компилятор не может определить, является лиFruits.allCases
или любой другой массив, который вы ему даете, пуст или нет.