allow replacement with capture groups even if not all are participating in the match #7

Open
opened 2026-02-04 12:50:24 +00:00 by krystian.nowak · 0 comments

Currently when trying to use string replacement like in:
https://regex101.com/r/UFsDMr/1
so having regular expression:

^(BRAVO-(GOLF)|(ALPHA|BRAVO|CHARLIE|DELTA|ECHO|FOXTROT)).*

and test string:

BRAVO-CHARLIE something

and replacement:

${2}${3}

the following runtime error is thrown:

panic: runtime error: unsafe.Slice: ptr is nil and len is not zero [recovered, repanicked]

goroutine 11 [running]:
testing.tRunner.func1.2({0x104462a20, 0x104492aa0})
	/opt/homebrew/opt/go/libexec/src/testing/testing.go:1872 +0x190
testing.tRunner.func1()
	/opt/homebrew/opt/go/libexec/src/testing/testing.go:1875 +0x31c
panic({0x104462a20?, 0x104492aa0?})
	/opt/homebrew/opt/go/libexec/src/runtime/panic.go:783 +0x120
go.elara.ws/pcre.ConvertGlob({0x1043f40b3, 0x7})
	/somemydir/pcre/glob.go:56 +0x1f0
go.elara.ws/pcre.CompileGlob({0x1043f40b3?, 0x11e1add08?})
	/somemydir/pcre/glob.go:66 +0x20
go.elara.ws/pcre_test.TestCompileGlob(0x14000186a80)

as capture group not participating in the match are still tried to be accessed, but they should be allowed and if their value is not set should not produce any replacement value (= they should be an empty string).

This PR https://github.com/Elara6331/pcre/pull/3/changes fixes that incorrect behaviour, so that the above regular expression replacement results in expected:

BRAVO

string.

Inlining the code from the above PR from GH:

From 3bc9fd06dfe3e35866c73aad917001e5a3a389c6 Mon Sep 17 00:00:00 2001
From: Krystian Nowak <Krystian.Nowak@gmail.com>
Date: Tue, 13 Jan 2026 12:33:08 +0100
Subject: [PATCH] allow replacement with capture groups even if not all are
 participating in the match

---
 pcre.go      |  5 +++++
 pcre_test.go | 11 +++++++++++
 2 files changed, 16 insertions(+)

diff --git a/pcre.go b/pcre.go
index 6ed89f5..305c6f8 100644
--- a/pcre.go
+++ b/pcre.go
@@ -417,6 +417,11 @@ func (r *Regexp) ReplaceAll(src, repl []byte) []byte {
 				return ""
 			}
 
+			// If the capture group didn't participate in the match, return empty string
+			if match[2*i] == Unset {
+				return ""
+			}
+
 			// Return match
 			return string(src[match[2*i]:match[(2*i)+1]])
 		})
diff --git a/pcre_test.go b/pcre_test.go
index 4617413..4a4c61c 100644
--- a/pcre_test.go
+++ b/pcre_test.go
@@ -96,6 +96,17 @@ func TestReplace(t *testing.T) {
 	}
 }
 
+func TestReplaceAllStringWithNonParticipatingCaptureGroupReplacement(t *testing.T) {
+  r := pcre.MustCompile(`^(BRAVO-(GOLF)|(ALPHA|BRAVO|CHARLIE|DELTA|ECHO|FOXTROT)).*`)
+  defer r.Close()
+
+  testStr := "BRAVO-CHARLIE something"
+  newStr := r.ReplaceAllString(testStr, "${2}${3}")
+  if newStr != "BRAVO" {
+    t.Errorf(`expected "BRAVO", got "%s"`, newStr)
+  }
+}
+
 func TestSplit(t *testing.T) {
 	r := pcre.MustCompile("a*")
 	defer r.Close()
Currently when trying to use string replacement like in: https://regex101.com/r/UFsDMr/1 so having regular expression: ``` ^(BRAVO-(GOLF)|(ALPHA|BRAVO|CHARLIE|DELTA|ECHO|FOXTROT)).* ``` and test string: ``` BRAVO-CHARLIE something ``` and replacement: ``` ${2}${3} ``` the following runtime error is thrown: ``` panic: runtime error: unsafe.Slice: ptr is nil and len is not zero [recovered, repanicked] goroutine 11 [running]: testing.tRunner.func1.2({0x104462a20, 0x104492aa0}) /opt/homebrew/opt/go/libexec/src/testing/testing.go:1872 +0x190 testing.tRunner.func1() /opt/homebrew/opt/go/libexec/src/testing/testing.go:1875 +0x31c panic({0x104462a20?, 0x104492aa0?}) /opt/homebrew/opt/go/libexec/src/runtime/panic.go:783 +0x120 go.elara.ws/pcre.ConvertGlob({0x1043f40b3, 0x7}) /somemydir/pcre/glob.go:56 +0x1f0 go.elara.ws/pcre.CompileGlob({0x1043f40b3?, 0x11e1add08?}) /somemydir/pcre/glob.go:66 +0x20 go.elara.ws/pcre_test.TestCompileGlob(0x14000186a80) ``` as capture group not participating in the match are still tried to be accessed, but they should be allowed and if their value is not set should not produce any replacement value (= they should be an empty string). This PR https://github.com/Elara6331/pcre/pull/3/changes fixes that incorrect behaviour, so that the above regular expression replacement results in expected: ``` BRAVO ``` string. Inlining the code from the above PR from GH: ``` From 3bc9fd06dfe3e35866c73aad917001e5a3a389c6 Mon Sep 17 00:00:00 2001 From: Krystian Nowak <Krystian.Nowak@gmail.com> Date: Tue, 13 Jan 2026 12:33:08 +0100 Subject: [PATCH] allow replacement with capture groups even if not all are participating in the match --- pcre.go | 5 +++++ pcre_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/pcre.go b/pcre.go index 6ed89f5..305c6f8 100644 --- a/pcre.go +++ b/pcre.go @@ -417,6 +417,11 @@ func (r *Regexp) ReplaceAll(src, repl []byte) []byte { return "" } + // If the capture group didn't participate in the match, return empty string + if match[2*i] == Unset { + return "" + } + // Return match return string(src[match[2*i]:match[(2*i)+1]]) }) diff --git a/pcre_test.go b/pcre_test.go index 4617413..4a4c61c 100644 --- a/pcre_test.go +++ b/pcre_test.go @@ -96,6 +96,17 @@ func TestReplace(t *testing.T) { } } +func TestReplaceAllStringWithNonParticipatingCaptureGroupReplacement(t *testing.T) { + r := pcre.MustCompile(`^(BRAVO-(GOLF)|(ALPHA|BRAVO|CHARLIE|DELTA|ECHO|FOXTROT)).*`) + defer r.Close() + + testStr := "BRAVO-CHARLIE something" + newStr := r.ReplaceAllString(testStr, "${2}${3}") + if newStr != "BRAVO" { + t.Errorf(`expected "BRAVO", got "%s"`, newStr) + } +} + func TestSplit(t *testing.T) { r := pcre.MustCompile("a*") defer r.Close() ```
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Elara6331/pcre#7