Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions cpp/src/strings/regex/glushkov_regcomp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,29 +278,23 @@ 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
* alternative when both are active.
*/
bool frontier_has_priority_conflict(std::vector<frontier_item> 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) {
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/strings/regex/glushkov_regcomp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 30 additions & 1 deletion cpp/tests/strings/split_tests.cpp
Original file line number Diff line number Diff line change
@@ -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
*/

Expand Down Expand Up @@ -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<cudf::string_view>;

{
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<char const*> h_strings{" Héllo\tthesé", nullptr, "are\nsome ", "tést\rString", ""};
Expand Down
Loading