diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 56d1bdf605..029e3b8325 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -1385,6 +1385,71 @@ static int latin1nicmp(const char *lhsChar, qsizetype lSize, const char *rhsChar } return lencmp(lSize, rSize); } + +namespace { + +template +inline uchar latin1_fold(const uchar c) +{ + return c; +} + +template<> +inline uchar latin1_fold(const uchar c) +{ + return latin1Lower[c]; +} + +template +inline void bm_latin1_init_skiptable(const uchar *cc, qsizetype len, uchar *skiptable) +{ + int l = int(qMin(len, qsizetype(255))); + memset(skiptable, l, 256 * sizeof(uchar)); + cc += len - l; + while (l--) + skiptable[latin1_fold(*cc++)] = l; +} + +template +inline qsizetype bm_latin1_find(const uchar *cc, qsizetype l, qsizetype index, const uchar *puc, + qsizetype pl, const uchar *skiptable) +{ + if (pl == 0) + return index > l ? -1 : index; + const qsizetype pl_minus_one = pl - 1; + + const uchar *current = cc + index + pl_minus_one; + const uchar *end = cc + l; + + while (current < end) { + qsizetype skip = skiptable[latin1_fold(*current)]; + if (!skip) { + // possible match + while (skip < pl) { + if (latin1_fold(*(current - skip)) != latin1_fold(puc[pl_minus_one - skip])) + break; + skip++; + } + if (skip > pl_minus_one) // we have a match + return (current - cc) - skip + 1; + + // in case we don't have a match we are a bit inefficient as we only skip by one + // when we have the non matching char in the string. + if (skiptable[latin1_fold(*(current - skip))] == pl) + skip = pl - skip; + else + skip = 1; + } + if (current > end - skip) + break; + current += skip; + } + + return -1; // not found +} + +} + bool QtPrivate::equalStrings(QStringView lhs, QStringView rhs) noexcept { return ucstreq(lhs.utf16(), lhs.size(), rhs.utf16(), rhs.size()); @@ -10752,28 +10817,12 @@ qsizetype QtPrivate::findString(QLatin1StringView haystack, qsizetype from, QLat } return -1; } - -#ifdef __cpp_lib_boyer_moore_searcher - const auto ciHasher = [](char a) { return latin1Lower[uchar(a)]; }; - const auto ciEqual = [](char a, char b) { - return latin1Lower[uchar(a)] == latin1Lower[uchar(b)]; - }; - const auto it = - std::search(haystack.begin() + from, haystack.end(), - std::boyer_moore_searcher(needle.begin(), needle.end(), ciHasher, ciEqual)); - return it == haystack.end() ? -1 : std::distance(haystack.begin(), it); -#else - QVarLengthArray h(adjustedSize); - const auto begin = haystack.end() - adjustedSize; - qt_from_latin1(h.data(), begin, adjustedSize); - QVarLengthArray n(needle.size()); - qt_from_latin1(n.data(), needle.latin1(), needle.size()); - qsizetype res = QtPrivate::findString(QStringView(h.constData(), h.size()), 0, - QStringView(n.constData(), n.size()), cs); - if (res == -1) - return -1; - return res + std::distance(haystack.begin(), begin); -#endif + uchar skiptable[256]; + bm_latin1_init_skiptable( + reinterpret_cast(needle.begin()), needle.size(), skiptable); + return bm_latin1_find( + reinterpret_cast(haystack.begin()), haystack.size(), from, + reinterpret_cast(needle.begin()), needle.size(), skiptable); } qsizetype QtPrivate::lastIndexOf(QStringView haystack, qsizetype from, QStringView needle, Qt::CaseSensitivity cs) noexcept diff --git a/tests/auto/corelib/text/qlatin1stringview/tst_qlatin1stringview.cpp b/tests/auto/corelib/text/qlatin1stringview/tst_qlatin1stringview.cpp index 17454c8cc9..14dee9d58d 100644 --- a/tests/auto/corelib/text/qlatin1stringview/tst_qlatin1stringview.cpp +++ b/tests/auto/corelib/text/qlatin1stringview/tst_qlatin1stringview.cpp @@ -31,6 +31,8 @@ private Q_SLOTS: void relationalOperators_data(); void relationalOperators(); void count(); + void indexOf_data(); + void indexOf(); }; void tst_QLatin1StringView::constExpr() @@ -410,6 +412,108 @@ void tst_QLatin1StringView::count() QCOMPARE("a\0b"_L1.count(QChar::SpecialCharacter::LineSeparator), 0); } +void tst_QLatin1StringView::indexOf_data() +{ + using namespace Qt::Literals::StringLiterals; + + QTest::addColumn("needle"); + QTest::addColumn("haystack"); + QTest::addColumn("from"); + QTest::addColumn("indexCaseSensitive"); + QTest::addColumn("indexCaseInsensitive"); + + // Should never trigger Boyer Moore algorithm + QTest::newRow("Single letter match start") + << QLatin1StringView("A") << QLatin1StringView("ABCDEF") << 0 << 0 << 0; + QTest::newRow("Single letter match second letter") + << QLatin1StringView("B") << QLatin1StringView("ABCDEF") << 0 << 1 << 1; + QTest::newRow("Single letter mismatch") + << QLatin1StringView("G") << QLatin1StringView("ABCDEF") << 0 << -1 << -1; + QTest::newRow("Single letter case sensitive start") + << QLatin1StringView("a") << QLatin1StringView("ABCDEF") << 0 << -1 << 0; + QTest::newRow("Single letter case sensitive end") + << QLatin1StringView("f") << QLatin1StringView("ABCDEF") << 0 << -1 << 5; + QTest::newRow("Single letter different match depending on case") + << QLatin1StringView("a") << QLatin1StringView("ABCabc") << 0 << 3 << 0; + QTest::newRow("Single letter different match depending on case from 2") + << QLatin1StringView("a") << QLatin1StringView("ABCABCabc") << 2 << 6 << 3; + QTest::newRow("Single letter negative from") + << QLatin1StringView("a") << QLatin1StringView("abcabc") << -3 << 3 << 3; + QTest::newRow("Single letter non-ASCII") // searching for "ø" in "Øø" + << "\xf8"_L1 + << "\xd8\xf8"_L1 << 0 << 1 << 0; + QTest::newRow("Single uppercase letter") + << QLatin1StringView("A") << QLatin1StringView("aA") << 0 << 1 << 0; + + // Might trigger Boyer Moore algorithm + QTest::newRow("Small match start") + << QLatin1StringView("ABC") << QLatin1StringView("ABCDEF") << 0 << 0 << 0; + QTest::newRow("Small match second letter") + << QLatin1StringView("BCD") << QLatin1StringView("ABCDEF") << 0 << 1 << 1; + QTest::newRow("Small mismatch") + << QLatin1StringView("EFG") << QLatin1StringView("ABCDEF") << 0 << -1 << -1; + QTest::newRow("Small case sensitive start") + << QLatin1StringView("abc") << QLatin1StringView("ABCDEF") << 0 << -1 << 0; + QTest::newRow("Small case sensitive end") + << QLatin1StringView("DEF") << QLatin1StringView("abcdef") << 0 << -1 << 3; + QTest::newRow("Small different match depending on case") + << QLatin1StringView("abcabc") << QLatin1StringView("!!ABCabcabcABC") << 0 << 5 << 2; + QTest::newRow("Small different match depending on case from 2") + << QLatin1StringView("abcabc") << QLatin1StringView("ABCABCabcabcABC") << 2 << 6 << 3; + QTest::newRow("Small negative from") << QLatin1StringView("negative") + << QLatin1StringView("negativenegative") << -8 << 8 << 8; + QTest::newRow("Small non-ASCII") // searching for "løve" in "LØVEløve" + << "l\xf8ve"_L1 + << "L\xd8VEl\xf8ve"_L1 << 0 << 4 << 0; + QTest::newRow("Small skip test") + << QLatin1StringView("ABBB") << QLatin1StringView("ABABBB") << 0 << 2 << 2; + QTest::newRow("Small uppercase needle") + << QLatin1StringView("ABCDEF") << QLatin1StringView("abcdefABCDEF") << 0 << 6 << 0; + + // Should trigger Boyer Moore algorithm + QTest::newRow("Medium match start") + << QLatin1StringView("ABCDEFGHIJKLMNOP") + << QLatin1StringView("ABCDEFGHIJKLMNOPQRSTUVWXYZ") << 0 << 0 << 0; + QTest::newRow("Medium match second letter") + << QLatin1StringView("BCDEFGHIJKLMNOPQ") + << QLatin1StringView("ABCDEFGHIJKLMNOPQRSTUVWXYZ") << 0 << 1 << 1; + QTest::newRow("Medium mismatch") + << QLatin1StringView("PONMLKJIHGFEDCBA") + << QLatin1StringView("ABCDEFGHIJKLMNOPQRSTUVWXYZ") << 0 << -1 << -1; + QTest::newRow("Medium case sensitive start") + << QLatin1StringView("abcdefghijklmnopq") + << QLatin1StringView("ABCDEFGHIJKLMNOPQRSTUVWXYZ") << 0 << -1 << 0; + QTest::newRow("Medium case sensitive second letter") + << QLatin1StringView("BCDEFGHIJKLMNOPQR") + << QLatin1StringView("abcdefghijklmnopqrstuvxyz") << 0 << -1 << 1; + QTest::newRow("Medium different match depending on case") + << QLatin1StringView("testingtesting") + << QLatin1StringView("TESTINGtestingtestingTESTING") << 0 << 7 << 0; + QTest::newRow("Medium different match depending on case from 2") + << QLatin1StringView("testingtesting") + << QLatin1StringView("TESTINGTESTINGtestingtestingTESTING") << 2 << 14 << 7; + QTest::newRow("Medium negative from") + << QLatin1StringView("abcdefghijklmnop") + << QLatin1StringView("abcdefghijklmnopabcdefghijklmnop") << -16 << 16 << 16; + QTest::newRow("Medium non-ASCII") // searching for "dampfschiffahrtsgesellschaftskapitän" + << "dampfschiffahrtsgesellschaftskapit\xe4n"_L1 + << "DAMPFSCHIFFAHRTSGESELLSCHAFTSKAPIT\xc4Ndampfschiffahrtsgesellschaftskapit\xe4n"_L1 + << 0 << 36 << 0; + QTest::newRow("Medium skip test") << QLatin1StringView("ABBBBBBBBBBBBBBB") + << QLatin1StringView("ABABBBBBBBBBBBBBBB") << 0 << 2 << 2; +} + +void tst_QLatin1StringView::indexOf() +{ + QFETCH(QLatin1StringView, needle); + QFETCH(QLatin1StringView, haystack); + QFETCH(int, from); + QFETCH(int, indexCaseSensitive); + QFETCH(int, indexCaseInsensitive); + QCOMPARE(haystack.indexOf(needle, from, Qt::CaseSensitive), (qsizetype)indexCaseSensitive); + QCOMPARE(haystack.indexOf(needle, from, Qt::CaseInsensitive), (qsizetype)indexCaseInsensitive); +} + QTEST_APPLESS_MAIN(tst_QLatin1StringView) #include "tst_qlatin1stringview.moc"