Programmiersprachen: Reguläre Ausdrücke in Swift neu aufgestellt

Seite 2: Baukasten für reguläre Ausdrücke

Inhaltsverzeichnis

Sogar kürzere reguläre Ausdrücke können kryptisch sein und ein Nachschlagen in der Dokumentation erfordern. Die Logik erst in einem String oder einem Regex Literal zu kodieren, um sie anschließend wieder zu analysieren, ist zudem nicht der direkte Weg.

Reguläre Ausdrücke eignen sich hervorragend für eine domänenspezifische Sprache (DSL). Swift bringt hierfür in der Standard Library einen ResultBuilder im Modul RegexBuilder mit. Damit lassen sich reguläre Ausdrücke ähnlich bequem und lesbar zusammenbauen wie eine View in SwiftUI, das dafür den ViewBuilder eingeführt hat.

Bevor ein Swift-Programm den RegexBuilder verwenden kann, muss es ihn importieren:

import RegexBuilder

let regex = Regex {
  "b"
  Capture {
    ZeroOrMore(.any)
  }
  "d"
}

if let match = "abcde".firstMatch(of: regex) {
  let (matched, detail) = match.output
  print(matched) // "bcd"
  print(detail) // "c"
}

Der RegexBuilder ermöglicht es, Teile eines regulären Ausdruckes auszulagern, um ihn modularer und lesbarer zu gestalten.

Ein Beispiel dafür sind die Formatter aus dem Framework Foundation. Sie kodieren und dekodieren Daten wie Fließkommazahlen und Zeitangaben in einem String. Apple hat die Formatter für den Einsatz in den neuen regulären Ausdrücken erweitert:

import Foundation
import RegexBuilder

let regex = Regex {
  "Datum: "
  Capture {
    One(.date(.numeric, 
              locale: Locale(identifier: "de_DE"),
              timeZone: .current))
  }
}

if let match = "Datum: 11.06.2022".firstMatch(of: regex) {
  let (matched, detail) = match.output
  print(matched) // "Datum: 11.06.2022"
  print(detail) // 2022-06-10 22:00:00 +0000
}

Die Debug-Ausgabe mittels print() verwendet GMT als Zeitzone, womit das Ergebnis dem erwarteten 11.06.2022 00:00 MESZ entspricht.

Der Output ist (Substring, Date). detail ist somit tatsächlich ein Date und kein Substring.

Mit dem RegexBuilder lassen sich zudem eigene Teile lokal auslagern:

import Foundation
import RegexBuilder

let label = Regex {
  "Datum: "
}

let captureDate = Regex {
  Capture {
    One(.date(.numeric, 
              locale: Locale(identifier: "de_DE"),
              timeZone: .current))
  }
}

let regex = Regex {
  label
  captureDate
}

if let match = "Datum: 11.06.2022".firstMatch(of: regex) {
  let (matched, detail) = match.output
  print(matched) // "Datum: 11.06.2022"
  print(detail)  // 2022-06-10 22:00:00 +0000
}

Um Teile regulärer Ausdrücke wie obiges label an anderer Stelle und in anderen Projekten weiterzuverwenden, kann man sie als eigene structs auslagern.

import Foundation
import RegexBuilder

struct Label: RegexComponent {
  @RegexComponentBuilder var regex: Regex<(Substring)> {
    "Datum: "
  }
}

let captureDate = Regex {
  Capture {
    One(.date(.numeric, 
              locale: Locale(identifier: "de_DE"),
              timeZone: .current))
  }
}

let regex = Regex {
  Label()
  captureDate
}

if let match = "Datum: 11.06.2022".firstMatch(of: regex) {
  let (matched, detail) = match.output
  print(matched) // "Datum: 11.06.2022"
  print(detail) // 2022-06-10 22:00:00 +0000
}

Weiterführende Informationen finden sich in den Videos der beiden Sessions von Apples Entwicklerkonferenz WWDC 2022 zu regulären Ausdrücken: