duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

small_string.cpp (23089B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "small_string.h"
      5 #include "assert.h"
      6 #include "string_util.h"
      7 
      8 #include <algorithm>
      9 #include <cctype>
     10 #include <cstdio>
     11 #include <cstring>
     12 
     13 #ifdef _WIN32
     14 #include "windows_headers.h"
     15 #endif
     16 
     17 SmallStringBase::SmallStringBase() = default;
     18 
     19 SmallStringBase::SmallStringBase(const SmallStringBase& copy)
     20 {
     21   assign(copy.m_buffer, copy.m_length);
     22 }
     23 
     24 SmallStringBase::SmallStringBase(const char* str)
     25 {
     26   assign(str);
     27 }
     28 
     29 SmallStringBase::SmallStringBase(const char* str, u32 count)
     30 {
     31   assign(str, count);
     32 }
     33 
     34 SmallStringBase::SmallStringBase(SmallStringBase&& move)
     35 {
     36   assign(std::move(move));
     37 }
     38 
     39 SmallStringBase::SmallStringBase(const std::string_view sv)
     40 {
     41   assign(sv);
     42 }
     43 
     44 SmallStringBase::SmallStringBase(const std::string& str)
     45 {
     46   assign(str);
     47 }
     48 
     49 SmallStringBase::~SmallStringBase()
     50 {
     51   if (m_on_heap)
     52     std::free(m_buffer);
     53 }
     54 
     55 void SmallStringBase::reserve(u32 new_reserve)
     56 {
     57   const u32 real_reserve = new_reserve + 1;
     58   if (m_buffer_size >= real_reserve)
     59     return;
     60 
     61   if (m_on_heap)
     62   {
     63     char* new_ptr = static_cast<char*>(std::realloc(m_buffer, real_reserve));
     64     if (!new_ptr)
     65       Panic("Memory allocation failed.");
     66 
     67 #ifdef _DEBUG
     68     std::memset(new_ptr + m_length, 0, real_reserve - m_length);
     69 #endif
     70     m_buffer = new_ptr;
     71   }
     72   else
     73   {
     74     char* new_ptr = static_cast<char*>(std::malloc(real_reserve));
     75     if (!new_ptr)
     76       Panic("Memory allocation failed.");
     77 
     78     if (m_length > 0)
     79       std::memcpy(new_ptr, m_buffer, m_length);
     80 #ifdef _DEBUG
     81     std::memset(new_ptr + m_length, 0, real_reserve - m_length);
     82 #else
     83     new_ptr[m_length] = 0;
     84 #endif
     85     m_buffer = new_ptr;
     86     m_on_heap = true;
     87   }
     88 
     89   m_buffer_size = new_reserve;
     90 }
     91 
     92 void SmallStringBase::shrink_to_fit()
     93 {
     94   const u32 buffer_size = (m_length + 1);
     95   if (!m_on_heap || buffer_size == m_buffer_size)
     96     return;
     97 
     98   if (m_length == 0)
     99   {
    100     std::free(m_buffer);
    101     m_buffer = nullptr;
    102     m_buffer_size = 0;
    103     return;
    104   }
    105 
    106   char* new_ptr = static_cast<char*>(std::realloc(m_buffer, buffer_size));
    107   if (!new_ptr)
    108     Panic("Memory allocation failed.");
    109 
    110   m_buffer = new_ptr;
    111   m_buffer_size = buffer_size;
    112 }
    113 
    114 std::string_view SmallStringBase::view() const
    115 {
    116   return (m_length == 0) ? std::string_view() : std::string_view(m_buffer, m_length);
    117 }
    118 
    119 SmallStringBase& SmallStringBase::operator=(SmallStringBase&& move)
    120 {
    121   assign(move);
    122   return *this;
    123 }
    124 
    125 SmallStringBase& SmallStringBase::operator=(const std::string_view str)
    126 {
    127   assign(str);
    128   return *this;
    129 }
    130 
    131 SmallStringBase& SmallStringBase::operator=(const std::string& str)
    132 {
    133   assign(str);
    134   return *this;
    135 }
    136 
    137 SmallStringBase& SmallStringBase::operator=(const char* str)
    138 {
    139   assign(str);
    140   return *this;
    141 }
    142 
    143 SmallStringBase& SmallStringBase::operator=(const SmallStringBase& copy)
    144 {
    145   assign(copy);
    146   return *this;
    147 }
    148 
    149 void SmallStringBase::make_room_for(u32 space)
    150 {
    151   const u32 required_size = m_length + space + 1;
    152   if (m_buffer_size >= required_size)
    153     return;
    154 
    155   reserve(std::max(required_size, m_buffer_size * 2));
    156 }
    157 
    158 void SmallStringBase::append(const char* str, u32 length)
    159 {
    160   if (length == 0)
    161     return;
    162 
    163   make_room_for(length);
    164 
    165   DebugAssert((length + m_length) < m_buffer_size);
    166 
    167   std::memcpy(m_buffer + m_length, str, length);
    168   m_length += length;
    169   m_buffer[m_length] = 0;
    170 }
    171 
    172 void SmallStringBase::append_hex(const void* data, size_t len)
    173 {
    174   if (len == 0)
    175     return;
    176 
    177   make_room_for(static_cast<u32>(len) * 4);
    178   const u8* bytes = static_cast<const u8*>(data);
    179   append_format("{:02X}", bytes[0]);
    180   for (size_t i = 1; i < len; i++)
    181     append_format(", {:02X}", bytes[i]);
    182 }
    183 
    184 void SmallStringBase::prepend(const char* str, u32 length)
    185 {
    186   if (length == 0)
    187     return;
    188 
    189   make_room_for(length);
    190 
    191   DebugAssert((length + m_length) < m_buffer_size);
    192 
    193   std::memmove(m_buffer + length, m_buffer, m_length);
    194   std::memcpy(m_buffer, str, length);
    195   m_length += length;
    196   m_buffer[m_length] = 0;
    197 }
    198 
    199 void SmallStringBase::append(char c)
    200 {
    201   append(&c, 1);
    202 }
    203 
    204 void SmallStringBase::append(const SmallStringBase& str)
    205 {
    206   append(str.m_buffer, str.m_length);
    207 }
    208 
    209 void SmallStringBase::append(const char* str)
    210 {
    211   append(str, static_cast<u32>(std::strlen(str)));
    212 }
    213 
    214 void SmallStringBase::append(const std::string& str)
    215 {
    216   append(str.c_str(), static_cast<u32>(str.length()));
    217 }
    218 
    219 void SmallStringBase::append(const std::string_view str)
    220 {
    221   append(str.data(), static_cast<u32>(str.length()));
    222 }
    223 
    224 void SmallStringBase::append_sprintf(const char* format, ...)
    225 {
    226   std::va_list ap;
    227   va_start(ap, format);
    228   append_vsprintf(format, ap);
    229   va_end(ap);
    230 }
    231 
    232 void SmallStringBase::append_vsprintf(const char* format, va_list ap)
    233 {
    234   // We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
    235   // but 1KB should be enough for most strings.
    236   char stack_buffer[1024];
    237   char* heap_buffer = nullptr;
    238   char* buffer = stack_buffer;
    239   u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
    240   u32 written;
    241 
    242   for (;;)
    243   {
    244     std::va_list ap_copy;
    245     va_copy(ap_copy, ap);
    246     const int ret = std::vsnprintf(buffer, buffer_size, format, ap_copy);
    247     va_end(ap_copy);
    248     if (ret < 0 || (static_cast<u32>(ret) >= (buffer_size - 1)))
    249     {
    250       buffer_size *= 2;
    251       buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
    252       if (!buffer) [[unlikely]]
    253         Panic("Memory allocation failed.");
    254       continue;
    255     }
    256 
    257     written = static_cast<u32>(ret);
    258     break;
    259   }
    260 
    261   append(buffer, written);
    262 
    263   if (heap_buffer)
    264     std::free(heap_buffer);
    265 }
    266 
    267 void SmallStringBase::prepend(char c)
    268 {
    269   prepend(&c, 1);
    270 }
    271 
    272 void SmallStringBase::prepend(const SmallStringBase& str)
    273 {
    274   prepend(str.m_buffer, str.m_length);
    275 }
    276 
    277 void SmallStringBase::prepend(const char* str)
    278 {
    279   prepend(str, static_cast<u32>(std::strlen(str)));
    280 }
    281 
    282 void SmallStringBase::prepend(const std::string& str)
    283 {
    284   prepend(str.c_str(), static_cast<u32>(str.length()));
    285 }
    286 
    287 void SmallStringBase::prepend(const std::string_view str)
    288 {
    289   prepend(str.data(), static_cast<u32>(str.length()));
    290 }
    291 
    292 void SmallStringBase::prepend_sprintf(const char* format, ...)
    293 {
    294   va_list ap;
    295   va_start(ap, format);
    296   prepend_vsprintf(format, ap);
    297   va_end(ap);
    298 }
    299 
    300 void SmallStringBase::prepend_vsprintf(const char* format, va_list ArgPtr)
    301 {
    302   // We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
    303   // but 1KB should be enough for most strings.
    304   char stack_buffer[1024];
    305   char* heap_buffer = nullptr;
    306   char* buffer = stack_buffer;
    307   u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
    308   u32 written;
    309 
    310   for (;;)
    311   {
    312     int ret = std::vsnprintf(buffer, buffer_size, format, ArgPtr);
    313     if (ret < 0 || (static_cast<u32>(ret) >= (buffer_size - 1)))
    314     {
    315       buffer_size *= 2;
    316       buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
    317       if (!buffer) [[unlikely]]
    318         Panic("Memory allocation failed.");
    319       continue;
    320     }
    321 
    322     written = static_cast<u32>(ret);
    323     break;
    324   }
    325 
    326   prepend(buffer, written);
    327 
    328   if (heap_buffer)
    329     std::free(heap_buffer);
    330 }
    331 
    332 void SmallStringBase::insert(s32 offset, const char* str)
    333 {
    334   insert(offset, str, static_cast<u32>(std::strlen(str)));
    335 }
    336 
    337 void SmallStringBase::insert(s32 offset, const SmallStringBase& str)
    338 {
    339   insert(offset, str, str.m_length);
    340 }
    341 
    342 void SmallStringBase::insert(s32 offset, const char* str, u32 length)
    343 {
    344   if (length == 0)
    345     return;
    346 
    347   make_room_for(length);
    348 
    349   // calc real offset
    350   u32 real_offset;
    351   if (offset < 0)
    352     real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + offset));
    353   else
    354     real_offset = std::min(static_cast<u32>(offset), m_length);
    355 
    356   // determine number of characters after offset
    357   DebugAssert(real_offset <= m_length);
    358   const u32 chars_after_offset = m_length - real_offset;
    359   if (chars_after_offset > 0)
    360     std::memmove(m_buffer + offset + length, m_buffer + offset, chars_after_offset);
    361 
    362   // insert the string
    363   std::memcpy(m_buffer + real_offset, str, length);
    364   m_length += length;
    365 
    366   // ensure null termination
    367   m_buffer[m_length] = 0;
    368 }
    369 
    370 void SmallStringBase::insert(s32 offset, const std::string& str)
    371 {
    372   insert(offset, str.c_str(), static_cast<u32>(str.size()));
    373 }
    374 
    375 void SmallStringBase::insert(s32 offset, const std::string_view str)
    376 {
    377   insert(offset, str.data(), static_cast<u32>(str.size()));
    378 }
    379 
    380 void SmallStringBase::sprintf(const char* format, ...)
    381 {
    382   va_list ap;
    383   va_start(ap, format);
    384   vsprintf(format, ap);
    385   va_end(ap);
    386 }
    387 
    388 void SmallStringBase::vsprintf(const char* format, va_list ap)
    389 {
    390   clear();
    391   append_vsprintf(format, ap);
    392 }
    393 
    394 void SmallStringBase::assign(const SmallStringBase& copy)
    395 {
    396   assign(copy.c_str(), copy.length());
    397 }
    398 
    399 void SmallStringBase::assign(const char* str)
    400 {
    401   assign(str, static_cast<u32>(std::strlen(str)));
    402 }
    403 
    404 void SmallStringBase::assign(const char* str, u32 length)
    405 {
    406   clear();
    407   if (length > 0)
    408     append(str, length);
    409 }
    410 
    411 void SmallStringBase::assign(SmallStringBase&& move)
    412 {
    413   if (move.m_on_heap)
    414   {
    415     if (m_on_heap)
    416       std::free(m_buffer);
    417     m_buffer = move.m_buffer;
    418     m_buffer_size = move.m_buffer_size;
    419     m_length = move.m_length;
    420     m_on_heap = true;
    421     move.m_buffer = nullptr;
    422     move.m_buffer_size = 0;
    423     move.m_length = 0;
    424   }
    425   else
    426   {
    427     assign(move.m_buffer, move.m_buffer_size);
    428   }
    429 }
    430 
    431 void SmallStringBase::assign(const std::string& str)
    432 {
    433   clear();
    434   append(str.data(), static_cast<u32>(str.size()));
    435 }
    436 
    437 void SmallStringBase::assign(const std::string_view str)
    438 {
    439   clear();
    440   append(str.data(), static_cast<u32>(str.size()));
    441 }
    442 
    443 #ifdef _WIN32
    444 
    445 void SmallStringBase::assign(const std::wstring_view wstr)
    446 {
    447   int mblen =
    448     WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.length()), nullptr, 0, nullptr, nullptr);
    449   if (mblen < 0)
    450   {
    451     clear();
    452     return;
    453   }
    454 
    455   reserve(static_cast<u32>(mblen));
    456   if (mblen > 0 && WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.length()), m_buffer, mblen,
    457                                        nullptr, nullptr) < 0)
    458   {
    459     clear();
    460     return;
    461   }
    462 
    463   m_length = static_cast<u32>(mblen);
    464 }
    465 
    466 std::wstring SmallStringBase::wstring() const
    467 {
    468   return StringUtil::UTF8StringToWideString(view());
    469 }
    470 
    471 #endif
    472 
    473 void SmallStringBase::vformat(fmt::string_view fmt, fmt::format_args args)
    474 {
    475   clear();
    476   fmt::vformat_to(std::back_inserter(*this), fmt, args);
    477 }
    478 
    479 bool SmallStringBase::equals(const char* str) const
    480 {
    481   if (m_length == 0)
    482     return (std::strlen(str) == 0);
    483   else
    484     return (std::strcmp(m_buffer, str) == 0);
    485 }
    486 
    487 bool SmallStringBase::equals(const SmallStringBase& str) const
    488 {
    489   return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
    490 }
    491 
    492 bool SmallStringBase::equals(const std::string_view str) const
    493 {
    494   return (m_length == static_cast<u32>(str.length()) &&
    495           (m_length == 0 || std::memcmp(m_buffer, str.data(), m_length) == 0));
    496 }
    497 
    498 bool SmallStringBase::equals(const std::string& str) const
    499 {
    500   return (m_length == static_cast<u32>(str.length()) &&
    501           (m_length == 0 || std::memcmp(m_buffer, str.data(), m_length) == 0));
    502 }
    503 
    504 bool SmallStringBase::iequals(const char* otherText) const
    505 {
    506   if (m_length == 0)
    507     return (std::strlen(otherText) == 0);
    508   else
    509     return StringUtil::EqualNoCase(view(), otherText);
    510 }
    511 
    512 bool SmallStringBase::iequals(const SmallStringBase& str) const
    513 {
    514   return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
    515 }
    516 
    517 bool SmallStringBase::iequals(const std::string_view str) const
    518 {
    519   return (m_length == static_cast<u32>(str.length()) &&
    520           (m_length == 0 || StringUtil::Strncasecmp(m_buffer, str.data(), m_length) == 0));
    521 }
    522 
    523 bool SmallStringBase::iequals(const std::string& str) const
    524 {
    525   return (m_length == static_cast<u32>(str.length()) &&
    526           (m_length == 0 || StringUtil::Strncasecmp(m_buffer, str.data(), m_length) == 0));
    527 }
    528 
    529 int SmallStringBase::compare(const char* otherText) const
    530 {
    531   return compare(std::string_view(otherText));
    532 }
    533 
    534 int SmallStringBase::compare(const SmallStringBase& str) const
    535 {
    536   if (m_length == 0)
    537     return (str.m_length == 0) ? 0 : -1;
    538   else if (str.m_length == 0)
    539     return 1;
    540 
    541   const int res = std::strncmp(m_buffer, str.m_buffer, std::min(m_length, str.m_length));
    542   if (m_length == str.m_length || res != 0)
    543     return res;
    544   else
    545     return (m_length > str.m_length) ? 1 : -1;
    546 }
    547 
    548 int SmallStringBase::compare(const std::string_view str) const
    549 {
    550   const u32 slength = static_cast<u32>(str.length());
    551   if (m_length == 0)
    552     return (slength == 0) ? 0 : -1;
    553   else if (slength == 0)
    554     return 1;
    555 
    556   const int res = std::strncmp(m_buffer, str.data(), std::min(m_length, slength));
    557   if (m_length == slength || res != 0)
    558     return res;
    559   else
    560     return (m_length > slength) ? 1 : -1;
    561 }
    562 
    563 int SmallStringBase::compare(const std::string& str) const
    564 {
    565   const u32 slength = static_cast<u32>(str.length());
    566   if (m_length == 0)
    567     return (slength == 0) ? 0 : -1;
    568   else if (slength == 0)
    569     return 1;
    570 
    571   const int res = std::strncmp(m_buffer, str.data(), std::min(m_length, slength));
    572   if (m_length == slength || res != 0)
    573     return res;
    574   else
    575     return (m_length > slength) ? 1 : -1;
    576 }
    577 
    578 int SmallStringBase::icompare(const char* otherText) const
    579 {
    580   return icompare(std::string_view(otherText));
    581 }
    582 
    583 int SmallStringBase::icompare(const SmallStringBase& str) const
    584 {
    585   if (m_length == 0)
    586     return (str.m_length == 0) ? 0 : -1;
    587   else if (str.m_length == 0)
    588     return 1;
    589 
    590   const int res = StringUtil::Strncasecmp(m_buffer, str.m_buffer, std::min(m_length, str.m_length));
    591   if (m_length == str.m_length || res != 0)
    592     return res;
    593   else
    594     return (m_length > str.m_length) ? 1 : -1;
    595 }
    596 
    597 int SmallStringBase::icompare(const std::string_view str) const
    598 {
    599   const u32 slength = static_cast<u32>(str.length());
    600   if (m_length == 0)
    601     return (slength == 0) ? 0 : -1;
    602   else if (slength == 0)
    603     return 1;
    604 
    605   const int res = StringUtil::Strncasecmp(m_buffer, str.data(), std::min(m_length, slength));
    606   if (m_length == slength || res != 0)
    607     return res;
    608   else
    609     return (m_length > slength) ? 1 : -1;
    610 }
    611 
    612 int SmallStringBase::icompare(const std::string& str) const
    613 {
    614   const u32 slength = static_cast<u32>(str.length());
    615   if (m_length == 0)
    616     return (slength == 0) ? 0 : -1;
    617   else if (slength == 0)
    618     return 1;
    619 
    620   const int res = StringUtil::Strncasecmp(m_buffer, str.data(), std::min(m_length, slength));
    621   if (m_length == slength || res != 0)
    622     return res;
    623   else
    624     return (m_length > slength) ? 1 : -1;
    625 }
    626 
    627 bool SmallStringBase::starts_with(const char* str, bool case_sensitive) const
    628 {
    629   const u32 other_length = static_cast<u32>(std::strlen(str));
    630   if (other_length > m_length)
    631     return false;
    632 
    633   return (case_sensitive) ? (std::strncmp(str, m_buffer, other_length) == 0) :
    634                             (StringUtil::Strncasecmp(str, m_buffer, other_length) == 0);
    635 }
    636 
    637 bool SmallStringBase::starts_with(const SmallStringBase& str, bool case_sensitive) const
    638 {
    639   const u32 other_length = str.m_length;
    640   if (other_length > m_length)
    641     return false;
    642 
    643   return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer, other_length) == 0) :
    644                             (StringUtil::Strncasecmp(str.m_buffer, m_buffer, other_length) == 0);
    645 }
    646 
    647 bool SmallStringBase::starts_with(const std::string_view str, bool case_sensitive) const
    648 {
    649   const u32 other_length = static_cast<u32>(str.length());
    650   if (other_length > m_length)
    651     return false;
    652 
    653   return (case_sensitive) ? (std::strncmp(str.data(), m_buffer, other_length) == 0) :
    654                             (StringUtil::Strncasecmp(str.data(), m_buffer, other_length) == 0);
    655 }
    656 
    657 bool SmallStringBase::starts_with(const std::string& str, bool case_sensitive) const
    658 {
    659   const u32 other_length = static_cast<u32>(str.length());
    660   if (other_length > m_length)
    661     return false;
    662 
    663   return (case_sensitive) ? (std::strncmp(str.data(), m_buffer, other_length) == 0) :
    664                             (StringUtil::Strncasecmp(str.data(), m_buffer, other_length) == 0);
    665 }
    666 
    667 bool SmallStringBase::ends_with(const char* str, bool case_sensitive) const
    668 {
    669   const u32 other_length = static_cast<u32>(std::strlen(str));
    670   if (other_length > m_length)
    671     return false;
    672 
    673   u32 start_offset = m_length - other_length;
    674   return (case_sensitive) ? (std::strncmp(str, m_buffer + start_offset, other_length) == 0) :
    675                             (StringUtil::Strncasecmp(str, m_buffer + start_offset, other_length) == 0);
    676 }
    677 
    678 bool SmallStringBase::ends_with(const SmallStringBase& str, bool case_sensitive) const
    679 {
    680   const u32 other_length = str.m_length;
    681   if (other_length > m_length)
    682     return false;
    683 
    684   const u32 start_offset = m_length - other_length;
    685   return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer + start_offset, other_length) == 0) :
    686                             (StringUtil::Strncasecmp(str.m_buffer, m_buffer + start_offset, other_length) == 0);
    687 }
    688 
    689 bool SmallStringBase::ends_with(const std::string_view str, bool case_sensitive) const
    690 {
    691   const u32 other_length = static_cast<u32>(str.length());
    692   if (other_length > m_length)
    693     return false;
    694 
    695   const u32 start_offset = m_length - other_length;
    696   return (case_sensitive) ? (std::strncmp(str.data(), m_buffer + start_offset, other_length) == 0) :
    697                             (StringUtil::Strncasecmp(str.data(), m_buffer + start_offset, other_length) == 0);
    698 }
    699 
    700 bool SmallStringBase::ends_with(const std::string& str, bool case_sensitive) const
    701 {
    702   const u32 other_length = static_cast<u32>(str.length());
    703   if (other_length > m_length)
    704     return false;
    705 
    706   const u32 start_offset = m_length - other_length;
    707   return (case_sensitive) ? (std::strncmp(str.data(), m_buffer + start_offset, other_length) == 0) :
    708                             (StringUtil::Strncasecmp(str.data(), m_buffer + start_offset, other_length) == 0);
    709 }
    710 
    711 void SmallStringBase::clear()
    712 {
    713   // in debug, zero whole string, in release, zero only the first character
    714 #if _DEBUG
    715   std::memset(m_buffer, 0, m_buffer_size);
    716 #else
    717   m_buffer[0] = '\0';
    718 #endif
    719   m_length = 0;
    720 }
    721 
    722 s32 SmallStringBase::find(char c, u32 offset) const
    723 {
    724   if (m_length == 0)
    725     return -1;
    726 
    727   DebugAssert(offset <= m_length);
    728   const char* at = std::strchr(m_buffer + offset, c);
    729   return at ? static_cast<s32>(at - m_buffer) : -1;
    730 }
    731 
    732 s32 SmallStringBase::rfind(char c, u32 offset) const
    733 {
    734   if (m_length == 0)
    735     return -1;
    736 
    737   DebugAssert(offset <= m_length);
    738   const char* at = std::strrchr(m_buffer + offset, c);
    739   return at ? static_cast<s32>(at - m_buffer) : -1;
    740 }
    741 
    742 s32 SmallStringBase::find(const char* str, u32 offset) const
    743 {
    744   if (m_length == 0)
    745     return -1;
    746 
    747   DebugAssert(offset <= m_length);
    748   const char* at = std::strstr(m_buffer + offset, str);
    749   return at ? static_cast<s32>(at - m_buffer) : -1;
    750 }
    751 
    752 u32 SmallStringBase::count(char ch) const
    753 {
    754   const char* ptr = m_buffer;
    755   const char* end = ptr + m_length;
    756   u32 count = 0;
    757   while (ptr != end)
    758     count += static_cast<u32>(*(ptr++) == ch);
    759   return count;
    760 }
    761 
    762 u32 SmallStringBase::replace(const char* search, const char* replacement)
    763 {
    764   const u32 search_length = static_cast<u32>(std::strlen(search));
    765   const u32 replacement_length = static_cast<u32>(std::strlen(replacement));
    766 
    767   s32 offset = 0;
    768   u32 count = 0;
    769   for (;;)
    770   {
    771     offset = find(search, static_cast<u32>(offset));
    772     if (offset < 0)
    773       break;
    774 
    775     const u32 new_length = m_length - search_length + replacement_length;
    776     reserve(new_length);
    777     m_length = new_length;
    778 
    779     const u32 chars_after_offset = (m_length - static_cast<u32>(offset));
    780     DebugAssert(chars_after_offset >= search_length);
    781     if (chars_after_offset > search_length)
    782     {
    783       std::memmove(&m_buffer[static_cast<u32>(offset) + replacement_length],
    784                    &m_buffer[static_cast<u32>(offset) + search_length], chars_after_offset - search_length);
    785       std::memcpy(&m_buffer[static_cast<u32>(offset)], replacement, replacement_length);
    786     }
    787     else
    788     {
    789       // at end of string
    790       std::memcpy(&m_buffer[static_cast<u32>(offset)], replacement, replacement_length);
    791       m_buffer[static_cast<u32>(offset) + replacement_length] = '\0';
    792     }
    793 
    794     offset += replacement_length;
    795     count++;
    796   }
    797 
    798   return count;
    799 }
    800 
    801 void SmallStringBase::resize(u32 new_size, char fill, bool shrink_if_smaller)
    802 {
    803   // if going larger, or we don't own the buffer, realloc
    804   if (new_size >= m_buffer_size)
    805   {
    806     reserve(new_size);
    807 
    808     if (m_length < new_size)
    809     {
    810       std::memset(m_buffer + m_length, fill, m_buffer_size - m_length - 1);
    811     }
    812 
    813     m_length = new_size;
    814   }
    815   else
    816   {
    817     // update length and terminator
    818 #if _DEBUG
    819     std::memset(m_buffer + new_size, 0, m_buffer_size - new_size);
    820 #else
    821     m_buffer[new_size] = 0;
    822 #endif
    823     m_length = new_size;
    824 
    825     // shrink if requested
    826     if (shrink_if_smaller)
    827       shrink_to_fit();
    828   }
    829 }
    830 
    831 void SmallStringBase::update_size()
    832 {
    833   m_length = static_cast<u32>(std::strlen(m_buffer));
    834 }
    835 
    836 std::string_view SmallStringBase::substr(s32 offset, s32 count) const
    837 {
    838   // calc real offset
    839   u32 real_offset;
    840   if (offset < 0)
    841     real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
    842   else
    843     real_offset = std::min((u32)offset, m_length);
    844 
    845   // calc real count
    846   u32 real_count;
    847   if (count < 0)
    848   {
    849     real_count =
    850       std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
    851   }
    852   else
    853   {
    854     real_count = std::min(m_length - real_offset, static_cast<u32>(count));
    855   }
    856 
    857   return (real_count > 0) ? std::string_view(m_buffer + real_offset, real_count) : std::string_view();
    858 }
    859 
    860 void SmallStringBase::erase(s32 offset, s32 count)
    861 {
    862   // calc real offset
    863   u32 real_offset;
    864   if (offset < 0)
    865     real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
    866   else
    867     real_offset = std::min((u32)offset, m_length);
    868 
    869   // calc real count
    870   u32 real_count;
    871   if (count < 0)
    872   {
    873     real_count =
    874       std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
    875   }
    876   else
    877   {
    878     real_count = std::min(m_length - real_offset, static_cast<u32>(count));
    879   }
    880 
    881   // Fastpath: offset == 0, count < 0, wipe whole string.
    882   if (real_offset == 0 && real_count == m_length)
    883   {
    884     clear();
    885     return;
    886   }
    887 
    888   // Fastpath: offset >= 0, count < 0, wipe everything after offset + count
    889   if ((real_offset + real_count) == m_length)
    890   {
    891     m_length -= real_count;
    892 #ifdef _DEBUG
    893     std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
    894 #else
    895     m_buffer[m_length] = 0;
    896 #endif
    897   }
    898   // Slowpath: offset >= 0, count < length
    899   else
    900   {
    901     const u32 after_erase_block = m_length - real_offset - real_count;
    902     DebugAssert(after_erase_block > 0);
    903 
    904     std::memmove(m_buffer + offset, m_buffer + real_offset + real_count, after_erase_block);
    905     m_length = m_length - real_count;
    906 
    907 #ifdef _DEBUG
    908     std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
    909 #else
    910     m_buffer[m_length] = 0;
    911 #endif
    912   }
    913 }