Skip to content

Instantly share code, notes, and snippets.

@robnadin
Created July 19, 2024 09:53
Show Gist options
  • Save robnadin/b2ad61ad3f3d31153ccbf1547b6cf9df to your computer and use it in GitHub Desktop.
Save robnadin/b2ad61ad3f3d31153ccbf1547b6cf9df to your computer and use it in GitHub Desktop.
Adds dynamic member lookup of captures when matching regular expressions using the RegexBuilder API
import os
import RegexBuilder
public struct RegularExpression<Output, Captures> {
fileprivate let references = References<Captures>()
fileprivate let regex: Regex<Output>
public init(for capturesType: Captures.Type = Captures.self, @RegexComponentBuilder _ content: (References<Captures>) -> some RegexComponent<Output>) {
let components = content(references)
regex = Regex { components }
}
}
extension RegularExpression {
@dynamicMemberLookup
public struct References<Root> {
private let state = OSAllocatedUnfairLock<[PartialKeyPath<Root>: any RegexComponent]>(initialState: [:])
public subscript<Value>(dynamicMember keyPath: KeyPath<Root, Value>) -> Reference<Value> {
state.withLock {
if let value = $0[keyPath] as? Reference<Value> {
return value
}
let newValue = Reference<Value>()
$0[keyPath] = newValue
return newValue
}
}
}
@dynamicMemberLookup
public struct Match {
fileprivate let references: References<Captures>
fileprivate let match: Regex<Output>.Match
public subscript<T>(dynamicMember keyPath: KeyPath<References<Captures>, Reference<T>>) -> T {
let reference = references[keyPath: keyPath]
return match[reference]
}
}
}
extension BidirectionalCollection where SubSequence == Substring {
public func firstMatch<Output, Captures>(of regex: RegularExpression<Output, Captures>) -> RegularExpression<Output, Captures>.Match? {
guard let match = firstMatch(of: regex.regex) else {
return nil
}
return .init(references: regex.references, match: match)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment