Skip to content

Commit 0108e22

Browse files
authored
DSL support for atomic groups (#238)
1 parent 365f5b5 commit 0108e22

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

Sources/RegexBuilder/DSL.swift

+12
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ public struct TryCapture<Output>: _BuiltinRegexComponent {
235235
// Note: Public initializers are currently gyb'd. See Variadics.swift.
236236
}
237237

238+
// MARK: - Groups
239+
240+
/// An atomic group, i.e. opens a local backtracking scope which, upon successful exit,
241+
/// discards any remaining backtracking points from within the scope
242+
public struct BacktrackingScope<Output>: _BuiltinRegexComponent {
243+
public var regex: Regex<Output>
244+
245+
internal init(_ regex: Regex<Output>) {
246+
self.regex = regex
247+
}
248+
}
249+
238250
// MARK: - Backreference
239251

240252
public struct Reference<Capture>: RegexComponent {

Sources/RegexBuilder/Variadics.swift

+167
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,173 @@ extension Repeat {
15661566
self.init(node: .repeating(expression.relative(to: 0..<Int.max), behavior, component().regex.root))
15671567
}
15681568
}
1569+
extension BacktrackingScope {
1570+
@_disfavoredOverload
1571+
public init<Component: RegexComponent>(
1572+
_ component: Component
1573+
) where Output == Substring {
1574+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1575+
}
1576+
}
1577+
1578+
extension BacktrackingScope {
1579+
@_disfavoredOverload
1580+
public init<Component: RegexComponent>(
1581+
@RegexComponentBuilder _ component: () -> Component
1582+
) where Output == Substring {
1583+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1584+
}
1585+
}
1586+
extension BacktrackingScope {
1587+
public init<W, C0, Component: RegexComponent>(
1588+
_ component: Component
1589+
) where Output == (Substring, C0), Component.Output == (W, C0) {
1590+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1591+
}
1592+
}
1593+
1594+
extension BacktrackingScope {
1595+
public init<W, C0, Component: RegexComponent>(
1596+
@RegexComponentBuilder _ component: () -> Component
1597+
) where Output == (Substring, C0), Component.Output == (W, C0) {
1598+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1599+
}
1600+
}
1601+
extension BacktrackingScope {
1602+
public init<W, C0, C1, Component: RegexComponent>(
1603+
_ component: Component
1604+
) where Output == (Substring, C0, C1), Component.Output == (W, C0, C1) {
1605+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1606+
}
1607+
}
1608+
1609+
extension BacktrackingScope {
1610+
public init<W, C0, C1, Component: RegexComponent>(
1611+
@RegexComponentBuilder _ component: () -> Component
1612+
) where Output == (Substring, C0, C1), Component.Output == (W, C0, C1) {
1613+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1614+
}
1615+
}
1616+
extension BacktrackingScope {
1617+
public init<W, C0, C1, C2, Component: RegexComponent>(
1618+
_ component: Component
1619+
) where Output == (Substring, C0, C1, C2), Component.Output == (W, C0, C1, C2) {
1620+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1621+
}
1622+
}
1623+
1624+
extension BacktrackingScope {
1625+
public init<W, C0, C1, C2, Component: RegexComponent>(
1626+
@RegexComponentBuilder _ component: () -> Component
1627+
) where Output == (Substring, C0, C1, C2), Component.Output == (W, C0, C1, C2) {
1628+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1629+
}
1630+
}
1631+
extension BacktrackingScope {
1632+
public init<W, C0, C1, C2, C3, Component: RegexComponent>(
1633+
_ component: Component
1634+
) where Output == (Substring, C0, C1, C2, C3), Component.Output == (W, C0, C1, C2, C3) {
1635+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1636+
}
1637+
}
1638+
1639+
extension BacktrackingScope {
1640+
public init<W, C0, C1, C2, C3, Component: RegexComponent>(
1641+
@RegexComponentBuilder _ component: () -> Component
1642+
) where Output == (Substring, C0, C1, C2, C3), Component.Output == (W, C0, C1, C2, C3) {
1643+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1644+
}
1645+
}
1646+
extension BacktrackingScope {
1647+
public init<W, C0, C1, C2, C3, C4, Component: RegexComponent>(
1648+
_ component: Component
1649+
) where Output == (Substring, C0, C1, C2, C3, C4), Component.Output == (W, C0, C1, C2, C3, C4) {
1650+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1651+
}
1652+
}
1653+
1654+
extension BacktrackingScope {
1655+
public init<W, C0, C1, C2, C3, C4, Component: RegexComponent>(
1656+
@RegexComponentBuilder _ component: () -> Component
1657+
) where Output == (Substring, C0, C1, C2, C3, C4), Component.Output == (W, C0, C1, C2, C3, C4) {
1658+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1659+
}
1660+
}
1661+
extension BacktrackingScope {
1662+
public init<W, C0, C1, C2, C3, C4, C5, Component: RegexComponent>(
1663+
_ component: Component
1664+
) where Output == (Substring, C0, C1, C2, C3, C4, C5), Component.Output == (W, C0, C1, C2, C3, C4, C5) {
1665+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1666+
}
1667+
}
1668+
1669+
extension BacktrackingScope {
1670+
public init<W, C0, C1, C2, C3, C4, C5, Component: RegexComponent>(
1671+
@RegexComponentBuilder _ component: () -> Component
1672+
) where Output == (Substring, C0, C1, C2, C3, C4, C5), Component.Output == (W, C0, C1, C2, C3, C4, C5) {
1673+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1674+
}
1675+
}
1676+
extension BacktrackingScope {
1677+
public init<W, C0, C1, C2, C3, C4, C5, C6, Component: RegexComponent>(
1678+
_ component: Component
1679+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6) {
1680+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1681+
}
1682+
}
1683+
1684+
extension BacktrackingScope {
1685+
public init<W, C0, C1, C2, C3, C4, C5, C6, Component: RegexComponent>(
1686+
@RegexComponentBuilder _ component: () -> Component
1687+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6) {
1688+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1689+
}
1690+
}
1691+
extension BacktrackingScope {
1692+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, Component: RegexComponent>(
1693+
_ component: Component
1694+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7) {
1695+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1696+
}
1697+
}
1698+
1699+
extension BacktrackingScope {
1700+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, Component: RegexComponent>(
1701+
@RegexComponentBuilder _ component: () -> Component
1702+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7) {
1703+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1704+
}
1705+
}
1706+
extension BacktrackingScope {
1707+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, Component: RegexComponent>(
1708+
_ component: Component
1709+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8) {
1710+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1711+
}
1712+
}
1713+
1714+
extension BacktrackingScope {
1715+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, Component: RegexComponent>(
1716+
@RegexComponentBuilder _ component: () -> Component
1717+
) where Output == (Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8), Component.Output == (W, C0, C1, C2, C3, C4, C5, C6, C7, C8) {
1718+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1719+
}
1720+
}
1721+
extension BacktrackingScope {
1722+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, Component: RegexComponent>(
1723+
_ component: Component
1724+
) 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) {
1725+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component.regex.root))
1726+
}
1727+
}
1728+
1729+
extension BacktrackingScope {
1730+
public init<W, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, Component: RegexComponent>(
1731+
@RegexComponentBuilder _ component: () -> Component
1732+
) 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) {
1733+
self.init(node: .nonCapturingGroup(.atomicNonCapturing, component().regex.root))
1734+
}
1735+
}
15691736
extension AlternationBuilder {
15701737
public static func buildPartialBlock<R0, R1>(
15711738
accumulated: R0, next: R1

Sources/VariadicsGenerator/VariadicsGenerator.swift

+61
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ struct VariadicsGenerator: ParsableCommand {
155155
print(to: &standardError)
156156
}
157157

158+
print("Generating atomic groups...", to: &standardError)
159+
for arity in 0...maxArity {
160+
print(" Arity \(arity): ", terminator: "", to: &standardError)
161+
emitAtomicGroup(arity: arity)
162+
print(to: &standardError)
163+
}
164+
158165
print("Generating alternation overloads...", to: &standardError)
159166
for (leftArity, rightArity) in Permutations(totalArity: maxArity) {
160167
print(
@@ -393,6 +400,60 @@ struct VariadicsGenerator: ParsableCommand {
393400
394401
""")
395402
}
403+
404+
405+
func emitAtomicGroup(arity: Int) {
406+
assert(arity >= 0)
407+
let groupName = "BacktrackingScope"
408+
func node(builder: Bool) -> String {
409+
"""
410+
.nonCapturingGroup(.atomicNonCapturing, component\(
411+
builder ? "()" : ""
412+
).regex.root)
413+
"""
414+
}
415+
416+
let disfavored = arity == 0 ? "@_disfavoredOverload\n" : ""
417+
let genericParams: String = {
418+
var result = ""
419+
if arity > 0 {
420+
result += "W"
421+
result += (0..<arity).map { ", C\($0)" }.joined()
422+
result += ", "
423+
}
424+
result += "Component: \(regexComponentProtocolName)"
425+
return result
426+
}()
427+
let captures = (0..<arity).map { "C\($0)" }
428+
let capturesJoined = captures.joined(separator: ", ")
429+
let matchType = arity == 0
430+
? baseMatchTypeName
431+
: "(\(baseMatchTypeName), \(capturesJoined))"
432+
let whereClauseForInit = "where \(outputAssociatedTypeName) == \(matchType)" +
433+
(arity == 0 ? "" : ", Component.\(outputAssociatedTypeName) == (W, \(capturesJoined))")
434+
435+
output("""
436+
extension \(groupName) {
437+
\(disfavored)\
438+
public init<\(genericParams)>(
439+
_ component: Component
440+
) \(whereClauseForInit) {
441+
self.init(node: \(node(builder: false)))
442+
}
443+
}
444+
445+
extension \(groupName) {
446+
\(disfavored)\
447+
public init<\(genericParams)>(
448+
@\(concatBuilderName) _ component: () -> Component
449+
) \(whereClauseForInit) {
450+
self.init(node: \(node(builder: true)))
451+
}
452+
}
453+
454+
""")
455+
}
456+
396457

397458
func emitRepeating(arity: Int) {
398459
assert(arity >= 0)

0 commit comments

Comments
 (0)