Repository: rapidsai/cudf (language: python)

Issue:
[BUG] 0 range regexp appear to be broken
**Describe the bug**
I recently was testing some spark cases and ran into some failures related to 0 range patterns.

- `A{0,}` for replaceRegexp NON_CAPTURE
- `A{0,5}` for replaceRegexp NON_CAPTURE
- `[a0-9]{0,2}` for replaceRegexp NON_CAPTURE
- `(?:ab){0,3}` for containsRe NON_CAPTURE


These were for  the java APIs, but it should apply to python too. The patch https://github.com/rapidsai/cudf/pull/16798 appears to have caused this some how.

The differences in replace appear to show that it no longer honors the 0 in the range some of the time. For example the pattern `A{0,}` being replaced with PROD for an input of 'TEST A' produces 'TEST PROD'. But before it would match everywhere and produce 'PRODTPRODEPRODSPRODTPROD PROD PRODPROD'. I think that is an issue for python too

```python
>>> re.sub("A{0,}","PROD","TEST A")
'PRODTPRODEPRODSPRODTPROD PRODPROD'
```

**Steps/Code to reproduce bug**
The tests failing in Spark are 

```
FAILED ../../src/main/python/regexp_test.py::test_regexp_replace_digit[DATAGEN_SEED=1728593263, TZ=UTC] - AssertionError: GPU and CPU string values are different at [0, 'regexp_repl...
FAILED ../../src/main/python/regexp_test.py::test_re_replace_repetition[DATAGEN_SEED=1728593263, TZ=UTC] - AssertionError: GPU and CPU string values are different at [0, 'regexp_repl...
FAILED ../../src/main/python/regexp_test.py::test_regexp_memory_ok[DATAGEN_SEED=1728593263, TZ=UTC, INJECT_OOM] - AssertionError: GPU and CPU boolean values are different at [0, 'RLIKE(a, (...
```

But the examples above are the cleaned up versions of the tests. 

**Expected behavior**
It should behave like python or java regular expressions.



Discussion / hints:

Fix patch:
diff --git a/cpp/src/strings/regex/regcomp.cpp b/cpp/src/strings/regex/regcomp.cpp
index 775a2580f60..b923a301f84 100644
--- a/cpp/src/strings/regex/regcomp.cpp
+++ b/cpp/src/strings/regex/regcomp.cpp
@@ -710,9 +710,7 @@ class regex_parser {
     std::stack<int> lbra_stack;
     auto repeat_start_index = -1;
 
-    for (std::size_t index = 0; index < in.size(); index++) {
-      auto const item = in[index];
-
+    for (auto const item : in) {
       if (item.type != COUNTED && item.type != COUNTED_LAZY) {
         out.push_back(item);
         if (item.type == LBRA || item.type == LBRA_NC) {
@@ -739,7 +737,7 @@ class regex_parser {
         auto const m = item.d.count.m;  // maximum count
         assert(n >= 0 && "invalid repeat count value n");
         // zero-repeat edge-case: need to erase the previous items
-        if (n == 0 && m == 0) { out.erase(begin, end); }
+        if (n == 0) { out.erase(begin, end); }
 
         std::vector<regex_parser::Item> repeat_copy(begin, end);
         // special handling for quantified capture groups


Test patch:
diff --git a/cpp/tests/strings/contains_tests.cpp b/cpp/tests/strings/contains_tests.cpp
index 216ddfce5f1..cceec1d3537 100644
--- a/cpp/tests/strings/contains_tests.cpp
+++ b/cpp/tests/strings/contains_tests.cpp
@@ -474,6 +474,40 @@ TEST_F(StringsContainsTests, FixedQuantifier)
   }
 }
 
+TEST_F(StringsContainsTests, ZeroRangeQuantifier)
+{
+  auto input = cudf::test::strings_column_wrapper({"a", "", "abc", "XYAZ", "ABC", "ZYXA"});
+  auto sv    = cudf::strings_column_view(input);
+
+  auto pattern = std::string("A{0,}");  // should match everyting
+  auto prog    = cudf::strings::regex_program::create(pattern);
+
+  {
+    auto expected = cudf::test::fixed_width_column_wrapper<bool>({1, 1, 1, 1, 1, 1});
+    auto results  = cudf::strings::contains_re(sv, *prog);
+    CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+  }
+  {
+    auto expected = cudf::test::fixed_width_column_wrapper<cudf::size_type>({2, 1, 4, 5, 4, 5});
+    auto results  = cudf::strings::count_re(sv, *prog);
+    CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+  }
+
+  pattern = std::string("(?:ab){0,3}");
+  prog    = cudf::strings::regex_program::create(pattern);
+
+  {
+    auto expected = cudf::test::fixed_width_column_wrapper<bool>({1, 1, 1, 1, 1, 1});
+    auto results  = cudf::strings::contains_re(sv, *prog);
+    CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+  }
+  {
+    auto expected = cudf::test::fixed_width_column_wrapper<cudf::size_type>({2, 1, 3, 5, 4, 5});
+    auto results  = cudf::strings::count_re(sv, *prog);
+    CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+  }
+}
+
 TEST_F(StringsContainsTests, NestedQuantifier)
 {
   auto input   = cudf::test::strings_column_wrapper({"TEST12 1111 2222 3333 4444 5555",
diff --git a/cpp/tests/strings/replace_regex_tests.cpp b/cpp/tests/strings/replace_regex_tests.cpp
index 9847d8d6bb5..abc12b00a81 100644
--- a/cpp/tests/strings/replace_regex_tests.cpp
+++ b/cpp/tests/strings/replace_regex_tests.cpp
@@ -200,6 +200,34 @@ TEST_F(StringsReplaceRegexTest, ZeroLengthMatch)
   CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(*results, expected);
 }
 
+TEST_F(StringsReplaceRegexTest, ZeroRangeQuantifier)
+{
+  auto input = cudf::test::strings_column_wrapper({"a", "", "123", "XYAZ", "abc", "zéyab"});
+  auto sv    = cudf::strings_column_view(input);
+
+  auto pattern  = std::string("A{0,5}");
+  auto prog     = cudf::strings::regex_program::create(pattern);
+  auto repl     = cudf::string_scalar("_");
+  auto expected = cudf::test::strings_column_wrapper(
+    {"_a_", "_", "_1_2_3_", "_X_Y__Z_", "_a_b_c_", "_z_é_y_a_b_"});
+  auto results = cudf::strings::replace_re(sv, *prog, repl);
+  CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+
+  pattern = std::string("[a0-9]{0,2}");
+  prog    = cudf::strings::regex_program::create(pattern);
+  expected =
+    cudf::test::strings_column_wrapper({"__", "_", "___", "_X_Y_A_Z_", "__b_c_", "_z_é_y__b_"});
+  results = cudf::strings::replace_re(sv, *prog, repl);
+  CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+
+  pattern = std::string("(?:ab){0,3}");
+  prog    = cudf::strings::regex_program::create(pattern);
+  expected =
+    cudf::test::strings_column_wrapper({"_a_", "_", "_1_2_3_", "_X_Y_A_Z_", "__c_", "_z_é_y__"});
+  results = cudf::strings::replace_re(sv, *prog, repl);
+  CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
+}
+
 TEST_F(StringsReplaceRegexTest, Multiline)
 {
   auto const multiline = cudf::strings::regex_flags::MULTILINE;