From b708dbdd0f1bd4d46e56fd996c1d023c87ba2595 Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:45:24 +0000 Subject: [PATCH] Leave {0}-quantified captures unset instead of empty Groups quantified {0} or {0,0} never run, so nested captures must stay nil. Skipping the body was still emitting begin/end capture, which stored an empty match at the current position. Fixes #902. --- .../ByteCodeGen+DSLList.swift | 54 +++++++++++++++---- Tests/RegexTests/MatchTests.swift | 21 ++++++++ 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Sources/_StringProcessing/ByteCodeGen+DSLList.swift b/Sources/_StringProcessing/ByteCodeGen+DSLList.swift index 4d1290cd0..0eca86a89 100644 --- a/Sources/_StringProcessing/ByteCodeGen+DSLList.swift +++ b/Sources/_StringProcessing/ByteCodeGen+DSLList.swift @@ -424,7 +424,10 @@ fileprivate extension Compiler.ByteCodeGen { } switch (low, high) { case (_, 0): - try skipNode(&list) + // `{0}` / `{0,0}` never runs the operand. Drop the body, but still + // allocate any nested captures so they remain unset (nil) rather than + // recording an empty match at the current position. + try skipNode(&list, preservingCaptures: false, allocatingCaptures: true) return case let (n, m?) where n > m: // TODO: Should error out earlier, maybe DSL and parser @@ -848,18 +851,25 @@ fileprivate extension Compiler.ByteCodeGen { extension Compiler.ByteCodeGen { mutating func skipNode( _ list: inout ArraySlice, - preservingCaptures: Bool = true + preservingCaptures: Bool = true, + allocatingCaptures: Bool = false ) throws { guard let node = list.popFirst() else { return } switch node { case let .orderedChoice(n): for _ in 0..