Repository :
https://github.com/colorer/Colorer-library
On branch : cregexp_refactoring
Link :
https://github.com/colorer/Colorer-library/commit/c236eeff8904867a9e45e2235d576efb93dfc2e6
>---------------------------------------------------------------
commit c236eeff8904867a9e45e2235d576efb93dfc2e6
Author: Aleksey Dobrunov <
cta...@ctapmex.com>
Date: Sun Aug 16 21:34:46 2026 +0500
Treat CRegExp groups beyond 16 slots as non-capturing so existing HRC still compiles.
>---------------------------------------------------------------
c236eeff8904867a9e45e2235d576efb93dfc2e6
src/colorer/cregexp/cregexp.cpp | 7 ++++-
tests/unit/test_cregexp.cpp | 70 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/src/colorer/cregexp/cregexp.cpp b/src/colorer/cregexp/cregexp.cpp
index 621a58d..a6fb564 100644
--- a/src/colorer/cregexp/cregexp.cpp
+++ b/src/colorer/cregexp/cregexp.cpp
@@ -675,8 +675,11 @@ EError CRegExp::setStructs(SRegInfo*& re, const UnicodeString& expr, int& retPos
brnames[cnMatch] = br_name;
cnMatch++;
}
- else
+ else {
+ // HRC schemes may exceed the slot count; extra groups stay non-capturing
delete br_name;
+ next->param0 = -1;
+ }
#else
#ifdef CHECKNAMES
if (br_name->length() && namedMatches && namedMatches->getItem(br_name)) {
@@ -700,6 +703,8 @@ EError CRegExp::setStructs(SRegInfo*& re, const UnicodeString& expr, int& retPos
next->param0 = cMatch;
cMatch++;
}
+ else
+ next->param0 = -1;
i += 1;
}
next->un.param = new SRegInfo;
diff --git a/tests/unit/test_cregexp.cpp b/tests/unit/test_cregexp.cpp
index 8b7af25..fabff78 100644
--- a/tests/unit/test_cregexp.cpp
+++ b/tests/unit/test_cregexp.cpp
@@ -39,6 +39,28 @@ void require_no_match(const char16_t* pattern, const char16_t* text, bool moves
REQUIRE_FALSE(parse_re(re, str, match, moves, pos));
}
+UnicodeString sequential_captures(int inner)
+{
+ UnicodeString pattern(u"/a");
+ for (int i = 0; i < inner; i++) {
+ pattern += UnicodeString(u"(x)");
+ }
+ pattern += UnicodeString(u"z/");
+ return pattern;
+}
+
+UnicodeString named_captures(int count)
+{
+ UnicodeString pattern(u"/");
+ for (int i = 0; i < count; i++) {
+ pattern += UnicodeString(u"(?{n");
+ pattern += UStr::to_unistr(i);
+ pattern += UnicodeString(u"}x)");
+ }
+ pattern += UnicodeString(u"/");
+ return pattern;
+}
+
} // namespace
TEST_CASE("CRegExp compilation", "[cregexp]")
@@ -279,6 +301,54 @@ TEST_CASE("CRegExp groups and backreferences", "[cregexp]")
REQUIRE(match.e[2] == 2);
}
+ SECTION("numeric groups beyond the slot limit stay non-capturing")
+ {
+ const auto ok_pattern = sequential_captures(MATCHES_NUM - 1);
+ UnicodeString ok_text(u"a");
+ for (int i = 0; i < MATCHES_NUM - 1; i++) {
+ ok_text += UnicodeString(u"x");
+ }
+ ok_text += UnicodeString(u"z");
+ CRegExp ok_re(&ok_pattern);
+ REQUIRE(ok_re.isOk());
+ SMatches ok_match;
+ REQUIRE(ok_re.parse(&ok_text, &ok_match));
+ REQUIRE(ok_match.cMatch == MATCHES_NUM);
+ REQUIRE(ok_match.s[0] == 0);
+ REQUIRE(ok_match.e[0] == ok_text.length());
+
+ const auto overflow = sequential_captures(MATCHES_NUM);
+ UnicodeString overflow_text(u"a");
+ for (int i = 0; i < MATCHES_NUM; i++) {
+ overflow_text += UnicodeString(u"x");
+ }
+ overflow_text += UnicodeString(u"z");
+ CRegExp overflow_re(&overflow);
+ REQUIRE(overflow_re.isOk());
+ SMatches overflow_match;
+ REQUIRE(overflow_re.parse(&overflow_text, &overflow_match));
+ REQUIRE(overflow_match.cMatch == MATCHES_NUM);
+ REQUIRE(overflow_match.s[0] == 0);
+ REQUIRE(overflow_match.e[0] == overflow_text.length());
+ }
+
+ SECTION("named groups beyond the slot limit stay non-capturing")
+ {
+ const auto ok_pattern = named_captures(NAMED_MATCHES_NUM);
+ CRegExp ok_re(&ok_pattern);
+ REQUIRE(ok_re.isOk());
+
+ const auto overflow = named_captures(NAMED_MATCHES_NUM + 1);
+ CRegExp overflow_re(&overflow);
+ REQUIRE(overflow_re.isOk());
+ const auto last_name_n = UStr::to_unistr(NAMED_MATCHES_NUM);
+ UnicodeString last_name(u"n");
+ last_name += last_name_n;
+ REQUIRE(overflow_re.getBracketNo(&last_name) == -1);
+ const auto first_name = ustr(u"n0");
+ REQUIRE(overflow_re.getBracketNo(&first_name) == 0);
+ }
+
SECTION("non-capturing (?:) does not create a group")
{
const auto pre = ustr(u"/(?:ab)c/");