// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "utils_global.h" #include "smallstringliteral.h" #include "smallstringiterator.h" #include "smallstringview.h" #include "smallstringmemory.h" #include #include #include #include #include #include #include #include #include #include #ifdef UNIT_TESTS #define unittest_public public #else #define unittest_public private #endif namespace Utils { template class alignas(16) BasicSmallString { public: using const_iterator = Internal::SmallStringIterator; using iterator = Internal::SmallStringIterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; using size_type = std::size_t; static constexpr size_type Capacity = Size; static_assert(Size < 64 ? sizeof(Internal::StringDataLayout) == Size + 1 : sizeof(Internal::StringDataLayout) == Size + 16, "Size is wrong"); BasicSmallString() noexcept = default; constexpr BasicSmallString(const BasicSmallStringLiteral &stringReference) noexcept : m_data(stringReference.m_data) { } template constexpr BasicSmallString(const char (&string)[ArraySize]) noexcept : m_data(string) {} BasicSmallString(const char *string, size_type size, size_type capacity) noexcept : m_data{string, size, capacity} {} explicit BasicSmallString(SmallStringView stringView) noexcept : BasicSmallString(stringView.data(), stringView.size(), stringView.size()) {} BasicSmallString(const char *string, size_type size) noexcept : BasicSmallString(string, size, size) {} explicit BasicSmallString(const_iterator begin, const_iterator end) noexcept : BasicSmallString{std::addressof(*begin), static_cast(std::distance(begin, end))} {} explicit BasicSmallString(iterator begin, iterator end) noexcept : BasicSmallString{std::addressof(*begin), static_cast(std::distance(begin, end))} {} template::value, bool> = true> BasicSmallString(Type characterPointer) noexcept : BasicSmallString(characterPointer, std::char_traits::length(characterPointer)) { static_assert(!std::is_array::value, "Input type is array and not char pointer!"); } BasicSmallString(const QString &qString) noexcept { append(qString); } BasicSmallString(const QStringView qStringView) noexcept { append(qStringView); } BasicSmallString(const QByteArray &qByteArray) noexcept : BasicSmallString(qByteArray.constData(), qByteArray.size()) {} template = 0> BasicSmallString(const String &string) noexcept : BasicSmallString(string.data(), string.size()) { } BasicSmallString(const std::wstring &wstring) noexcept { append(wstring); } template::value, bool> = true> BasicSmallString(BeginIterator begin, EndIterator end) noexcept : BasicSmallString(&(*begin), size_type(end - begin)) {} ~BasicSmallString() noexcept { if (hasAllocatedMemory()) [[unlikely]] Memory::deallocate(m_data.data()); } BasicSmallString(const BasicSmallString &other) noexcept { if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; } BasicSmallString &operator=(const BasicSmallString &other) noexcept { if (this != &other) [[likely]] { this->~BasicSmallString(); if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; } return *this; } BasicSmallString(BasicSmallString &&other) noexcept : m_data(std::move(other.m_data)) { other.m_data.reset(); } BasicSmallString &operator=(BasicSmallString &&other) noexcept { if (this != &other) [[likely]] { this->~BasicSmallString(); m_data = std::move(other.m_data); other.m_data.reset(); } return *this; } BasicSmallString take() noexcept { return std::move(*this); } friend void swap(BasicSmallString &first, BasicSmallString &second) noexcept { using std::swap; swap(first.m_data, second.m_data); } QByteArray toQByteArray() const noexcept { return QByteArray(data(), int(size())); } QString toQString() const noexcept { return QString::fromUtf8(data(), int(size())); } SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); } constexpr operator SmallStringView() const noexcept { return SmallStringView(data(), size()); } explicit operator QLatin1StringView() const noexcept { return QLatin1StringView(data(), Utils::ssize(*this)); } operator QUtf8StringView() const noexcept { return QUtf8StringView(data(), Utils::ssize(*this)); } explicit operator QString() const noexcept { return toQString(); } explicit operator std::string() const { return std::string(data(), size()); } static BasicSmallString fromUtf8(const char *characterPointer) noexcept { return BasicSmallString(characterPointer, std::char_traits::length(characterPointer)); } void reserve(size_type newCapacity) noexcept { if (fitsNotInCapacity(newCapacity)) { if (hasAllocatedMemory()) { m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity)); m_data.setAllocatedCapacity(newCapacity); } else { auto oldSize = size(); new (this) BasicSmallString{data(), oldSize, std::max(newCapacity, oldSize)}; } } } void resize(size_type newSize) noexcept { reserve(newSize); setSize(newSize); } void pop_back() noexcept { setSize(size() - 1); } void clear() noexcept { this->~BasicSmallString(); m_data = Internal::StringDataLayout(); } char *data() noexcept { return m_data.data(); } const char *data() const noexcept { return m_data.data(); } iterator begin() noexcept { return data(); } iterator end() noexcept { return data() + size(); } reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_iterator begin() const noexcept { return data(); } const_iterator end() const noexcept { return data() + size(); } const_iterator cbegin() const noexcept { return begin(); } const_iterator cend() const noexcept { return end(); } static BasicSmallString fromQString(const QString &qString) noexcept { BasicSmallString string; string.append(qString); return string; } static BasicSmallString fromQStringView(QStringView qStringView) noexcept { BasicSmallString string; string.append(qStringView); return string; } static BasicSmallString fromQByteArray(const QByteArray &utf8ByteArray) noexcept { return BasicSmallString(utf8ByteArray.constData(), uint(utf8ByteArray.size())); } bool contains(SmallStringView subStringToSearch) const noexcept { return SmallStringView{*this}.find(subStringToSearch) != SmallStringView::npos; } bool contains(char characterToSearch) const noexcept { auto found = std::char_traits::find(data(), size(), characterToSearch); return found != nullptr; } bool startsWith(SmallStringView subStringToSearch) const noexcept { if (size() >= subStringToSearch.size()) return !std::char_traits::compare(data(), subStringToSearch.data(), subStringToSearch.size()); return false; } bool startsWith(QStringView subStringToSearch) const noexcept { if (size() >= Utils::usize(subStringToSearch)) return subStringToSearch == QLatin1StringView{data(), subStringToSearch.size()}; return false; } bool startsWith(char characterToSearch) const noexcept { return data()[0] == characterToSearch; } bool endsWith(SmallStringView subStringToSearch) const noexcept { if (size() >= subStringToSearch.size()) { const int comparison = std::char_traits::compare(end().data() - subStringToSearch.size(), subStringToSearch.data(), subStringToSearch.size()); return comparison == 0; } return false; } bool endsWith(char character) const noexcept { return at(size() - 1) == character; } size_type size() const noexcept { return m_data.size(); } size_type capacity() const noexcept { return m_data.capacity(); } bool isEmpty() const noexcept { return size() == 0; } bool empty() const noexcept { return isEmpty(); } bool hasContent() const noexcept { return size() != 0; } SmallStringView mid(size_type position) const noexcept { return SmallStringView(data() + position, size() - position); } SmallStringView mid(size_type position, size_type length) const noexcept { return SmallStringView(data() + position, length); } void append(SmallStringView string) noexcept { size_type oldSize = size(); size_type newSize = oldSize + string.size(); if (fitsNotInCapacity(newSize)) reserve(optimalCapacity(newSize)); std::char_traits::copy(std::next(data(), static_cast(oldSize)), string.data(), string.size()); setSize(newSize); } void append(char character) noexcept { size_type oldSize = size(); size_type newSize = oldSize + 1; if (fitsNotInCapacity(newSize)) reserve(optimalCapacity(newSize)); auto current = std::next(data(), static_cast(oldSize)); *current = character; setSize(newSize); } template, bool> = true> void append(Type number) { #if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); auto endOfConversionString = result.ptr; append({buffer, endOfConversionString}); #else if constexpr (std::is_floating_point_v) { QLocale locale{QLocale::Language::C}; append(locale.toString(number)); return; } else { // 2 bytes for the sign and because digits10 returns the floor char buffer[std::numeric_limits::digits10 + 2]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); auto endOfConversionString = result.ptr; append({buffer, endOfConversionString}); } #endif } void append(QStringView string) noexcept { QStringEncoder encoder{QStringEncoder::Utf8}; constexpr size_type temporaryArraySize = Size * 6; size_type oldSize = size(); size_type maximumRequiredSize = static_cast(encoder.requiredSpace(string.size())); char *newEnd = nullptr; if (maximumRequiredSize > temporaryArraySize) { size_type newSize = oldSize + maximumRequiredSize; reserve(optimalCapacity(newSize)); newEnd = encoder.appendToBuffer(data() + oldSize, string); } else { char temporaryArray[temporaryArraySize]; auto newTemporaryArrayEnd = encoder.appendToBuffer(temporaryArray, string); auto newAppendedStringSize = newTemporaryArrayEnd - temporaryArray; size_type newSize = oldSize + newAppendedStringSize; reserve(optimalCapacity(newSize)); auto begin = data(); std::memcpy(begin + oldSize, temporaryArray, newAppendedStringSize); newEnd = begin + newSize; } setSize(newEnd - data()); } BasicSmallString &operator+=(SmallStringView string) noexcept { append(string); return *this; } BasicSmallString &operator+=(char character) noexcept { append(character); return *this; } BasicSmallString &operator+=(QStringView string) noexcept { append(string); return *this; } template, bool> = true> BasicSmallString &operator+=(Type number) noexcept { append(number); return *this; } BasicSmallString &operator+=(std::initializer_list list) noexcept { appendInitializerList(list, size()); return *this; } void replace(SmallStringView fromText, SmallStringView toText) noexcept { if (toText.size() == fromText.size()) replaceEqualSized(fromText, toText); else if (toText.size() < fromText.size()) replaceSmallerSized(fromText, toText); else replaceLargerSized(fromText, toText); } void replace(char fromCharacter, char toCharacter) noexcept { reserve(size()); std::replace(begin(), end(), fromCharacter, toCharacter); } void replace(size_type position, size_type length, SmallStringView replacementText) noexcept { size_type newSize = size() - length + replacementText.size(); reserve(optimalCapacity(newSize)); auto replaceStart = begin() + position; auto replaceEnd = replaceStart + length; auto replacementEnd = replaceStart + replacementText.size(); size_type tailSize = size_type(end() - replaceEnd + 1); std::memmove(replacementEnd.data(), replaceEnd.data(), tailSize); std::memcpy(replaceStart.data(), replacementText.data(), replacementText.size()); setSize(newSize); } BasicSmallString toCarriageReturnsStripped() const noexcept { BasicSmallString text = *this; text.replace("\r", ""); return text; } constexpr static size_type shortStringCapacity() noexcept { return Internal::StringDataLayout::shortStringCapacity(); } size_type optimalCapacity(const size_type size) { if (fitsNotInCapacity(size)) return optimalHeapCapacity(size); return size; } static BasicSmallString join(std::initializer_list list, Utils::SmallStringView separator) noexcept { size_type totalSize = 0; for (SmallStringView string : list) totalSize += string.size() + separator.size(); BasicSmallString joinedString; joinedString.reserve(totalSize); for (auto it = list.begin(); it != list.end(); ++it) { joinedString.append(*it); if (std::next(it) != list.end()) joinedString.append(separator); } return joinedString; } static BasicSmallString join(std::initializer_list list) noexcept { size_type totalSize = 0; for (SmallStringView string : list) totalSize += string.size(); BasicSmallString joinedString; joinedString.reserve(totalSize); for (SmallStringView string : list) joinedString.append(string); return joinedString; } template, bool> = true> static BasicSmallString number(Type number) { BasicSmallString string; string.append(number); return string; } char &operator[](std::size_t index) noexcept { return *(data() + index); } char operator[](std::size_t index) const noexcept { return *(data() + index); } friend BasicSmallString operator+(const BasicSmallString &first, const BasicSmallString &second) noexcept { BasicSmallString text; text.reserve(first.size() + second.size()); text.append(first); text.append(second); return text; } friend BasicSmallString operator+(const BasicSmallString &first, SmallStringView second) noexcept { BasicSmallString text; text.reserve(first.size() + second.size()); text.append(first); text.append(second); return text; } friend BasicSmallString operator+(SmallStringView first, const BasicSmallString &second) noexcept { BasicSmallString text; text.reserve(first.size() + second.size()); text.append(first); text.append(second); return text; } template friend BasicSmallString operator+(const BasicSmallString &first, const char (&second)[ArraySize]) noexcept { return operator+(first, SmallStringView(second)); } template friend BasicSmallString operator+(const char (&first)[ArraySize], const BasicSmallString &second) noexcept { return operator+(SmallStringView(first), second); } size_type count(SmallStringView text) const noexcept { auto found = begin(); size_type count = 0; while (true) { found = std::search(found, end(), text.begin(), text.end()); if (found == end()) break; ++count; found += text.size(); } return count; } size_type count(char character) const noexcept { return std::count(begin(), end(), character); } unittest_public : constexpr bool isShortString() const noexcept { return m_data.isShortString(); } constexpr bool isReadOnlyReference() const noexcept { return m_data.isReadOnlyReference(); } constexpr bool hasAllocatedMemory() const noexcept { return !isShortString() && !isReadOnlyReference(); } bool fitsNotInCapacity(size_type capacity) const noexcept { return capacity > m_data.capacity(); } static size_type optimalHeapCapacity(const size_type size) noexcept { const size_type cacheLineSize = 64; size_type cacheLineBlocks = (size - 1) / cacheLineSize; return (cacheLineBlocks + 1) * cacheLineSize; } private: BasicSmallString(const Internal::StringDataLayout &data) noexcept : m_data(data) { } void appendInitializerList(std::initializer_list list, std::size_t initialSize) noexcept { auto addSize = [] (std::size_t size, SmallStringView string) { return size + string.size(); }; std::size_t size = std::accumulate(list.begin(), list.end(), initialSize, addSize); reserve(size); setSize(size); char *currentData = data() + initialSize; for (SmallStringView string : list) { std::memcpy(currentData, string.data(), string.size()); currentData += string.size(); } } char &at(size_type index) noexcept { return *(data() + index); } char at(size_type index) const noexcept { return *(data() + index); } void replaceEqualSized(SmallStringView fromText, SmallStringView toText) noexcept { reserve(size()); auto start = begin(); auto found = std::search(start, end(), fromText.begin(), fromText.end()); while (found != end()) { start = found + toText.size(); std::char_traits::copy(found.data(), toText.data(), toText.size()); found = std::search(start, end(), fromText.begin(), fromText.end()); } } void replaceSmallerSized(SmallStringView fromText, SmallStringView toText) noexcept { auto found = std::search(begin(), end(), fromText.begin(), fromText.end()); if (found != end()) { size_type newSize = size(); { size_type foundIndex = found - begin(); reserve(newSize); found = begin() + foundIndex; } size_type sizeDifference = 0; while (found != end()) { auto start = found + fromText.size(); auto nextFound = std::search(start, end(), fromText.begin(), fromText.end()); auto replacedTextEndPosition = found + fromText.size(); auto replacementTextEndPosition = found + toText.size() - sizeDifference; auto replacementTextStartPosition = found - sizeDifference; std::memmove(replacementTextEndPosition.data(), replacedTextEndPosition.data(), std::distance(start, nextFound)); std::memcpy(replacementTextStartPosition.data(), toText.data(), toText.size()); sizeDifference += fromText.size() - toText.size(); found = nextFound; } newSize -= sizeDifference; setSize(newSize); } } iterator replaceLargerSizedRecursive(size_type startIndex, SmallStringView fromText, SmallStringView toText, size_type sizeDifference) noexcept { auto found = std::search(begin() + startIndex, end(), fromText.begin(), fromText.end()); auto foundIndex = found - begin(); if (found != end()) { size_type startNextSearchIndex = foundIndex + fromText.size(); size_type newSizeDifference = sizeDifference + (toText.size() - fromText.size()); auto nextFound = replaceLargerSizedRecursive(startNextSearchIndex, fromText, toText, newSizeDifference); auto startFound = begin() + foundIndex; auto endOfFound = begin() + startNextSearchIndex; auto replacedTextEndPosition = endOfFound; auto replacementTextEndPosition = endOfFound + newSizeDifference; auto replacementTextStartPosition = startFound + sizeDifference; std::memmove(replacementTextEndPosition.data(), replacedTextEndPosition.data(), std::distance(endOfFound, nextFound)); std::memcpy(replacementTextStartPosition.data(), toText.data(), toText.size()); } else if (startIndex != 0) { size_type newSize = size() + sizeDifference; setSize(newSize); } return begin() + foundIndex; } void replaceLargerSized(SmallStringView fromText, SmallStringView toText) noexcept { size_type sizeDifference = 0; size_type startIndex = 0; size_type replacementTextSizeDifference = toText.size() - fromText.size(); size_type occurrences = count(fromText); size_type newSize = size() + (replacementTextSizeDifference * occurrences); if (occurrences > 0) { reserve(optimalCapacity(newSize)); replaceLargerSizedRecursive(startIndex, fromText, toText, sizeDifference); } } void setSize(size_type size) noexcept { m_data.setSize(size); } private: Internal::StringDataLayout m_data; }; template class String, uint Size> using isSameString = std::is_same>>, BasicSmallString>; template, typename KeyEqual = std::equal_to, typename Allocator = std::allocator>> std::unordered_map clone(const std::unordered_map &map) { std::unordered_map clonedMap; clonedMap.reserve(clonedMap.size()); for (auto &&entry : map) clonedMap.emplace(entry.first, entry.second.clone()); return clonedMap; } template Type clone(const Type &vector) { return vector; } using SmallString = BasicSmallString<31>; using PathString = BasicSmallString<176>; inline SmallString operator+(SmallStringView first, SmallStringView second) noexcept { SmallString text; text.reserve(first.size() + second.size()); text.append(first); text.append(second); return text; } inline SmallString operator+(SmallStringView first, char second) noexcept { SmallString text; text.reserve(first.size() + 1); text.append(first); text.append(second); return text; } template inline SmallString operator+(SmallStringView first, const char (&second)[Size]) noexcept { return operator+(first, SmallStringView(second)); } template inline SmallString operator+(const char(&first)[Size], SmallStringView second) { return operator+(SmallStringView(first), second); } } // namespace Utils namespace std { template class TQual, template class UQual> struct basic_common_reference, Utils::BasicSmallString, TQual, UQual> { using type = Utils::SmallStringView; }; } // namespace std Q_DECLARE_METATYPE(Utils::SmallString)