// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // // This software is licensed under the BSD-style license. // Additional exceptions apply. For full license details, see LICENSE.txt or visit: // https://yapb.ru/license // #pragma once #include #include #include #include #include #include #include CR_NAMESPACE_BEGIN // small-string optimized string class, sso stuff based on: https://github.com/elliotgoodrich/SSO-23/ class String final { public: static constexpr size_t kInvalidIndex = static_cast (-1); private: static constexpr size_t kExcessSpace = 32; private: using Length = Twin ; private: union Data { struct Big { char excess[kExcessSpace - sizeof (char *) - 2 * sizeof (size_t)]; char *ptr; size_t length; size_t capacity; } big; struct Small { char str[sizeof (Big) / sizeof (char) - 1]; uint8 length; } small; } m_data; private: static size_t const kSmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1; public: explicit String () { reset (); assign ("", 0); } String (const char *str, size_t length = 0) { reset (); assign (str, length); } String (const String &str) { reset (); assign (str.data (), str.length ()); } String (const char ch) { reset (); assign (ch); } String (String &&rhs) noexcept { m_data = rhs.m_data; rhs.setMoved (); } ~String () { destroy (); } private: template static uint8 &getMostSignificantByte (T &object) { return *(reinterpret_cast (&object) + sizeof (object) - 1); } template static bool getLeastSignificantBit (uint8 byte) { return byte & cr::bit (N); } template static bool getMostSignificantBit (uint8 byte) { return byte & cr::bit (CHAR_BIT - N - 1); } template static void setLeastSignificantBit (uint8 &byte, bool bit) { if (bit) { byte |= cr::bit (N); } else { byte &= ~cr::bit (N); } } template static void setMostSignificantBit (uint8 &byte, bool bit) { if (bit) { byte |= cr::bit (CHAR_BIT - N - 1); } else { byte &= ~cr::bit (CHAR_BIT - N - 1); } } void destroy () { if (!isSmall ()) { alloc.deallocate (m_data.big.ptr); } } void reset () { m_data.small.length = 0; m_data.small.str[0] = '\0'; m_data.big.ptr = nullptr; m_data.big.length = 0; } void endString (const char *str, size_t at) { const_cast (str)[at] = '\0'; } void moveString (const char *dst, const char *src, size_t length) { if (!dst) { return; } memmove (const_cast (dst), src, length); } const char *data () const { return isSmall () ? m_data.small.str : m_data.big.ptr; } void setMoved () { setSmallLength (0); } void setLength (size_t amount, size_t capacity) { if (amount <= kSmallCapacity) { endString (m_data.small.str, amount); setSmallLength (static_cast (amount)); } else { endString (m_data.big.ptr, amount); setDataNonSmall (amount, capacity); } } bool isSmall () const { return !getLeastSignificantBit <0> (m_data.small.length) && !getLeastSignificantBit <1> (m_data.small.length); } void setSmallLength (uint8 length) { m_data.small.length = static_cast (kSmallCapacity - length) << 2; } size_t getSmallLength () const { return kSmallCapacity - ((m_data.small.length >> 2) & 63u); } void setDataNonSmall (size_t length, size_t capacity) { uint8 &lengthHighByte = getMostSignificantByte (length); uint8 &capacityHighByte = getMostSignificantByte (capacity); const bool lengthHasHighBit = getMostSignificantBit <0> (lengthHighByte); const bool capacityHasHighBit = getMostSignificantBit <0> (capacityHighByte); const bool capacityHasSecHighBit = getMostSignificantBit <1> (capacityHighByte); setMostSignificantBit <0> (lengthHighByte, capacityHasSecHighBit); capacityHighByte <<= 2; setLeastSignificantBit <0> (capacityHighByte, capacityHasHighBit); setLeastSignificantBit <1> (capacityHighByte, !lengthHasHighBit); m_data.big.length = length; m_data.big.capacity = capacity; } Length getDataNonSmall () const { size_t length = m_data.big.length; size_t capacity = m_data.big.capacity; uint8 &lengthHighByte = getMostSignificantByte (length); uint8 &capacityHighByte = getMostSignificantByte (capacity); const bool capacityHasHighBit = getLeastSignificantBit <0> (capacityHighByte); const bool lengthHasHighBit = !getLeastSignificantBit <1> (capacityHighByte); const bool capacityHasSecHighBit = getMostSignificantBit <0> (lengthHighByte); setMostSignificantBit <0> (lengthHighByte, lengthHasHighBit); capacityHighByte >>= 2; setMostSignificantBit <0> (capacityHighByte, capacityHasHighBit); setMostSignificantBit <1> (capacityHighByte, capacityHasSecHighBit); return { length, capacity }; } public: String &assign (const char *str, size_t length = 0) { length = length > 0 ? length : strlen (str); if (length <= kSmallCapacity) { moveString (m_data.small.str, str, length); endString (m_data.small.str, length); setSmallLength (static_cast (length)); } else { auto capacity = cr::max (kSmallCapacity * 2, length); m_data.big.ptr = alloc.allocate (capacity + 1); if (m_data.big.ptr) { moveString (m_data.big.ptr, str, length); endString (m_data.big.ptr, length); setDataNonSmall (length, capacity); } } return *this; } String &assign (const String &str, size_t length = 0) { return assign (str.chars (), length); } String &assign (const char ch) { const char str[] { ch, '\0' }; return assign (str, strlen (str)); } String &append (const char *str, size_t length = 0) { if (empty ()) { return assign (str, length); } length = length > 0 ? length : strlen (str); size_t oldLength = this->length (); size_t newLength = oldLength + length; resize (newLength); moveString (&data ()[oldLength], str, length); endString (data (), newLength); return *this; } String &append (const String &str, size_t length = 0) { return append (str.chars (), length); } String &append (const char ch) { const char str[] { ch, '\0' }; return append (str, strlen (str)); } template String &assignf (const char *fmt, Args ...args) { const size_t size = snprintf (nullptr, 0, fmt, args...); SmallArray buffer (size + 1); snprintf (buffer.data (), size + 1, fmt, cr::forward (args)...); return assign (buffer.data ()); } template String &appendf (const char *fmt, Args ...args) { if (empty ()) { return assignf (fmt, cr::forward (args)...); } const size_t size = snprintf (nullptr, 0, fmt, args...) + length (); SmallArray buffer (size + 1); snprintf (buffer.data (), size + 1, fmt, cr::forward (args)...); return append (buffer.data ()); } void resize (size_t amount) { size_t oldLength = length (); if (amount <= kSmallCapacity) { if (!isSmall ()) { auto ptr = m_data.big.ptr; moveString (m_data.small.str, ptr, cr::min (oldLength, amount)); alloc.deallocate (ptr); } setLength (amount, 0); } else { size_t newCapacity = 0; if (isSmall ()) { newCapacity = cr::max (amount, kSmallCapacity * 2); auto ptr = alloc.allocate (newCapacity + 1); moveString (ptr, m_data.small.str, cr::min (oldLength, amount)); m_data.big.ptr = ptr; } else if (amount < capacity ()) { newCapacity = capacity (); } else { newCapacity = cr::max (amount, capacity () * 3 / 2); auto ptr = alloc.allocate (newCapacity + 1); moveString (ptr, m_data.big.ptr, cr::min (oldLength, amount)); alloc.deallocate (m_data.big.ptr); m_data.big.ptr = ptr; } setLength (amount, newCapacity); } } bool insert (size_t index, const String &str) { if (str.empty ()) { return false; } const size_t strLength = str.length (); const size_t dataLength = length (); if (index >= dataLength) { append (str.chars (), strLength); } else { resize (dataLength + strLength); for (size_t i = dataLength; i > index; --i) { at (i + strLength - 1) = at (i - 1); } for (size_t i = 0; i < strLength; ++i) { at (i + index) = str.at (i); } } return true; } bool erase (size_t index, size_t count = 1) { const size_t dataLength = length (); if (index + count > dataLength) { return false; } const size_t newLength = dataLength - count; for (size_t i = index; i < newLength; ++i) { at (i) = at (i + count); } resize (newLength); return true; } size_t find (char pattern, size_t start = 0) const { for (size_t i = start; i < length (); ++i) { if (at (i) == pattern) { return i; } } return kInvalidIndex; } size_t find (const String &pattern, size_t start = 0) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); if (patternLength > dataLength || start > dataLength) { return kInvalidIndex; } for (size_t i = start; i <= dataLength - patternLength; ++i) { size_t index = 0; for (; at (index) && index < patternLength; ++index) { if (at (i + index) != pattern.at (index)) { break; } } if (!pattern.at (index)) { return i; } } return kInvalidIndex; } size_t rfind (char pattern) const { for (size_t i = length (); i != 0; i--) { if (at (i) == pattern) { return i; } } return kInvalidIndex; } size_t rfind (const String &pattern) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); if (patternLength > dataLength) { return kInvalidIndex; } bool match = true; for (size_t i = dataLength - 1; i >= patternLength; i--) { match = true; for (size_t j = patternLength - 1; j > 0; j--) { if (at (i + j) != pattern.at (j)) { match = false; break; } } if (match) { return i; } } return kInvalidIndex; } size_t findFirstOf (const String &pattern, size_t start = 0) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); for (size_t i = start; i < dataLength; ++i) { for (size_t j = 0; j < patternLength; ++j) { if (at (i) == pattern.at (j)) { return i; } } } return kInvalidIndex; } size_t findLastOf (const String &pattern) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); for (size_t i = dataLength - 1; i > 0; i--) { for (size_t j = 0; j < patternLength; ++j) { if (at (i) == pattern.at (j)) { return i; } } } return kInvalidIndex; } size_t findFirstNotOf (const String &pattern, size_t start = 0) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); bool different = true; for (size_t i = start; i < dataLength; ++i) { different = true; for (size_t j = 0; j < patternLength; ++j) { if (at (i) == pattern.at (j)) { different = false; break; } } if (different) { return i; } } return kInvalidIndex; } size_t findLastNotOf (const String &pattern) const { const size_t patternLength = pattern.length (); const size_t dataLength = length (); bool different = true; for (size_t i = dataLength - 1; i > 0; i--) { different = true; for (size_t j = 0; j < patternLength; ++j) { if (at (i) == pattern.at (j)) { different = false; break; } } if (different) { return i; } } return kInvalidIndex; } size_t countChar (char ch) const { size_t count = 0; for (size_t i = 0, e = length (); i != e; ++i) { if (at (i) == ch) { ++count; } } return count; } size_t countStr (const String &pattern) const { const size_t patternLen = pattern.length (); const size_t dataLength = length (); if (patternLen > dataLength) { return 0; } size_t count = 0; for (size_t i = 0, e = dataLength - patternLen + 1; i != e; ++i) { if (substr (i, patternLen).compare (pattern)) { ++count; } } return count; } String substr (size_t start, size_t count = kInvalidIndex) const { start = cr::min (start, length ()); if (count == kInvalidIndex) { count = length (); } return String (data () + start, cr::min (count, length () - start)); } size_t replace (const String &needle, const String &to) { if (needle.empty () || to.empty ()) { return 0; } size_t replaced = 0, pos = 0; while (pos < length ()) { pos = find (needle, pos); if (pos == kInvalidIndex) { break; } erase (pos, needle.length ()); insert (pos, to); pos += to.length (); replaced++; } return replaced; } bool startsWith (const String &prefix) const { const size_t prefixLength = prefix.length (); const size_t dataLength = length (); return prefixLength <= dataLength && strncmp (data (), prefix.data (), prefixLength) == 0; } bool endsWith (const String &suffix) const { const size_t suffixLength = suffix.length (); const size_t dataLength = length (); return suffixLength <= dataLength && strncmp (data () + dataLength - suffixLength, suffix.data (), suffixLength) == 0; } Array split (const String &delim) const { Array tokens; size_t prev = 0, pos = 0; while ((pos = find (delim, pos)) != kInvalidIndex) { tokens.push (substr (prev, pos - prev)); prev = ++pos; } tokens.push (substr (prev, pos - prev)); return tokens; } size_t length () const { if (isSmall ()) { return getSmallLength (); } else { return getDataNonSmall ().first; } } size_t capacity () const { if (isSmall ()) { return sizeof (m_data) - 1; } else { return getDataNonSmall ().second; } } bool small () const { return isSmall (); } bool empty () const { return length () == 0; } void clear () { assign (""); } const char *chars () const { return data (); } const char &at (size_t index) const { return begin ()[index]; } char &at (size_t index) { return begin ()[index]; } int32 compare (const String &rhs) const { return strcmp (rhs.data (), data ()); } int32 compare (const char *rhs) const { return strcmp (rhs, data ()); } bool contains (const String &rhs) const { return find (rhs) != kInvalidIndex; } String &lowercase () { for (auto &ch : *this) { ch = static_cast (::tolower (ch)); } return *this; } String &uppercase () { for (auto &ch : *this) { ch = static_cast (::toupper (ch)); } return *this; } int32 int_ () const { return atoi (data ()); } float float_ () const { return static_cast (atof (data ())); } String <rim (const String &characters = "\r\n\t ") { size_t begin = length (); for (size_t i = 0; i < begin; ++i) { if (characters.find (at (i)) == kInvalidIndex) { begin = i; break; } } return *this = substr (begin, length () - begin); } String &rtrim (const String &characters = "\r\n\t ") { size_t end = 0; for (size_t i = length (); i > 0; --i) { if (characters.find (at (i - 1)) == kInvalidIndex) { end = i; break; } } return *this = substr (0, end); } String &trim (const String &characters = "\r\n\t ") { return ltrim (characters).rtrim (characters); } public: String &operator = (String &&rhs) noexcept { destroy (); m_data = rhs.m_data; rhs.setMoved (); return *this; } String &operator = (const String &rhs) { return assign (rhs); } String &operator = (const char *rhs) { return assign (rhs); } String &operator = (char rhs) { return assign (rhs); } String &operator += (const String &rhs) { return append (rhs); } String &operator += (const char *rhs) { return append (rhs); } const char &operator [] (size_t index) const { return at (index); } char &operator [] (size_t index) { return at (index); } friend String operator + (const String &lhs, char rhs) { return String (lhs).append (rhs); } friend String operator + (char lhs, const String &rhs) { return String (lhs).append (rhs); } friend String operator + (const String &lhs, const char *rhs) { return String (lhs).append (rhs); } friend String operator + (const char *lhs, const String &rhs) { return String (lhs).append (rhs); } friend String operator + (const String &lhs, const String &rhs) { return String (lhs).append (rhs); } friend bool operator == (const String &lhs, const String &rhs) { return lhs.compare (rhs) == 0; } friend bool operator < (const String &lhs, const String &rhs) { return lhs.compare (rhs) < 0; } friend bool operator > (const String &lhs, const String &rhs) { return lhs.compare (rhs) > 0; } friend bool operator == (const char *lhs, const String &rhs) { return rhs.compare (lhs) == 0; } friend bool operator == (const String &lhs, const char *rhs) { return lhs.compare (rhs) == 0; } friend bool operator != (const String &lhs, const String &rhs) { return lhs.compare (rhs) != 0; } friend bool operator != (const char *lhs, const String &rhs) { return rhs.compare (lhs) != 0; } friend bool operator != (const String &lhs, const char *rhs) { return lhs.compare (rhs) != 0; } public: static String join (const Array &sequence, const String &delim, size_t start = 0) { if (sequence.empty ()) { return ""; } if (sequence.length () == 1) { return sequence.at (0); } String result; for (size_t index = start; index < sequence.length (); ++index) { if (index != start) { result += delim + sequence[index]; } else { result += sequence[index]; } } return result; } // for range-based loops public: char *begin () { return const_cast (data ()); } char *begin () const { return const_cast (data ()); } char *end () { return begin () + length (); } char *end () const { return begin () + length (); } }; // simple rotation-string pool for holding temporary data passed to different modules and for formatting class StringBuffer final : public Singleton { public: enum : size_t { StaticBufferSize = static_cast (768), RotationCount = static_cast (32) }; private: char m_data[RotationCount + 1][StaticBufferSize] {}; size_t m_rotate = 0; public: StringBuffer () = default; ~StringBuffer () = default; public: char *chars () { if (++m_rotate >= RotationCount) { m_rotate = 0; } return m_data[cr::clamp (m_rotate, 0, RotationCount)]; } template U *format (const U *fmt, Args ...args) { auto buffer = Singleton ::get ().chars (); snprintf (buffer, StaticBufferSize, fmt, args...); return buffer; } template U *format (const U *fmt) { auto buffer = Singleton ::get ().chars (); strncpy (buffer, fmt, StaticBufferSize); return buffer; } }; // expose global string pool static auto &strings = StringBuffer::get (); CR_NAMESPACE_END