diff --git a/cpp/src/strings/regex/glushkov_regcomp.cpp b/cpp/src/strings/regex/glushkov_regcomp.cpp index cb740d0cb9af..8d06aa980208 100644 --- a/cpp/src/strings/regex/glushkov_regcomp.cpp +++ b/cpp/src/strings/regex/glushkov_regcomp.cpp @@ -278,9 +278,9 @@ bool positions_chars_overlap(gkprog const& gp, uint32_t const p, uint32_t const * Glushkov's bit-order cannot represent. * * Two rules: - * Rule 1 – END before char: an ACCEPT item appears before the first CHAR_POS - * item in Thompson priority order → the pattern can empty-match in a way - * that priority_kill cannot handle correctly. + * Rule 1 – ACCEPT before later char: an ACCEPT item appears before a CHAR_POS + * item in Thompson priority order → the accepted path has higher + * priority than a continuation that Glushkov cannot kill correctly. * Rule 2 – non-monotone gpos + char overlap: two CHAR_POS items appear with * the higher-priority one at a larger gpos (inverted bit order), AND * they can match a common character → priority_kill picks the wrong @@ -288,19 +288,13 @@ bool positions_chars_overlap(gkprog const& gp, uint32_t const p, uint32_t const */ bool frontier_has_priority_conflict(std::vector const& items, gkprog const& gp) { - // Rule 1: ACCEPT before any CHAR_POS, but only when the frontier also - // contains at least one CHAR_POS. An ACCEPT-only frontier (the normal - // "end of pattern" case) is not a priority conflict. - bool seen_char = false; - bool accept_before_char = false; + // Rule 1: ACCEPT before a later CHAR_POS. A frontier ending in ACCEPT (the + // normal "end of pattern" case) is not a priority conflict. + bool seen_accept = false; for (auto const& item : items) { - if (item.kind == frontier_item::CHAR_POS) { - seen_char = true; - } else if (item.kind == frontier_item::ACCEPT && !seen_char) { - accept_before_char = true; - } + if (item.kind == frontier_item::ACCEPT) { seen_accept = true; } + if (item.kind == frontier_item::CHAR_POS && seen_accept) { return true; } } - if (accept_before_char && seen_char) { return true; } // Rule 2: non-monotone gpos pair with character overlap for (size_t i = 0; i < items.size(); ++i) { diff --git a/cpp/src/strings/regex/glushkov_regcomp.hpp b/cpp/src/strings/regex/glushkov_regcomp.hpp index 32c306520c25..6d7450aef070 100644 --- a/cpp/src/strings/regex/glushkov_regcomp.hpp +++ b/cpp/src/strings/regex/glushkov_regcomp.hpp @@ -102,6 +102,8 @@ struct gkprog { * - Pattern has more than GLUSHKOV_MAX_STATES character-consuming positions. * - Pattern is nullable (matches the empty string): priority semantics cannot * be faithfully represented without an ε-position for the empty match. + * - Pattern has a Thompson-priority frontier that Glushkov's bit ordering + * cannot represent faithfully. * * @param prog Compiled Thompson NFA (after reprog::finalize()). * @return Host-side Glushkov program, or nullptr on failure. diff --git a/cpp/tests/strings/split_tests.cpp b/cpp/tests/strings/split_tests.cpp index cacd98328844..ce9fd7a8b10e 100644 --- a/cpp/tests/strings/split_tests.cpp +++ b/cpp/tests/strings/split_tests.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -517,6 +517,35 @@ TEST_F(StringsSplitTest, SplitRecordRegex) } } +TEST_F(StringsSplitTest, SplitRecordRegexLazyQuantifier) +{ + auto const input = cudf::test::strings_column_wrapper({"\rbaab\r\ra"}); + auto const sv = cudf::strings_column_view(input); + using LCW = cudf::test::lists_column_wrapper; + + { + LCW expected({LCW{"\rbaa", "\ra"}}); + auto const prog = + cudf::strings::regex_program::create("[^ \v\n\t\r\f]\\r+?\\n*", + cudf::strings::regex_flags::EXT_NEWLINE, + cudf::strings::capture_groups::NON_CAPTURE); + auto const result = cudf::strings::split_record_re(sv, *prog); + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(result->view(), expected); + } + + { + LCW expected({LCW{"\rbaa", "a"}}); + auto const prog = + cudf::strings::regex_program::create("[^ \v\n\t\r\f]\\r+\\n*", + cudf::strings::regex_flags::EXT_NEWLINE, + cudf::strings::capture_groups::NON_CAPTURE); + auto const result = cudf::strings::split_record_re(sv, *prog); + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(result->view(), expected); + } +} + TEST_F(StringsSplitTest, SplitRegexWithMaxSplit) { std::vector h_strings{" Héllo\tthesé", nullptr, "are\nsome ", "tést\rString", ""};