Skip to content

DSL support for atomic groups #238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sources/RegexBuilder/DSL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,18 @@ public struct TryCapture<Output>: _BuiltinRegexComponent {
// Note: Public initializers are currently gyb'd. See Variadics.swift.
}

// MARK: - Groups

/// An atomic group, i.e. opens a local backtracking scope which, upon successful exit,
/// discards any remaining backtracking points from within the scope
public struct BacktrackingScope<Output>: _BuiltinRegexComponent {
public var regex: Regex<Output>

internal init(_ regex: Regex<Output>) {
self.regex = regex
}
}

// MARK: - Backreference

public struct Reference<Capture>: RegexComponent {
Expand Down
167 changes: 167 additions & 0 deletions Sources/RegexBuilder/Variadics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,173 @@ extension Repeat {
self.init(node: .repeating(expression.relative(to: 0..<Int.max), behavior, component().regex.root))
}
}
extension BacktrackingScope {
@_disfavoredOverload
public init<Component: RegexComponent>(
_ component: Component
) where Output == Substring {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
@_disfavoredOverload
public init<Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == Substring {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, Component: RegexComponent>(
_ component: Component
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we need a variant that takes a non-closure?

) where Output == (Substring, C0), Component.Output == (W, C0) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0), Component.Output == (W, C0) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1), Component.Output == (W, C0, C1) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1), Component.Output == (W, C0, C1) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2), Component.Output == (W, C0, C1, C2) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2), Component.Output == (W, C0, C1, C2) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3), Component.Output == (W, C0, C1, C2, C3) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3), Component.Output == (W, C0, C1, C2, C3) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4), Component.Output == (W, C0, C1, C2, C3, C4) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4), Component.Output == (W, C0, C1, C2, C3, C4) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5), Component.Output == (W, C0, C1, C2, C3, C4, C5) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5), Component.Output == (W, C0, C1, C2, C3, C4, C5) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, Component: RegexComponent>(
_ component: Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
}
}

extension BacktrackingScope {
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, Component: RegexComponent>(
@RegexComponentBuilder _ component: () -> Component
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) {
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
}
}
extension AlternationBuilder {
public static func buildPartialBlock<R0, R1>(
accumulated: R0, next: R1
Expand Down
61 changes: 61 additions & 0 deletions Sources/VariadicsGenerator/VariadicsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ struct VariadicsGenerator: ParsableCommand {
print(to: &standardError)
}

print("Generating atomic groups...", to: &standardError)
for arity in 0...maxArity {
print(" Arity \(arity): ", terminator: "", to: &standardError)
emitAtomicGroup(arity: arity)
print(to: &standardError)
}

print("Generating alternation overloads...", to: &standardError)
for (leftArity, rightArity) in Permutations(totalArity: maxArity) {
print(
Expand Down Expand Up @@ -393,6 +400,60 @@ struct VariadicsGenerator: ParsableCommand {

""")
}


func emitAtomicGroup(arity: Int) {
assert(arity >= 0)
let groupName = "BacktrackingScope"
func node(builder: Bool) -> String {
"""
.nonCapturingGroup(.atomicNonCapturing, component\(
builder ? "()" : ""
).regex.root)
"""
}

let disfavored = arity == 0 ? "@_disfavoredOverload\n" : ""
let genericParams: String = {
var result = ""
if arity > 0 {
result += "W"
result += (0..<arity).map { ", C\($0)" }.joined()
result += ", "
}
result += "Component: \(regexComponentProtocolName)"
return result
}()
let captures = (0..<arity).map { "C\($0)" }
let capturesJoined = captures.joined(separator: ", ")
let matchType = arity == 0
? baseMatchTypeName
: "(\(baseMatchTypeName), \(capturesJoined))"
let whereClauseForInit = "where \(outputAssociatedTypeName) == \(matchType)" +
(arity == 0 ? "" : ", Component.\(outputAssociatedTypeName) == (W, \(capturesJoined))")

output("""
extension \(groupName) {
\(disfavored)\
public init<\(genericParams)>(
_ component: Component
) \(whereClauseForInit) {
self.init(node: \(node(builder: false)))
}
}

extension \(groupName) {
\(disfavored)\
public init<\(genericParams)>(
@\(concatBuilderName) _ component: () -> Component
) \(whereClauseForInit) {
self.init(node: \(node(builder: true)))
}
}

""")
}


func emitRepeating(arity: Int) {
assert(arity >= 0)
Expand Down