simstr/src/sstring.cpp

549 lines
22 KiB
C++
Raw Normal View History

2025-04-05 14:21:11 +00:00
/*
* (c) Проект "SimStr", Александр Орефков orefkov@gmail.com
* ver. 1.0
* Реализация строковых функций
*/
#include "simstr/simple_unicode.h"
#include "simstr/sstring.h"
#include <simdutf.h>
namespace simstr {
template class sstring<u8s>;
template class sstring<uws>;
template class sstring<u16s>;
template class sstring<u32s>;
// from sqlite.c
/*
** Notes On Invalid UTF-8:
**
** * This routine never allows a 7-bit character (0x00 through 0x7f) to
** be encoded as a multi-byte character. Any multi-byte character that
** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
**
** * This routine never allows a UTF16 surrogate value to be encoded.
** If a multi-byte character attempts to encode a value between
** 0xd800 and 0xe000 then it is rendered as 0xfffd.
**
** * Bytes in the range of 0x80 through 0xbf which occur as the first
** byte of a character are interpreted as single-byte characters
** and rendered as themselves even though they are technically
** invalid characters.
**
** * This routine accepts over-length UTF8 encodings
** for unicode values 0x80 and greater. It does not change over-length
** encodings to 0xfffd as some systems recommend.
*/
static const uu8s sqlite3Utf8Trans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
};
u32s readUtf8Symbol(const uu8s*& ptr, const uu8s* end) {
u32s us = static_cast<u32s>(*ptr++);
if (us >= 0xC0) {
us = sqlite3Utf8Trans1[us - 0xC0];
while (ptr < end && (*ptr & 0xC0) == 0x80)
us = (us << 6) + (0x3F & *(ptr++));
if (us < 0x80 || (us & 0xFFFFF800) == 0xD800 || (us & 0xFFFFFFFE) == 0xFFFE) {
us = 0xFFFD;
}
}
return us;
}
void writeUtf8Symbol(uu8s*& write, u32s us) {
using u8 = uu8s;
if (us <= 0x80)
*write++ = (u8)us;
else if (us < 0x00800) {
*write++ = 0xC0 + (u8)((us >> 6) & 0x1F);
*write++ = 0x80 + (u8)(us & 0x3F);
} else if (us < 0x10000) {
*write++ = 0xE0 + (u8)((us >> 12) & 0x0F);
*write++ = 0x80 + (u8)((us >> 6) & 0x3F);
*write++ = 0x80 + (u8)(us & 0x3F);
} else {
*write++ = 0xF0 + (u8)((us >> 18) & 0x07);
*write++ = 0x80 + (u8)((us >> 12) & 0x3F);
*write++ = 0x80 + (u8)((us >> 6) & 0x3F);
*write++ = 0x80 + (u8)(us & 0x3F);
}
}
u32s readUtf16Symbol(const u16s*& ptr, const u16s* end) {
u32s us = static_cast<u32s>(*ptr++);
if (us >= 0xD800) {
if (us < 0xDC00 && ptr < end) {
if (u32s s = static_cast<u32s>(*ptr++); s >= 0xDC00 && s < 0xE000) {
return ((us & 0x3ff) << 10) + (s & 0x3ff) + 0x10000;
}
}
return 0xFFFD;
}
return us;
}
void writeUtf16Symbol(u16s*& ptr, u32s s) {
if (s < 0x10000) {
*ptr++ = static_cast<u16s>(s);
} else {
s -= 0x10000;
*ptr++ = 0xD800 + ((s >> 10) & 0x3FF);
*ptr++ = 0xDC00 + (s & 0x3FF);
}
}
size_t utf8len(u32s us) {
if (us <= 0x80)
return 1;
else if (us < 0x00800)
return 2;
else if (us < 0x10000)
return 3;
else
return 4;
}
SIMSTR_API size_t utf_convert_selector<u8s, u16s>::need_len(const u8s*src, size_t srcLen) {
return simdutf::utf16_length_from_utf8(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u8s, u16s>::convert(const u8s* src, size_t srcLen, u16s* dest) {
return simdutf::convert_utf8_to_utf16(src, srcLen, dest);
}
SIMSTR_API size_t utf_convert_selector<u8s, u32s>::need_len(const u8s*src, size_t srcLen) {
return simdutf::utf32_length_from_utf8(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u8s, u32s>::convert(const u8s* src, size_t srcLen, u32s* dest) {
return simdutf::convert_utf8_to_utf32(src, srcLen, dest);
}
SIMSTR_API size_t utf_convert_selector<u16s, u8s>::need_len(const u16s*src, size_t srcLen) {
return simdutf::utf8_length_from_utf16(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u16s, u8s>::convert(const u16s* src, size_t srcLen, u8s* dest) {
return simdutf::convert_utf16_to_utf8(src, srcLen, dest);
}
SIMSTR_API size_t utf_convert_selector<u16s, u32s>::need_len(const u16s*src, size_t srcLen) {
return simdutf::utf32_length_from_utf16(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u16s, u32s>::convert(const u16s* src, size_t srcLen, u32s* dest) {
return simdutf::convert_utf16_to_utf32(src, srcLen, dest);
}
SIMSTR_API size_t utf_convert_selector<u32s, u8s>::need_len(const u32s*src, size_t srcLen) {
return simdutf::utf8_length_from_utf32(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u32s, u8s>::convert(const u32s* src, size_t srcLen, u8s* dest) {
return simdutf::convert_utf32_to_utf8(src, srcLen, dest);
}
SIMSTR_API size_t utf_convert_selector<u32s, u16s>::need_len(const u32s*src, size_t srcLen) {
return simdutf::utf16_length_from_utf32(src, srcLen);
}
SIMSTR_API size_t utf_convert_selector<u32s, u16s>::convert(const u32s* src, size_t srcLen, u16s* dest) {
return simdutf::convert_utf32_to_utf16(src, srcLen, dest);
}
inline u32s makeLowerU(u32s s) {
if (isAsciiUpper(s))
return s | 0x20;
else if (s > 127 && s < 0x10000)
return simpleUnicodeLower(static_cast<uint16_t>(s));
else
return s;
}
inline u32s makeUpperU(u32s s) {
if (isAsciiLower(s))
return s & ~0x20;
else if (s > 127 && s < 0x10000)
return simpleUnicodeUpper(static_cast<uint16_t>(s));
else
return s;
}
inline u32s makeFoldU(u32s s) {
if (isAsciiUpper(s))
return s | 0x20;
else if (s > 127 && s < 0x10000)
return simpleUnicodeFold(static_cast<uint16_t>(s));
else
return s;
}
/*
* Поиск utf-8 symbols, которые меняют свою длину при upper/lower преобразовании
* вот они нашлись
* "İiıIſSȺⱥȾⱦɐⱯɑⱭɫⱢɱⱮɽⱤẞßΙΩωkÅåⱢɫⱤɽⱥȺⱦȾⱭɑⱮɱⱯɐ"
l2u ıIſSɐⱯɑⱭɫⱢɱⱮɽⱤΙⱥȺⱦȾ
u2l İiȺⱥȾⱦẞßΩωkÅåⱢɫⱤɽⱭɑⱮɱⱯɐ
void findStrange() {
u16s badsL2U[100], * pBadslu = badsL2U;
u16s badsU2L[100], * pBadsul = badsU2L;
uu8s buf[20];
size_t allToUpper = 0, allToLower = 0, lowerbadshorter = 0, upperbadshorter = 0, lowerbadlonger = 0, upperbadlonger = 0;
for (u32s s = 128; s <= 0xFFFF; s++) {
u16s upper = makeUpperU(s);
if (upper != s) {
allToUpper++;
uu8s* pTest = buf;
writeUtf8Symbol(pTest, s);
size_t len1 = pTest - buf;
pTest = buf;
writeUtf8Symbol(pTest, upper);
size_t len2 = pTest - buf;
if (len1 != len2) {
*pBadslu++ = s;
*pBadslu++ = upper;
if (len1 > len2) {
upperbadshorter++;
} else if (len1 < len2) {
upperbadlonger++;
}
}
}
u16s lower = makeLowerU(s);
if (lower != s) {
allToLower++;
uu8s* pTest = buf;
writeUtf8Symbol(pTest, s);
size_t len1 = pTest - buf;
pTest = buf;
writeUtf8Symbol(pTest, lower);
size_t len2 = pTest - buf;
if (len1 != len2) {
*pBadsul++ = s;
*pBadsul++ = lower;
if (len1 > len2) {
lowerbadshorter++;
} else if (len1 < len2) {
lowerbadlonger++;
}
}
}
}
*pBadsul = 0;
*pBadslu = 0;
return;
}
*/
template<auto Op>
size_t utf8_case_change(const u8s*& src, size_t len, u8s*& dest, size_t lenBuffer) {
// Допущение для оптимизации - считается, что выделенный буфер записи всегда не меньше длины буфера чтения
// и что буфер записи не начинается внутри буфера чтения
// то есть если символ считан, и длина записываемого символа не больше считанного, то он поместится в буфер записи
// и не перетрет символы, которые еще не прочитали
// По другому работать откажемся
if (lenBuffer < len || (dest > src && dest < src + len))
return len;
const uu8s *beginReadPos = reinterpret_cast<const uu8s*>(src), *readPos = beginReadPos, *endReadPos = beginReadPos + len,
*readFromPos = readPos;
uu8s *beginWritePos = reinterpret_cast<uu8s*>(dest), *writePos = beginWritePos, *endWritePos = writePos + lenBuffer, *tempWrite;
size_t state = 0, writedSymbolLen, needExtraLen = 0;
u32s readedSymbol, writedSymbol;
lstringa<4096> tempStore;
while (readPos < endReadPos) {
readedSymbol = readUtf8Symbol(readPos, endReadPos), writedSymbol = Op(readedSymbol);
writedSymbolLen = utf8len(writedSymbol);
switch (state) {
case 0: // начальное состояние
// Так как выделенный буфер записи всегда не меньше длины буфера чтения
// то если символ считан, и длина записываемого символа не больше считанного,
// то он поместится в буфер записи, можно не проверять
// оптимизация для inplace конвертации в идеальном случае
if (writePos == readFromPos && readedSymbol == writedSymbol) {
writePos = const_cast<uu8s*>(readPos);
readFromPos = readPos;
continue;
}
if (writePos + writedSymbolLen <= endWritePos && (writePos > readPos || writePos + writedSymbolLen <= readPos)) {
// всё отлично
writeUtf8Symbol(writePos, writedSymbol);
} else {
state = 1;
goto state1;
}
break;
case 1:
state1:
// До этого когда-то был считан символ, при записи ставший длиннее.
// Можно его писать, но надо проверять, что записываемый символ:
// - не перезатрёт читаемые
// - не выйдет за границы буфера записи
// - восстановит обычный режим, если записываемый символ короче прочитанного
if (writePos + writedSymbolLen > endWritePos) {
// Записываемый символ не поместится в буфер записи
// Запомним, где это произошло, подсчитаем символы далее
src = reinterpret_cast<const u8s*>(readFromPos);
dest = reinterpret_cast<u8s*>(writePos);
needExtraLen = 0;
state = 2;
goto state2;
} else if (writePos < readPos && writePos + writedSymbolLen > readPos) {
// Совсем плохой случай - записываемый символ перезатрет следующий читаемый
// Запомним, где это произошло, подсчитаем символы далее
src = reinterpret_cast<const u8s*>(readFromPos);
dest = reinterpret_cast<u8s*>(writePos);
needExtraLen = 0;
if (lenBuffer > len) {
// считаем, что это второй вызов, и места в буфере в итоге хватит
// поэтому будем сохранять считанные символы в свой буфер, а потом скопируем их в результат
tempWrite = reinterpret_cast<uu8s*>(tempStore.reserve(lenBuffer - static_cast<size_t>(writePos - beginWritePos)));
state = 3;
goto state3;
} else {
// Это первый вызов, будем просто считать нужное место
state = 2;
goto state2;
}
} else {
// Можем записать прочитанный символ
writeUtf8Symbol(writePos, writedSymbol);
// Возможно, мы выровнялись по прочитанным/записанным байтам, тогда можно снова писать в буфер
// в обычном состоянии
size_t allReaded = static_cast<size_t>(readPos - beginReadPos);
size_t allWrite = static_cast<size_t>(writePos - beginWritePos);
if (allReaded >= allWrite) {
// вернулись в обычное состояние
state = 0;
}
}
break;
case 2:
state2:
// Место в буфере кончилось, просто считаем нужное место
needExtraLen += writedSymbolLen;
break;
case 3:
state3:
// Был прочитан удлинившийся символ, который мог перезатереть читаемые.
// Конец буфера записи не достигнут, но писать туда нельзя.
// При этом известно, что место в буфере выделено
// с нужным запасом, и сейчас мы просто запоминаем считанные символы, чтобы
// потом вписать их когда позволит указатель чтения
if (writePos + writedSymbolLen <= readPos || readPos == endReadPos) {
// Опа, символ стал короче и снова влезает в буфер. Или все прочитали, можно записывать
std::char_traits<u8s>::copy(dest, tempStore.symbols(), needExtraLen);
writeUtf8Symbol(writePos, writedSymbol);
state = 0;
} else {
if (writePos + writedSymbolLen > endWritePos) {
// Таки всё-равно не влезем в буфер
state = 2;
goto state2;
}
writeUtf8Symbol(tempWrite, writedSymbol);
writePos += writedSymbolLen;
needExtraLen += writedSymbolLen;
}
break;
}
readFromPos = readPos;
}
if (state == 0 || state == 1) {
// все отлично, можно возвращать получившуюся длину
src = reinterpret_cast<const u8s*>(readFromPos);
dest = reinterpret_cast<u8s*>(writePos);
return static_cast<size_t>(writePos - beginWritePos);
} else {
size_t writedCount = static_cast<size_t>(dest - (u8s*)beginWritePos);
size_t needBuffer = writedCount + needExtraLen;
if (needBuffer <= lenBuffer) {
// значит попали во 2ое состояние и считали символы, так как перезатирали читаемые
// но видимо потом попались укоротившиеся и по итогу всё-равно все влезет, только
// нужно временно сохранять символы. Запустим еще раз
size_t readedCount = static_cast<size_t>((uu8s*)src - beginReadPos);
len -= readedCount;
lenBuffer -= writedCount; // по идее они должны быть одинаковые
utf8_case_change<Op>(src, len, dest, lenBuffer + 1);
}
// в dest и src уже сохранены последние позиции. Вернем нужную длину
return needBuffer;
}
}
SIMSTR_API size_t unicode_traits<u8s>::upper(const u8s*& src, size_t len, u8s*& dest, size_t lenBuffer) {
return utf8_case_change<makeUpperU>(src, len, dest, lenBuffer);
}
SIMSTR_API size_t unicode_traits<u8s>::lower(const u8s*& src, size_t len, u8s*& dest, size_t lenBuffer) {
return utf8_case_change<makeLowerU>(src, len, dest, lenBuffer);
}
template<auto Op>
void utf16_change_case(const u16s* src, size_t len, u16s* dest) {
if (dest != src) {
// не inplace - надо писать все символы
for (size_t l = 0; l < len; l++)
dest[l] = static_cast<u16s>(Op(src[l]));
} else {
// inplace - будем писать только измененные символы
for (size_t l = 0; l < len; l++) {
u16s s = src[l], s1 = static_cast<u16s>(Op(s));
if (s != s1)
dest[l] = s1;
}
}
}
void unicode_traits<u16s>::upper(const u16s* src, size_t len, u16s* dest) {
utf16_change_case<makeUpperU>(src, len, dest);
}
void unicode_traits<u16s>::lower(const u16s* src, size_t len, u16s* dest) {
utf16_change_case<makeLowerU>(src, len, dest);
}
template<auto Op>
void utf32_change_case(const u32s* src, size_t len, u32s* dest) {
if (dest != src) {
// не inplace - надо писать все символы
for (size_t l = 0; l < len; l++)
*dest++ = Op(*src++);
} else {
// inplace - будем писать только измененные символы
for (size_t l = 0; l < len; l++, src++, dest++) {
u32s s = *src, s1 = Op(s);
if (s != s1)
*dest = s1;
}
}
}
SIMSTR_API void unicode_traits<u32s>::upper(const u32s* src, size_t len, u32s* dest) {
utf32_change_case<makeUpperU>(src, len, dest);
}
SIMSTR_API void unicode_traits<u32s>::lower(const u32s* src, size_t len, u32s* dest) {
utf32_change_case<makeLowerU>(src, len, dest);
}
SIMSTR_API int unicode_traits<u8s>::compareiu(const u8s* text1, size_t len1, const u8s* text2, size_t len2) {
if (!len1) {
return len2 == 0 ? 0 : -1;
} else if (!len2)
return 1;
const uu8s *ptr1 = reinterpret_cast<const uu8s*>(text1), *ptr2 = reinterpret_cast<const uu8s*>(text2),
*ptr1End = ptr1 + len1, *ptr2End = ptr2 + len2;
for (;;) {
u32s s1 = makeFoldU(readUtf8Symbol(ptr1, ptr1End)), s2 = makeFoldU(readUtf8Symbol(ptr2, ptr2End));
if (s1 > s2)
return 1;
else if (s1 < s2)
return -1;
else if (ptr1 >= ptr1End)
return ptr2 >= ptr2End ? 0 : -1;
else if (ptr2 >= ptr2End)
return 1;
}
}
SIMSTR_API int unicode_traits<u16s>::compareiu(const u16s* text1, size_t len1, const u16s* text2, size_t len2) {
if (!len1) {
return len2 == 0 ? 0 : -1;
} else if (!len2)
return 1;
const u16s *ptr1End = text1 + len1, *ptr2End = text2 + len2;
for (;;) {
u32s s1 = makeFoldU(readUtf16Symbol(text1, ptr1End)), s2 = makeFoldU(readUtf16Symbol(text2, ptr2End));
if (s1 > s2)
return 1;
else if (s1 < s2)
return -1;
else if (text1 >= ptr1End)
return text2 >= ptr2End ? 0 : -1;
else if (text2 >= ptr2End)
return 1;
}
}
SIMSTR_API int unicode_traits<u32s>::compareiu(const u32s* text1, size_t len1, const u32s* text2, size_t len2) {
if (!len1) {
return len2 == 0 ? 0 : -1;
} else if (!len2)
return 1;
const u32s *ptr1End = text1 + len1, *ptr2End = text2 + len2;
for (;;) {
u32s s1 = makeFoldU(*text1++), s2 = makeFoldU(*text2++);
if (s1 > s2)
return 1;
else if (s1 < s2)
return -1;
else if (text1 > ptr1End)
return text2 >= ptr2End ? 0 : -1;
else if (text2 > ptr2End)
return 1;
}
}
SIMSTR_API size_t unicode_traits<u8s>::hashia(const u8s* src, size_t l) {
size_t h = fnv::basis;
for (size_t i = 0; i < l; i++) {
h = (h ^ (uu8s)makeAsciiLower(src[i])) * fnv::prime;
}
return h;
}
SIMSTR_API size_t unicode_traits<u8s>::hashiu(const u8s* src, size_t l) {
size_t h = fnv::basis;
const uu8s *ptr = (const uu8s*)src, *pEnd = ptr + l;
while (ptr < pEnd) {
h = (h ^ makeFoldU(readUtf8Symbol(ptr, pEnd))) * fnv::prime;
}
return h;
}
SIMSTR_API size_t unicode_traits<u16s>::hashia(const u16s* src, size_t l) {
size_t h = fnv::basis;
for (size_t i = 0; i < l; i++) {
h = (h ^ makeAsciiLower(src[i])) * fnv::prime;
}
return h;
}
SIMSTR_API size_t unicode_traits<u16s>::hashiu(const u16s* src, size_t l) {
size_t h = fnv::basis;
const u16s* pEnd = src + l;
while (src < pEnd) {
h = (h ^ makeFoldU(readUtf16Symbol(src, pEnd))) * fnv::prime;
}
return h;
}
SIMSTR_API size_t unicode_traits<u32s>::hashia(const u32s* src, size_t l) {
size_t h = fnv::basis;
for (size_t i = 0; i < l; i++) {
h = (h ^ makeAsciiLower(src[i])) * fnv::prime;
}
return h;
}
SIMSTR_API size_t unicode_traits<u32s>::hashiu(const u32s* src, size_t l) {
size_t h = fnv::basis;
for (size_t i = 0; i < l; i++) {
h = (h ^ makeFoldU(src[i])) * fnv::prime;
}
return h;
}
} // namespace simstr