diff --git a/Sources/_StringProcessing/Regex/DSLList.swift b/Sources/_StringProcessing/Regex/DSLList.swift index 3a5164ebd..e18e75cfe 100644 --- a/Sources/_StringProcessing/Regex/DSLList.swift +++ b/Sources/_StringProcessing/Regex/DSLList.swift @@ -125,7 +125,7 @@ extension DSLList { case .concatenation(let count): var position = position + 1 if findLast { - for _ in 0..<(count - 1) { + for _ in (0..= 0 { switch other.nodes[i] { case .concatenation(let count): - other.nodes[i] = .concatenation(count - 1) - break Loop + // Omit a concatenation entirely if it would have zero children. + if count == 1 { + other.nodes[i] = .empty + } else { + other.nodes[i] = .concatenation(count - 1) + } case .limitCaptureNesting, .ignoreCapturesInTypedOutput: other.nodes.remove(at: i) i -= 1 default: - break Loop + return } } } diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index 3611ee2c0..a72a49fc0 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -1673,6 +1673,29 @@ class RegexDSLTests: XCTestCase { """) } + func testManyConcatenatedComponents() throws { + let r = Regex { + #/a\n/# + Regex(verbatim: "/some/path") + #/\n/# + #/b\n/# + Regex(verbatim: "/some/path") + #/\n/# + #/c\n/# + Regex(verbatim: "/some/path") + #/\n/# + #/d\n/# + Regex(verbatim: "/some/path") + #/\n/# + #/e\n/# + Regex(verbatim: "/some/path") + #/\n/# + #/f/# + } + let input = "a\n/some/path\nb\n/some/path\nc\n/some/path\nd\n/some/path\ne\n/some/path\nf" + XCTAssertNotNil(try r.wholeMatch(in: input)) + } + func testRegexComponentBuilderResultType() { // Test that the user can declare a closure or computed property marked with // `@RegexComponentBuilder` with `Regex` as the result type.