diff --git a/Sources/RegexBuilder/DSL.swift b/Sources/RegexBuilder/DSL.swift index 457439a43..632f1baba 100644 --- a/Sources/RegexBuilder/DSL.swift +++ b/Sources/RegexBuilder/DSL.swift @@ -235,6 +235,18 @@ public struct TryCapture: _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: _BuiltinRegexComponent { + public var regex: Regex + + internal init(_ regex: Regex) { + self.regex = regex + } +} + // MARK: - Backreference public struct Reference: RegexComponent { diff --git a/Sources/RegexBuilder/Variadics.swift b/Sources/RegexBuilder/Variadics.swift index f59b1f13a..002898dfd 100644 --- a/Sources/RegexBuilder/Variadics.swift +++ b/Sources/RegexBuilder/Variadics.swift @@ -1566,6 +1566,173 @@ extension Repeat { self.init(node: .repeating(expression.relative(to: 0..( + _ component: Component + ) where Output == Substring { + self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root)) + } +} + +extension BacktrackingScope { + @_disfavoredOverload + public init( + @RegexComponentBuilder _ component: () -> Component + ) where Output == Substring { + self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root)) + } +} +extension BacktrackingScope { + public init( + _ component: Component + ) where Output == (Substring, C0), Component.Output == (W, C0) { + self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root)) + } +} + +extension BacktrackingScope { + public init( + @RegexComponentBuilder _ component: () -> Component + ) where Output == (Substring, C0), Component.Output == (W, C0) { + self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root)) + } +} +extension BacktrackingScope { + public init( + _ component: Component + ) where Output == (Substring, C0, C1), Component.Output == (W, C0, C1) { + self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root)) + } +} + +extension BacktrackingScope { + public init( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( + _ 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( + @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( accumulated: R0, next: R1 diff --git a/Sources/VariadicsGenerator/VariadicsGenerator.swift b/Sources/VariadicsGenerator/VariadicsGenerator.swift index ff406e9fb..23a362dad 100644 --- a/Sources/VariadicsGenerator/VariadicsGenerator.swift +++ b/Sources/VariadicsGenerator/VariadicsGenerator.swift @@ -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( @@ -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..( + _ 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)