Add Latin 1 case-insensitive Boyer-Moore searcher
The std::boyer_moore_searcher is buggy for older verions of Microsoft's STL, and missing in AppleClang's libc++ with an inefficient fall back. Fixes: QTBUG-100236 Change-Id: Ic3cc916946546d2ef78456cd15e1425d957b989d Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>bb10
parent
26a73e1b31
commit
15368fb31b
|
|
@ -1385,6 +1385,71 @@ static int latin1nicmp(const char *lhsChar, qsizetype lSize, const char *rhsChar
|
|||
}
|
||||
return lencmp(lSize, rSize);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template<Qt::CaseSensitivity cs>
|
||||
inline uchar latin1_fold(const uchar c)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline uchar latin1_fold<Qt::CaseSensitivity::CaseInsensitive>(const uchar c)
|
||||
{
|
||||
return latin1Lower[c];
|
||||
}
|
||||
|
||||
template<Qt::CaseSensitivity cs>
|
||||
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<cs>(*cc++)] = l;
|
||||
}
|
||||
|
||||
template<Qt::CaseSensitivity cs>
|
||||
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<cs>(*current)];
|
||||
if (!skip) {
|
||||
// possible match
|
||||
while (skip < pl) {
|
||||
if (latin1_fold<cs>(*(current - skip)) != latin1_fold<cs>(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<cs>(*(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<char16_t> h(adjustedSize);
|
||||
const auto begin = haystack.end() - adjustedSize;
|
||||
qt_from_latin1(h.data(), begin, adjustedSize);
|
||||
QVarLengthArray<char16_t> 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<Qt::CaseSensitivity::CaseInsensitive>(
|
||||
reinterpret_cast<const unsigned char *>(needle.begin()), needle.size(), skiptable);
|
||||
return bm_latin1_find<Qt::CaseSensitivity::CaseInsensitive>(
|
||||
reinterpret_cast<const unsigned char *>(haystack.begin()), haystack.size(), from,
|
||||
reinterpret_cast<const unsigned char *>(needle.begin()), needle.size(), skiptable);
|
||||
}
|
||||
|
||||
qsizetype QtPrivate::lastIndexOf(QStringView haystack, qsizetype from, QStringView needle, Qt::CaseSensitivity cs) noexcept
|
||||
|
|
|
|||
|
|
@ -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<QLatin1StringView>("needle");
|
||||
QTest::addColumn<QLatin1StringView>("haystack");
|
||||
QTest::addColumn<int>("from");
|
||||
QTest::addColumn<int>("indexCaseSensitive");
|
||||
QTest::addColumn<int>("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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue