Как создать компонент регулярного выражения 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или любой другой массив, который вы ему даете, пуст или нет.

Другие вопросы по тегам