simstr/src/sstring.cpp

549 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* (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