Currently I use this workaround to pass a list of enum cases to a 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: "|"))
}
}
Is there a more elegant way to do this, without using a hardcoded regex pattern? Something like ChoiceOf(Fruit.allCases)?
This is kind of a hack too, but you can see how the regex builders work in the Swift evolution proposal:
Regex {
regex0
regex1
regex2
regex3
}
becomes
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
}
Rather than RegexComponentBuilder, we can use AlternationBuilder here to make a ChoiceOf. You can see that the way that buildExpression and buildPartialBlock are called are like a map and 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)
}
}
We can put this into an extension:
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)
}
}
}
Notably, this does not work when the array is empty, i.e. when there is no choice to be made. You cannot just return: Choice { }. That violates one of the constraints of that initialiser. And indeed, Choice { } doesn't make sense anyway.
I think this is also why this isn't supported out of the box - the compiler cannot determine whether Fruits.allCases, or whatever other array you give it, is empty or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With