mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-21 07:15:40 -04:00
Common: Drop String, add SmallString
This commit is contained in:
@ -46,8 +46,8 @@ add_library(common
|
||||
settings_interface.h
|
||||
sha1_digest.cpp
|
||||
sha1_digest.h
|
||||
string.cpp
|
||||
string.h
|
||||
small_string.cpp
|
||||
small_string.h
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
thirdparty/thread_pool.cpp
|
||||
|
@ -34,7 +34,7 @@
|
||||
<ClInclude Include="scoped_guard.h" />
|
||||
<ClInclude Include="settings_interface.h" />
|
||||
<ClInclude Include="sha1_digest.h" />
|
||||
<ClInclude Include="string.h" />
|
||||
<ClInclude Include="small_string.h" />
|
||||
<ClInclude Include="heterogeneous_containers.h" />
|
||||
<ClInclude Include="string_util.h" />
|
||||
<ClInclude Include="thirdparty\StackWalker.h" />
|
||||
@ -62,7 +62,7 @@
|
||||
<ClCompile Include="minizip_helpers.cpp" />
|
||||
<ClCompile Include="progress_callback.cpp" />
|
||||
<ClCompile Include="sha1_digest.cpp" />
|
||||
<ClCompile Include="string.cpp" />
|
||||
<ClCompile Include="small_string.cpp" />
|
||||
<ClCompile Include="string_util.cpp" />
|
||||
<ClCompile Include="thirdparty\StackWalker.cpp" />
|
||||
<ClCompile Include="threading.cpp" />
|
||||
|
@ -7,7 +7,7 @@
|
||||
<ClInclude Include="heap_array.h" />
|
||||
<ClInclude Include="rectangle.h" />
|
||||
<ClInclude Include="log.h" />
|
||||
<ClInclude Include="string.h" />
|
||||
<ClInclude Include="small_string.h" />
|
||||
<ClInclude Include="byte_stream.h" />
|
||||
<ClInclude Include="timer.h" />
|
||||
<ClInclude Include="assert.h" />
|
||||
@ -46,7 +46,7 @@
|
||||
<ClInclude Include="memmap.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="string.cpp" />
|
||||
<ClCompile Include="small_string.cpp" />
|
||||
<ClCompile Include="byte_stream.cpp" />
|
||||
<ClCompile Include="log.cpp" />
|
||||
<ClCompile Include="timer.cpp" />
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "log.h"
|
||||
#include "assert.h"
|
||||
#include "file_system.h"
|
||||
#include "string.h"
|
||||
#include "small_string.h"
|
||||
#include "timer.h"
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
@ -36,7 +36,7 @@ static LOGLEVEL s_filter_level = LOGLEVEL_TRACE;
|
||||
static Common::Timer::Value s_startTimeStamp = Common::Timer::GetCurrentValue();
|
||||
|
||||
static bool s_console_output_enabled = false;
|
||||
static String s_console_output_channel_filter;
|
||||
static std::string s_console_output_channel_filter;
|
||||
static LOGLEVEL s_console_output_level_filter = LOGLEVEL_TRACE;
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -46,12 +46,12 @@ static HANDLE s_hConsoleStdErr = NULL;
|
||||
#endif
|
||||
|
||||
static bool s_debug_output_enabled = false;
|
||||
static String s_debug_output_channel_filter;
|
||||
static std::string s_debug_output_channel_filter;
|
||||
static LOGLEVEL s_debug_output_level_filter = LOGLEVEL_TRACE;
|
||||
|
||||
static bool s_file_output_enabled = false;
|
||||
static bool s_file_output_timestamp = false;
|
||||
static String s_file_output_channel_filter;
|
||||
static std::string s_file_output_channel_filter;
|
||||
static LOGLEVEL s_file_output_level_filter = LOGLEVEL_TRACE;
|
||||
std::unique_ptr<std::FILE, void (*)(std::FILE*)> s_fileOutputHandle(nullptr, [](std::FILE* fp) {
|
||||
if (fp)
|
||||
@ -242,7 +242,7 @@ static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName,
|
||||
LOGLEVEL level, const char* message)
|
||||
{
|
||||
if (!s_console_output_enabled || level > s_console_output_level_filter ||
|
||||
s_console_output_channel_filter.Find(channelName) >= 0)
|
||||
s_console_output_channel_filter.find(channelName) != std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -267,7 +267,7 @@ static void DebugOutputLogCallback(void* pUserParam, const char* channelName, co
|
||||
const char* message)
|
||||
{
|
||||
if (!s_debug_output_enabled || level > s_debug_output_level_filter ||
|
||||
s_debug_output_channel_filter.Find(functionName) >= 0)
|
||||
s_debug_output_channel_filter.find(functionName) != std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -393,7 +393,7 @@ void SetDebugOutputParams(bool enabled, const char* channelFilter /* = nullptr *
|
||||
static void FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||
const char* message)
|
||||
{
|
||||
if (level > s_file_output_level_filter || s_file_output_channel_filter.Find(channelName) >= 0)
|
||||
if (level > s_file_output_level_filter || s_file_output_channel_filter.find(channelName) != std::string::npos)
|
||||
return;
|
||||
|
||||
FormatLogMessageAndPrint(
|
||||
|
@ -18,7 +18,7 @@ void ProgressCallback::SetFormattedStatusText(const char* Format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, Format);
|
||||
str.FormatVA(Format, ap);
|
||||
str.format_va(Format, ap);
|
||||
va_end(ap);
|
||||
|
||||
SetStatusText(str);
|
||||
@ -30,7 +30,7 @@ void ProgressCallback::DisplayFormattedError(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
DisplayError(str);
|
||||
@ -42,7 +42,7 @@ void ProgressCallback::DisplayFormattedWarning(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
DisplayWarning(str);
|
||||
@ -54,7 +54,7 @@ void ProgressCallback::DisplayFormattedInformation(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
DisplayInformation(str);
|
||||
@ -66,7 +66,7 @@ void ProgressCallback::DisplayFormattedDebugMessage(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
DisplayDebugMessage(str);
|
||||
@ -78,7 +78,7 @@ void ProgressCallback::DisplayFormattedModalError(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
ModalError(str);
|
||||
@ -90,7 +90,7 @@ bool ProgressCallback::DisplayFormattedModalConfirmation(const char* format, ...
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ModalConfirmation(str);
|
||||
@ -102,7 +102,7 @@ void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...)
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
str.FormatVA(format, ap);
|
||||
str.format_va(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
ModalInformation(str);
|
||||
@ -312,10 +312,10 @@ void ConsoleProgressCallback::Clear()
|
||||
{
|
||||
SmallString message;
|
||||
for (u32 i = 0; i < COLUMNS; i++)
|
||||
message.AppendCharacter(' ');
|
||||
message.AppendCharacter('\r');
|
||||
message.append(' ');
|
||||
message.append('\r');
|
||||
|
||||
std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr);
|
||||
std::fwrite(message.c_str(), message.length(), 1, stderr);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ void ConsoleProgressCallback::Redraw(bool update_value_only)
|
||||
if (percent_complete > 100.0f)
|
||||
percent_complete = 100.0f;
|
||||
|
||||
const u32 current_length = m_status_text.GetLength() + 14;
|
||||
const u32 current_length = static_cast<u32>(m_status_text.length()) + 14;
|
||||
const u32 max_bar_length = (current_length < COLUMNS) ? COLUMNS - current_length : 0;
|
||||
const u32 current_bar_length =
|
||||
(max_bar_length > 0) ? (static_cast<u32>(percent_complete / 100.0f * (float)max_bar_length)) : 0;
|
||||
@ -340,25 +340,25 @@ void ConsoleProgressCallback::Redraw(bool update_value_only)
|
||||
m_last_percent_complete = percent_complete;
|
||||
|
||||
SmallString message;
|
||||
message.AppendString(m_status_text);
|
||||
message.AppendFormattedString(" [%.2f%%]", percent_complete);
|
||||
message.append(m_status_text);
|
||||
message.append_fmt(" [{:.2f}%]", percent_complete);
|
||||
|
||||
if (max_bar_length > 0)
|
||||
{
|
||||
message.AppendString(" |");
|
||||
message.append(" |");
|
||||
|
||||
u32 i;
|
||||
for (i = 0; i < current_bar_length; i++)
|
||||
message.AppendCharacter('=');
|
||||
message.append('=');
|
||||
for (; i < max_bar_length; i++)
|
||||
message.AppendCharacter(' ');
|
||||
message.append(' ');
|
||||
|
||||
message.AppendString("|");
|
||||
message.append('|');
|
||||
}
|
||||
|
||||
message.AppendCharacter('\r');
|
||||
message.append('\r');
|
||||
|
||||
std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr);
|
||||
std::fwrite(message.c_str(), message.length(), 1, stderr);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "string.h"
|
||||
#include "small_string.h"
|
||||
#include "types.h"
|
||||
|
||||
class ByteStream;
|
||||
@ -73,7 +73,7 @@ protected:
|
||||
struct State
|
||||
{
|
||||
State* next_saved_state;
|
||||
String status_text;
|
||||
std::string status_text;
|
||||
u32 progress_range;
|
||||
u32 progress_value;
|
||||
u32 base_progress_value;
|
||||
@ -82,7 +82,7 @@ protected:
|
||||
|
||||
bool m_cancellable;
|
||||
bool m_cancelled;
|
||||
String m_status_text;
|
||||
std::string m_status_text;
|
||||
u32 m_progress_range;
|
||||
u32 m_progress_value;
|
||||
|
||||
|
699
src/common/small_string.cpp
Normal file
699
src/common/small_string.cpp
Normal file
@ -0,0 +1,699 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "small_string.h"
|
||||
#include "assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CASE_COMPARE _stricmp
|
||||
#define CASE_N_COMPARE _strnicmp
|
||||
#else
|
||||
#define CASE_COMPARE strcasecmp
|
||||
#define CASE_N_COMPARE strncasecmp
|
||||
#endif
|
||||
|
||||
SmallStringBase::SmallStringBase() = default;
|
||||
|
||||
SmallStringBase::SmallStringBase(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy.m_buffer, copy.m_length);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const char* str)
|
||||
{
|
||||
assign(str);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const char* str, u32 count)
|
||||
{
|
||||
assign(str, count);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(SmallStringBase&& move)
|
||||
{
|
||||
assign(std::move(move));
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const std::string_view& sv)
|
||||
{
|
||||
assign(sv);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const std::string& str)
|
||||
{
|
||||
assign(str);
|
||||
}
|
||||
|
||||
SmallStringBase::~SmallStringBase()
|
||||
{
|
||||
if (m_on_heap)
|
||||
std::free(m_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::reserve(u32 new_reserve)
|
||||
{
|
||||
if (m_buffer_size >= new_reserve)
|
||||
return;
|
||||
|
||||
if (m_on_heap)
|
||||
{
|
||||
char* new_ptr = static_cast<char*>(std::realloc(m_buffer, new_reserve));
|
||||
if (!new_ptr)
|
||||
Panic("Memory allocation failed.");
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(new_ptr + m_length, 0, new_reserve - m_length);
|
||||
#endif
|
||||
m_buffer = new_ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
char* new_ptr = static_cast<char*>(std::malloc(new_reserve));
|
||||
if (!new_ptr)
|
||||
Panic("Memory allocation failed.");
|
||||
|
||||
if (m_length > 0)
|
||||
std::memcpy(new_ptr, m_buffer, m_length);
|
||||
#ifdef _DEBUG
|
||||
std::memset(new_ptr + m_length, 0, new_reserve - m_length);
|
||||
#else
|
||||
new_ptr[m_length] = 0;
|
||||
#endif
|
||||
m_buffer = new_ptr;
|
||||
m_on_heap = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SmallStringBase::shrink_to_fit()
|
||||
{
|
||||
if (!m_on_heap || m_length == m_buffer_size)
|
||||
return;
|
||||
|
||||
if (m_length == 0)
|
||||
{
|
||||
std::free(m_buffer);
|
||||
m_buffer_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char* new_ptr = static_cast<char*>(std::realloc(m_buffer, m_length));
|
||||
if (!new_ptr)
|
||||
Panic("Memory allocation failed.");
|
||||
|
||||
m_buffer = new_ptr;
|
||||
m_buffer_size = m_length;
|
||||
}
|
||||
|
||||
std::string_view SmallStringBase::view() const
|
||||
{
|
||||
return (m_length == 0) ? std::string_view() : std::string_view(m_buffer, m_length);
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(SmallStringBase&& move)
|
||||
{
|
||||
assign(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const std::string_view& str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const std::string& str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const char* str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SmallStringBase::make_room_for(u32 space)
|
||||
{
|
||||
const u32 required_size = m_length + space + 1;
|
||||
if (m_buffer_size >= required_size)
|
||||
return;
|
||||
|
||||
reserve(std::max(required_size, m_buffer_size * 2));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
DebugAssert((length + m_length) < m_buffer_size);
|
||||
|
||||
std::memcpy(m_buffer + m_length, str, length);
|
||||
m_length += length;
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
DebugAssert((length + m_length) < m_buffer_size);
|
||||
|
||||
std::memmove(m_buffer + length, m_buffer, m_length);
|
||||
std::memcpy(m_buffer, str, length);
|
||||
m_length += length;
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::append(char c)
|
||||
{
|
||||
append(&c, 1);
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const SmallStringBase& str)
|
||||
{
|
||||
append(str.m_buffer, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const char* str)
|
||||
{
|
||||
append(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const std::string& str)
|
||||
{
|
||||
append(str.c_str(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const std::string_view& str)
|
||||
{
|
||||
append(str.data(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::append_format(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
append_format_va(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::append_format_va(const char* format, va_list ap)
|
||||
{
|
||||
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
||||
// but 1KB should be enough for most strings.
|
||||
char stack_buffer[1024];
|
||||
char* heap_buffer = nullptr;
|
||||
char* buffer = stack_buffer;
|
||||
u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
|
||||
u32 written;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::va_list ap_copy;
|
||||
va_copy(ap_copy, ap);
|
||||
const int ret = std::vsnprintf(buffer, buffer_size, format, ap_copy);
|
||||
va_end(ap_copy);
|
||||
if (ret < 0 || ((u32)ret >= (buffer_size - 1)))
|
||||
{
|
||||
buffer_size *= 2;
|
||||
buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
written = static_cast<u32>(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
append(buffer, written);
|
||||
|
||||
if (heap_buffer)
|
||||
std::free(heap_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(char c)
|
||||
{
|
||||
prepend(&c, 1);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const SmallStringBase& str)
|
||||
{
|
||||
prepend(str.m_buffer, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const char* str)
|
||||
{
|
||||
prepend(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const std::string& str)
|
||||
{
|
||||
prepend(str.c_str(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const std::string_view& str)
|
||||
{
|
||||
prepend(str.data(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend_format(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
prepend_format_va(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend_format_va(const char* format, va_list ArgPtr)
|
||||
{
|
||||
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
||||
// but 1KB should be enough for most strings.
|
||||
char stack_buffer[1024];
|
||||
char* heap_buffer = NULL;
|
||||
char* buffer = stack_buffer;
|
||||
u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
|
||||
u32 written;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int ret = std::vsnprintf(buffer, buffer_size, format, ArgPtr);
|
||||
if (ret < 0 || (static_cast<u32>(ret) >= (buffer_size - 1)))
|
||||
{
|
||||
buffer_size *= 2;
|
||||
buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
written = static_cast<u32>(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
prepend(buffer, written);
|
||||
|
||||
if (heap_buffer)
|
||||
std::free(heap_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const char* str)
|
||||
{
|
||||
insert(offset, str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const SmallStringBase& str)
|
||||
{
|
||||
insert(offset, str, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + offset));
|
||||
else
|
||||
real_offset = std::min(static_cast<u32>(offset), m_length);
|
||||
|
||||
// determine number of characters after offset
|
||||
DebugAssert(real_offset <= m_length);
|
||||
const u32 chars_after_offset = m_length - real_offset;
|
||||
if (chars_after_offset > 0)
|
||||
std::memmove(m_buffer + offset + length, m_buffer + offset, chars_after_offset);
|
||||
|
||||
// insert the string
|
||||
std::memcpy(m_buffer + real_offset, str, length);
|
||||
m_length += length;
|
||||
|
||||
// ensure null termination
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const std::string& str)
|
||||
{
|
||||
insert(offset, str.c_str(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const std::string_view& str)
|
||||
{
|
||||
insert(offset, str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::format(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
format_va(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::format_va(const char* format, va_list ap)
|
||||
{
|
||||
clear();
|
||||
append_format_va(format, ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy.c_str(), copy.length());
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const char* str)
|
||||
{
|
||||
assign(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const char* str, u32 length)
|
||||
{
|
||||
clear();
|
||||
if (length > 0)
|
||||
append(str, length);
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(SmallStringBase&& move)
|
||||
{
|
||||
if (move.m_on_heap)
|
||||
{
|
||||
if (m_on_heap)
|
||||
std::free(m_buffer);
|
||||
m_buffer = move.m_buffer;
|
||||
m_buffer_size = move.m_buffer_size;
|
||||
m_length = move.m_length;
|
||||
m_on_heap = true;
|
||||
move.m_buffer = nullptr;
|
||||
move.m_buffer_size = 0;
|
||||
move.m_length = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assign(move.m_buffer, move.m_buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const std::string& str)
|
||||
{
|
||||
clear();
|
||||
append(str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const std::string_view& str)
|
||||
{
|
||||
clear();
|
||||
append(str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const char* str) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (std::strlen(str) == 0);
|
||||
else
|
||||
return (std::strcmp(m_buffer, str) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const SmallStringBase& str) const
|
||||
{
|
||||
return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const std::string_view& str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || CASE_N_COMPARE(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const char* otherText) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (std::strlen(otherText) == 0);
|
||||
else
|
||||
return (CASE_COMPARE(m_buffer, otherText) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const SmallStringBase& str) const
|
||||
{
|
||||
return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const std::string_view& str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || CASE_N_COMPARE(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const SmallStringBase& str) const
|
||||
{
|
||||
return std::strcmp(m_buffer, str.m_buffer);
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const char* otherText) const
|
||||
{
|
||||
return std::strcmp(m_buffer, otherText);
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const SmallStringBase& otherString) const
|
||||
{
|
||||
return CASE_COMPARE(m_buffer, otherString.m_buffer);
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const char* otherText) const
|
||||
{
|
||||
return CASE_COMPARE(m_buffer, otherText);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const char* str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(std::strlen(str));
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str, m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str, m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const SmallStringBase& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = str.m_length;
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.m_buffer, m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const std::string_view& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const char* str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(std::strlen(str));
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str, m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str, m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const SmallStringBase& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = str.m_length;
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
const u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.m_buffer, m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const std::string_view& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
const u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
void SmallStringBase::clear()
|
||||
{
|
||||
// in debug, zero whole string, in release, zero only the first character
|
||||
#if _DEBUG
|
||||
std::memset(m_buffer, 0, m_buffer_size);
|
||||
#else
|
||||
m_buffer[0] = '\0';
|
||||
#endif
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::find(char c, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
DebugAssert(offset <= m_length);
|
||||
const char* at = std::strchr(m_buffer + offset, c);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::rfind(char c, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
DebugAssert(offset <= m_length);
|
||||
const char* at = std::strrchr(m_buffer + offset, c);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::find(const char* str, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
DebugAssert(offset <= m_length);
|
||||
const char* at = std::strstr(m_buffer + offset, str);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
void SmallStringBase::resize(u32 new_size, char fill, bool shrink_if_smaller)
|
||||
{
|
||||
// if going larger, or we don't own the buffer, realloc
|
||||
if (new_size >= m_buffer_size)
|
||||
{
|
||||
reserve(new_size);
|
||||
|
||||
if (m_length < new_size)
|
||||
{
|
||||
std::memset(m_buffer + m_length, fill, m_buffer_size - m_length - 1);
|
||||
}
|
||||
|
||||
m_length = new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update length and terminator
|
||||
#if _DEBUG
|
||||
std::memset(m_buffer + new_size, 0, m_buffer_size - new_size);
|
||||
#else
|
||||
m_buffer[new_size] = 0;
|
||||
#endif
|
||||
m_length = new_size;
|
||||
|
||||
// shrink if requested
|
||||
if (shrink_if_smaller)
|
||||
shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void SmallStringBase::update_size()
|
||||
{
|
||||
m_length = static_cast<u32>(std::strlen(m_buffer));
|
||||
}
|
||||
|
||||
std::string_view SmallStringBase::substr(s32 offset, s32 count) const
|
||||
{
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
|
||||
else
|
||||
real_offset = std::min((u32)offset, m_length);
|
||||
|
||||
// calc real count
|
||||
u32 real_count;
|
||||
if (count < 0)
|
||||
{
|
||||
real_count =
|
||||
std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
real_count = std::min(m_length - real_offset, static_cast<u32>(count));
|
||||
}
|
||||
|
||||
return (real_count > 0) ? std::string_view(m_buffer + real_offset, real_count) : std::string_view();
|
||||
}
|
||||
|
||||
void SmallStringBase::erase(s32 offset, s32 count)
|
||||
{
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
|
||||
else
|
||||
real_offset = std::min((u32)offset, m_length);
|
||||
|
||||
// calc real count
|
||||
u32 real_count;
|
||||
if (count < 0)
|
||||
{
|
||||
real_count =
|
||||
std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
real_count = std::min(m_length - real_offset, static_cast<u32>(count));
|
||||
}
|
||||
|
||||
// Fastpath: offset == 0, count < 0, wipe whole string.
|
||||
if (real_offset == 0 && real_count == m_length)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fastpath: offset >= 0, count < 0, wipe everything after offset + count
|
||||
if ((real_offset + real_count) == m_length)
|
||||
{
|
||||
m_length -= real_count;
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
|
||||
#else
|
||||
m_buffer[m_length] = 0;
|
||||
#endif
|
||||
}
|
||||
// Slowpath: offset >= 0, count < length
|
||||
else
|
||||
{
|
||||
const u32 after_erase_block = m_length - real_offset - real_count;
|
||||
DebugAssert(after_erase_block > 0);
|
||||
|
||||
std::memmove(m_buffer + offset, m_buffer + real_offset + real_count, after_erase_block);
|
||||
m_length = m_length - real_count;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
|
||||
#else
|
||||
m_buffer[m_length] = 0;
|
||||
#endif
|
||||
}
|
||||
}
|
343
src/common/small_string.h
Normal file
343
src/common/small_string.h
Normal file
@ -0,0 +1,343 @@
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
//
|
||||
// SmallString
|
||||
// Lightweight string class which can be allocated on the stack, instead of with heap allocations.
|
||||
//
|
||||
class SmallStringBase
|
||||
{
|
||||
public:
|
||||
using value_type = char;
|
||||
|
||||
SmallStringBase();
|
||||
SmallStringBase(const char* str);
|
||||
SmallStringBase(const char* str, u32 length);
|
||||
SmallStringBase(const SmallStringBase& copy);
|
||||
SmallStringBase(SmallStringBase&& move);
|
||||
SmallStringBase(const std::string& str);
|
||||
SmallStringBase(const std::string_view& sv);
|
||||
|
||||
// Destructor. Child classes may not have any destructors, as this is not virtual.
|
||||
~SmallStringBase();
|
||||
|
||||
// manual assignment
|
||||
void assign(const char* str);
|
||||
void assign(const char* str, u32 length);
|
||||
void assign(const std::string& copy);
|
||||
void assign(const std::string_view& copy);
|
||||
void assign(const SmallStringBase& copy);
|
||||
void assign(SmallStringBase&& move);
|
||||
|
||||
// Ensures that we have space bytes free in the buffer.
|
||||
void make_room_for(u32 space);
|
||||
|
||||
// clears the contents of the string
|
||||
void clear();
|
||||
|
||||
// append a single character to this string
|
||||
void append(char c);
|
||||
|
||||
// append a string to this string
|
||||
void append(const char* appendText);
|
||||
void append(const char* str, u32 length);
|
||||
void append(const std::string& str);
|
||||
void append(const std::string_view& str);
|
||||
void append(const SmallStringBase& str);
|
||||
|
||||
// append formatted string to this string
|
||||
void append_format(const char* format, ...) printflike(2, 3);
|
||||
void append_format_va(const char* format, va_list ap);
|
||||
|
||||
template<typename... T>
|
||||
void append_fmt(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// append a single character to this string
|
||||
void prepend(char c);
|
||||
|
||||
// append a string to this string
|
||||
void prepend(const char* str);
|
||||
void prepend(const char* str, u32 length);
|
||||
void prepend(const std::string& str);
|
||||
void prepend(const std::string_view& str);
|
||||
void prepend(const SmallStringBase& str);
|
||||
|
||||
// append formatted string to this string
|
||||
void prepend_format(const char* format, ...) printflike(2, 3);
|
||||
void prepend_format_va(const char* format, va_list ap);
|
||||
|
||||
template<typename... T>
|
||||
void prepend_fmt(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// insert a string at the specified offset
|
||||
void insert(s32 offset, const char* str);
|
||||
void insert(s32 offset, const char* str, u32 length);
|
||||
void insert(s32 offset, const std::string& str);
|
||||
void insert(s32 offset, const std::string_view& str);
|
||||
void insert(s32 offset, const SmallStringBase& str);
|
||||
|
||||
// set to formatted string
|
||||
void format(const char* format, ...) printflike(2, 3);
|
||||
void format_va(const char* format, va_list ap);
|
||||
|
||||
template<typename... T>
|
||||
void fmt(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// compare one string to another
|
||||
bool equals(const char* str) const;
|
||||
bool equals(const SmallStringBase& str) const;
|
||||
bool equals(const std::string_view& str) const;
|
||||
bool iequals(const char* str) const;
|
||||
bool iequals(const SmallStringBase& str) const;
|
||||
bool iequals(const std::string_view& str) const;
|
||||
|
||||
// numerical compares
|
||||
int compare(const char* str) const;
|
||||
int compare(const SmallStringBase& str) const;
|
||||
int compare(const std::string_view& str) const;
|
||||
int icompare(const char* str) const;
|
||||
int icompare(const SmallStringBase& str) const;
|
||||
int icompare(const std::string_view& str) const;
|
||||
|
||||
// starts with / ends with
|
||||
bool starts_with(const char* str, bool case_sensitive = true) const;
|
||||
bool starts_with(const std::string_view& str, bool case_sensitive = true) const;
|
||||
bool starts_with(const SmallStringBase& str, bool case_sensitive = true) const;
|
||||
bool ends_with(const char* str, bool case_sensitive = true) const;
|
||||
bool ends_with(const std::string_view& str, bool case_sensitive = true) const;
|
||||
bool ends_with(const SmallStringBase& str, bool case_sensitive = true) const;
|
||||
|
||||
// searches for a character inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 find(char c, u32 offset = 0) const;
|
||||
s32 rfind(char c, u32 offset = 0) const;
|
||||
|
||||
// searches for a string inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 find(const char* str, u32 offset = 0) const;
|
||||
|
||||
// removes characters from string
|
||||
void erase(s32 offset, s32 count = std::numeric_limits<s32>::max());
|
||||
|
||||
// alters the length of the string to be at least len bytes long
|
||||
void reserve(u32 new_reserve);
|
||||
|
||||
// Cuts characters off the string to reduce it to len bytes long.
|
||||
void resize(u32 new_size, char fill = ' ', bool shrink_if_smaller = false);
|
||||
|
||||
// updates the internal length counter when the string is externally modified
|
||||
void update_size();
|
||||
|
||||
// shrink the string to the minimum size possible
|
||||
void shrink_to_fit();
|
||||
|
||||
// gets the size of the string
|
||||
ALWAYS_INLINE u32 length() const { return m_length; }
|
||||
ALWAYS_INLINE bool empty() const { return (m_length == 0); }
|
||||
|
||||
// gets the maximum number of bytes we can write to the string, currently
|
||||
ALWAYS_INLINE u32 buffer_size() const { return m_buffer_size; }
|
||||
|
||||
// gets a constant pointer to the C string
|
||||
ALWAYS_INLINE const char* c_str() const { return m_buffer; }
|
||||
|
||||
// gets a writable char array, do not write more than reserve characters to it.
|
||||
ALWAYS_INLINE char* data() { return m_buffer; }
|
||||
|
||||
// returns the end of the string (pointer is past the last character)
|
||||
ALWAYS_INLINE const char* end_ptr() const { return m_buffer + m_length; }
|
||||
|
||||
// STL adapters
|
||||
ALWAYS_INLINE void push_back(value_type&& val) { append(val); }
|
||||
|
||||
// returns a string view for this string
|
||||
std::string_view view() const;
|
||||
|
||||
// returns a substring view for this string
|
||||
std::string_view substr(s32 offset, s32 count) const;
|
||||
|
||||
// accessor operators
|
||||
ALWAYS_INLINE operator const char*() const { return c_str(); }
|
||||
ALWAYS_INLINE operator char*() { return data(); }
|
||||
ALWAYS_INLINE operator std::string_view() const { return view(); }
|
||||
|
||||
// comparative operators
|
||||
ALWAYS_INLINE bool operator==(const char* str) const { return equals(str); }
|
||||
ALWAYS_INLINE bool operator==(const SmallStringBase& str) const { return equals(str); }
|
||||
ALWAYS_INLINE bool operator==(const std::string_view& str) const { return equals(str); }
|
||||
ALWAYS_INLINE bool operator!=(const char* str) const { return !equals(str); }
|
||||
ALWAYS_INLINE bool operator!=(const SmallStringBase& str) const { return !equals(str); }
|
||||
ALWAYS_INLINE bool operator!=(const std::string_view& str) const { return !equals(str); }
|
||||
ALWAYS_INLINE bool operator<(const char* str) const { return (compare(str) < 0); }
|
||||
ALWAYS_INLINE bool operator<(const SmallStringBase& str) const { return (compare(str) < 0); }
|
||||
ALWAYS_INLINE bool operator<(const std::string_view& str) const { return (compare(str) < 0); }
|
||||
ALWAYS_INLINE bool operator>(const char* str) const { return (compare(str) > 0); }
|
||||
ALWAYS_INLINE bool operator>(const SmallStringBase& str) const { return (compare(str) > 0); }
|
||||
ALWAYS_INLINE bool operator>(const std::string_view& str) const { return (compare(str) > 0); }
|
||||
|
||||
SmallStringBase& operator=(const SmallStringBase& copy);
|
||||
SmallStringBase& operator=(const char* str);
|
||||
SmallStringBase& operator=(const std::string& str);
|
||||
SmallStringBase& operator=(const std::string_view& str);
|
||||
SmallStringBase& operator=(SmallStringBase&& move);
|
||||
|
||||
protected:
|
||||
// Pointer to memory where the string is located
|
||||
char* m_buffer = nullptr;
|
||||
|
||||
// Length of the string located in pBuffer (in characters)
|
||||
u32 m_length = 0;
|
||||
|
||||
// Size of the buffer pointed to by pBuffer
|
||||
u32 m_buffer_size = 0;
|
||||
|
||||
// True if the string is dynamically allocated on the heap.
|
||||
bool m_on_heap = false;
|
||||
};
|
||||
|
||||
// stack-allocated string
|
||||
template<u32 L>
|
||||
class SmallStackString : public SmallStringBase
|
||||
{
|
||||
public:
|
||||
ALWAYS_INLINE SmallStackString() { init(); }
|
||||
|
||||
ALWAYS_INLINE SmallStackString(const char* str)
|
||||
{
|
||||
init();
|
||||
assign(str);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE SmallStackString(const char* str, u32 length)
|
||||
{
|
||||
init();
|
||||
assign(str, length);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE SmallStackString(const SmallStringBase& copy)
|
||||
{
|
||||
init();
|
||||
assign(copy);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE SmallStackString(SmallStringBase&& move)
|
||||
{
|
||||
init();
|
||||
assign(move);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE SmallStackString(const std::string_view& sv)
|
||||
{
|
||||
init();
|
||||
assign(sv);
|
||||
}
|
||||
|
||||
// Override the fromstring method
|
||||
static SmallStackString from_format(const char* format, ...) printflike(1, 2);
|
||||
|
||||
template<typename... T>
|
||||
static SmallStackString from_fmt(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
private:
|
||||
char m_stack_buffer[L + 1];
|
||||
|
||||
ALWAYS_INLINE void init()
|
||||
{
|
||||
m_buffer = m_stack_buffer;
|
||||
m_buffer_size = L + 1;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_stack_buffer, 0, sizeof(m_stack_buffer));
|
||||
#else
|
||||
m_stack_buffer[0] = '\0';
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4459) // warning C4459: declaration of 'uint' hides global declaration
|
||||
#endif
|
||||
|
||||
template<u32 L>
|
||||
ALWAYS_INLINE SmallStackString<L> SmallStackString<L>::from_format(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
SmallStackString ret;
|
||||
ret.format_va(format, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<u32 L>
|
||||
template<typename... T>
|
||||
ALWAYS_INLINE SmallStackString<L> SmallStackString<L>::from_fmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
SmallStackString<L> ret;
|
||||
fmt::vformat_to(std::back_inserter(ret), fmt, fmt::make_format_args(args...));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// stack string types
|
||||
using TinyString = SmallStackString<64>;
|
||||
using SmallString = SmallStackString<256>;
|
||||
using LargeString = SmallStackString<512>;
|
||||
|
||||
template<typename... T>
|
||||
ALWAYS_INLINE void SmallStringBase::append_fmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
ALWAYS_INLINE void SmallStringBase::prepend_fmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
TinyString str;
|
||||
fmt::vformat_to(std::back_inserter(str), fmt, fmt::make_format_args(args...));
|
||||
prepend(str);
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
ALWAYS_INLINE void SmallStringBase::fmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
clear();
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<SmallStringBase>
|
||||
{
|
||||
template<typename ParseContext>
|
||||
constexpr auto parse(ParseContext& ctx)
|
||||
{
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(const SmallStringBase& str, FormatContext& ctx)
|
||||
{
|
||||
return fmt::format_to(ctx.out(), "{}", str.view());
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,472 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "fmt/core.h"
|
||||
#include "types.h"
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
//
|
||||
// String
|
||||
// Implements a UTF-8 string container with copy-on-write behavior.
|
||||
// The data class is not currently threadsafe (creating a mutex on each container would be overkill),
|
||||
// so locking is still required when multiple threads are involved.
|
||||
//
|
||||
class String
|
||||
{
|
||||
public:
|
||||
// Internal StringData class.
|
||||
struct StringData
|
||||
{
|
||||
// Pointer to memory where the string is located
|
||||
char* pBuffer;
|
||||
|
||||
// Length of the string located in pBuffer (in characters)
|
||||
u32 StringLength;
|
||||
|
||||
// Size of the buffer pointed to by pBuffer
|
||||
u32 BufferSize;
|
||||
|
||||
// Reference count of this data object. If set to -1,
|
||||
// it is considered noncopyable and any copies of the string
|
||||
// will always create their own copy.
|
||||
s32 ReferenceCount;
|
||||
|
||||
// Whether the memory pointed to by pBuffer is writable.
|
||||
bool ReadOnly;
|
||||
};
|
||||
|
||||
public:
|
||||
using value_type = char;
|
||||
|
||||
// Creates an empty string.
|
||||
String();
|
||||
|
||||
// Creates a string containing the specified text.
|
||||
// Note that this will incur a heap allocation, even if Text is on the stack.
|
||||
// For strings that do not allocate any space on the heap, see StaticString.
|
||||
String(const char* Text);
|
||||
|
||||
// Creates a string contained the specified text (with length).
|
||||
String(const char* Text, u32 Count);
|
||||
|
||||
// Creates a string using the same buffer as another string (copy-on-write).
|
||||
String(const String& copyString);
|
||||
|
||||
// Move constructor, take reference from other string.
|
||||
String(String&& moveString);
|
||||
|
||||
// Construct a string from a data object, does not increment the reference count on the string data, use carefully.
|
||||
explicit String(StringData* pStringData) : m_pStringData(pStringData) {}
|
||||
|
||||
// Creates string from string_view.
|
||||
String(const std::string_view& sv);
|
||||
|
||||
// Destructor. Child classes may not have any destructors, as this is not virtual.
|
||||
~String();
|
||||
|
||||
// manual assignment
|
||||
void Assign(const String& copyString);
|
||||
void Assign(const char* copyText);
|
||||
void Assign(const std::string& copyString);
|
||||
void Assign(const std::string_view& copyString);
|
||||
void Assign(String&& moveString);
|
||||
|
||||
// assignment but ensures that we have our own copy.
|
||||
void AssignCopy(const String& copyString);
|
||||
|
||||
// Ensures that the string has its own unique copy of the data.
|
||||
void EnsureOwnWritableCopy();
|
||||
|
||||
// Ensures that we have our own copy of the buffer, and spaceRequired bytes free in the buffer.
|
||||
void EnsureRemainingSpace(u32 spaceRequired);
|
||||
|
||||
// clears the contents of the string
|
||||
void Clear();
|
||||
|
||||
// clear the contents of the string, and free any memory currently being used
|
||||
void Obliterate();
|
||||
|
||||
// swaps strings
|
||||
void Swap(String& swapString);
|
||||
|
||||
// append a single character to this string
|
||||
void AppendCharacter(char c);
|
||||
|
||||
// append a string to this string
|
||||
void AppendString(const String& appendStr);
|
||||
void AppendString(const char* appendText);
|
||||
void AppendString(const char* appendString, u32 Count);
|
||||
void AppendString(const std::string& appendString);
|
||||
void AppendString(const std::string_view& appendString);
|
||||
|
||||
// append a substring of the specified string to this string
|
||||
void AppendSubString(const String& appendStr, s32 Offset = 0, s32 Count = std::numeric_limits<s32>::max());
|
||||
void AppendSubString(const char* appendText, s32 Offset = 0, s32 Count = std::numeric_limits<s32>::max());
|
||||
|
||||
// append formatted string to this string
|
||||
void AppendFormattedString(const char* FormatString, ...) printflike(2, 3);
|
||||
void AppendFormattedStringVA(const char* FormatString, va_list ArgPtr);
|
||||
|
||||
template<typename... T>
|
||||
void AppendFmtString(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// append a single character to this string
|
||||
void PrependCharacter(char c);
|
||||
|
||||
// append a string to this string
|
||||
void PrependString(const String& appendStr);
|
||||
void PrependString(const char* appendText);
|
||||
void PrependString(const char* appendString, u32 Count);
|
||||
void PrependString(const std::string& appendStr);
|
||||
void PrependString(const std::string_view& appendStr);
|
||||
|
||||
// append a substring of the specified string to this string
|
||||
void PrependSubString(const String& appendStr, s32 Offset = 0, s32 Count = std::numeric_limits<s32>::max());
|
||||
void PrependSubString(const char* appendText, s32 Offset = 0, s32 Count = std::numeric_limits<s32>::max());
|
||||
|
||||
// append formatted string to this string
|
||||
void PrependFormattedString(const char* FormatString, ...) printflike(2, 3);
|
||||
void PrependFormattedStringVA(const char* FormatString, va_list ArgPtr);
|
||||
|
||||
template<typename... T>
|
||||
void PrependFmtString(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// insert a string at the specified offset
|
||||
void InsertString(s32 offset, const String& appendStr);
|
||||
void InsertString(s32 offset, const char* appendStr);
|
||||
void InsertString(s32 offset, const char* appendStr, u32 appendStrLength);
|
||||
void InsertString(s32 offset, const std::string& appendStr);
|
||||
void InsertString(s32 offset, const std::string_view& appendStr);
|
||||
|
||||
// set to formatted string
|
||||
void Format(const char* FormatString, ...) printflike(2, 3);
|
||||
void FormatVA(const char* FormatString, va_list ArgPtr);
|
||||
|
||||
template<typename... T>
|
||||
void Fmt(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// compare one string to another
|
||||
bool Compare(const String& otherString) const;
|
||||
bool Compare(const char* otherText) const;
|
||||
bool SubCompare(const String& otherString, u32 Length) const;
|
||||
bool SubCompare(const char* otherText, u32 Length) const;
|
||||
bool CompareInsensitive(const String& otherString) const;
|
||||
bool CompareInsensitive(const char* otherText) const;
|
||||
bool SubCompareInsensitive(const String& otherString, u32 Length) const;
|
||||
bool SubCompareInsensitive(const char* otherText, u32 Length) const;
|
||||
|
||||
// numerical compares
|
||||
int NumericCompare(const String& otherString) const;
|
||||
int NumericCompare(const char* otherText) const;
|
||||
int NumericCompareInsensitive(const String& otherString) const;
|
||||
int NumericCompareInsensitive(const char* otherText) const;
|
||||
|
||||
// starts with / ends with
|
||||
bool StartsWith(const char* compareString, bool caseSensitive = true) const;
|
||||
bool StartsWith(const String& compareString, bool caseSensitive = true) const;
|
||||
bool EndsWith(const char* compareString, bool caseSensitive = true) const;
|
||||
bool EndsWith(const String& compareString, bool caseSensitive = true) const;
|
||||
|
||||
// searches for a character inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 Find(char c, u32 Offset = 0) const;
|
||||
s32 RFind(char c, u32 Offset = 0) const;
|
||||
|
||||
// searches for a string inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 Find(const char* str, u32 Offset = 0) const;
|
||||
|
||||
// alters the length of the string to be at least len bytes long
|
||||
void Reserve(u32 newReserve, bool Force = false);
|
||||
|
||||
// Cuts characters off the string to reduce it to len bytes long.
|
||||
void Resize(u32 newSize, char fillerCharacter = ' ', bool skrinkIfSmaller = false);
|
||||
|
||||
// updates the internal length counter when the string is externally modified
|
||||
void UpdateSize();
|
||||
|
||||
// shrink the string to the minimum size possible
|
||||
void Shrink(bool Force = false);
|
||||
|
||||
// gets the size of the string
|
||||
u32 GetLength() const { return m_pStringData->StringLength; }
|
||||
bool IsEmpty() const { return (m_pStringData->StringLength == 0); }
|
||||
|
||||
// gets the maximum number of bytes we can write to the string, currently
|
||||
u32 GetBufferSize() const { return m_pStringData->BufferSize; }
|
||||
u32 GetWritableBufferSize()
|
||||
{
|
||||
EnsureOwnWritableCopy();
|
||||
return m_pStringData->BufferSize;
|
||||
}
|
||||
|
||||
// creates a new string using part of this string
|
||||
String SubString(s32 Offset, s32 Count = std::numeric_limits<s32>::max()) const;
|
||||
|
||||
// erase count characters at offset from this string. if count is less than zero, everything past offset is erased
|
||||
void Erase(s32 Offset, s32 Count = std::numeric_limits<s32>::max());
|
||||
|
||||
// replaces all instances of character c with character r in this string
|
||||
// returns the number of changes
|
||||
u32 Replace(char searchCharacter, char replaceCharacter);
|
||||
|
||||
// replaces all instances of string s with string r in this string
|
||||
// returns the number of changes
|
||||
u32 Replace(const char* searchString, const char* replaceString);
|
||||
|
||||
// convert string to lowercase
|
||||
void ToLower();
|
||||
|
||||
// convert string to upper
|
||||
void ToUpper();
|
||||
|
||||
// strip characters from start and end of the string
|
||||
void LStrip(const char* szStripCharacters = " \t\r\n");
|
||||
void RStrip(const char* szStripCharacters = " \t\r\n");
|
||||
void Strip(const char* szStripCharacters = " \t\r\n");
|
||||
|
||||
// gets a constant pointer to the string
|
||||
const char* GetCharArray() const { return m_pStringData->pBuffer; }
|
||||
|
||||
// gets a writable char array, do not write more than reserve characters to it.
|
||||
char* GetWriteableCharArray()
|
||||
{
|
||||
EnsureOwnWritableCopy();
|
||||
return m_pStringData->pBuffer;
|
||||
}
|
||||
|
||||
// returns a string view for this string
|
||||
std::string_view GetStringView() const
|
||||
{
|
||||
return IsEmpty() ? std::string_view() : std::string_view(GetCharArray(), GetLength());
|
||||
}
|
||||
std::string ToStdString() const
|
||||
{
|
||||
return std::string(GetStringView());
|
||||
}
|
||||
|
||||
// creates a new string from the specified format
|
||||
static String FromFormat(const char* FormatString, ...) printflike(1, 2);
|
||||
|
||||
// accessor operators
|
||||
// const char &operator[](u32 i) const { DebugAssert(i < m_pStringData->StringLength); return
|
||||
// m_pStringData->pBuffer[i]; } char &operator[](u32 i) { DebugAssert(i < m_pStringData->StringLength); return
|
||||
// m_pStringData->pBuffer[i]; }
|
||||
operator const char*() const { return GetCharArray(); }
|
||||
operator char*() { return GetWriteableCharArray(); }
|
||||
operator std::string_view() const { return GetStringView(); }
|
||||
|
||||
// Will use the string data provided.
|
||||
String& operator=(const String& copyString)
|
||||
{
|
||||
Assign(copyString);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Allocates own buffer and copies text.
|
||||
String& operator=(const char* Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
String& operator=(const std::string& Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
String& operator=(const std::string_view& Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move operator.
|
||||
String& operator=(String&& moveString)
|
||||
{
|
||||
Assign(moveString);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// comparative operators
|
||||
bool operator==(const String& compString) const { return Compare(compString); }
|
||||
bool operator==(const char* compString) const { return Compare(compString); }
|
||||
bool operator!=(const String& compString) const { return !Compare(compString); }
|
||||
bool operator!=(const char* compString) const { return !Compare(compString); }
|
||||
bool operator<(const String& compString) const { return (NumericCompare(compString) < 0); }
|
||||
bool operator<(const char* compString) const { return (NumericCompare(compString) < 0); }
|
||||
bool operator>(const String& compString) const { return (NumericCompare(compString) > 0); }
|
||||
bool operator>(const char* compString) const { return (NumericCompare(compString) > 0); }
|
||||
|
||||
// STL adapters
|
||||
ALWAYS_INLINE void push_back(value_type&& val) { AppendCharacter(val); }
|
||||
|
||||
protected:
|
||||
// Internal append function.
|
||||
void InternalPrepend(const char* pString, u32 Length);
|
||||
void InternalAppend(const char* pString, u32 Length);
|
||||
|
||||
// Pointer to string data.
|
||||
StringData* m_pStringData;
|
||||
|
||||
// Empty string data.
|
||||
static const StringData s_EmptyStringData;
|
||||
};
|
||||
|
||||
// static string, stored in .rodata
|
||||
#define StaticString(Text) \
|
||||
[]() noexcept -> String { \
|
||||
static constexpr u32 buffer_size = sizeof(Text); \
|
||||
static constexpr u32 length = buffer_size - 1; \
|
||||
static constexpr String::StringData data{const_cast<char*>(Text), length, buffer_size, static_cast<s32>(-1), \
|
||||
true}; \
|
||||
return String(const_cast<String::StringData*>(&data)); \
|
||||
}()
|
||||
|
||||
// stack-allocated string
|
||||
template<u32 L>
|
||||
class StackString : public String
|
||||
{
|
||||
public:
|
||||
StackString() : String(&m_sStringData) { InitStackStringData(); }
|
||||
|
||||
StackString(const char* Text) : String(&m_sStringData)
|
||||
{
|
||||
InitStackStringData();
|
||||
Assign(Text);
|
||||
}
|
||||
|
||||
StackString(const char* Text, u32 Count) : String(&m_sStringData)
|
||||
{
|
||||
InitStackStringData();
|
||||
AppendString(Text, Count);
|
||||
}
|
||||
|
||||
StackString(const String& copyString) : String(&m_sStringData)
|
||||
{
|
||||
// force a copy by passing it a string pointer, instead of a string object
|
||||
InitStackStringData();
|
||||
Assign(copyString.GetCharArray());
|
||||
}
|
||||
|
||||
StackString(const StackString& copyString) : String(&m_sStringData)
|
||||
{
|
||||
// force a copy by passing it a string pointer, instead of a string object
|
||||
InitStackStringData();
|
||||
Assign(copyString.GetCharArray());
|
||||
}
|
||||
|
||||
StackString(const std::string_view& sv) : String(&m_sStringData)
|
||||
{
|
||||
InitStackStringData();
|
||||
AppendString(sv.data(), static_cast<u32>(sv.size()));
|
||||
}
|
||||
|
||||
// Override the fromstring method
|
||||
static StackString FromFormat(const char* FormatString, ...) printflike(1, 2)
|
||||
{
|
||||
va_list argPtr;
|
||||
va_start(argPtr, FormatString);
|
||||
|
||||
StackString returnValue;
|
||||
returnValue.FormatVA(FormatString, argPtr);
|
||||
|
||||
va_end(argPtr);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
static StackString FromFmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
StackString ret;
|
||||
fmt::vformat_to(std::back_inserter(ret), fmt, fmt::make_format_args(args...));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Will use the string data provided.
|
||||
StackString& operator=(const StackString& copyString)
|
||||
{
|
||||
Assign(copyString.GetCharArray());
|
||||
return *this;
|
||||
}
|
||||
StackString& operator=(const String& copyString)
|
||||
{
|
||||
Assign(copyString.GetCharArray());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Allocates own buffer and copies text.
|
||||
StackString& operator=(const char* Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
StackString& operator=(const std::string& Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
StackString& operator=(const std::string_view& Text)
|
||||
{
|
||||
Assign(Text);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
StringData m_sStringData;
|
||||
char m_strStackBuffer[L + 1];
|
||||
|
||||
inline void InitStackStringData()
|
||||
{
|
||||
m_sStringData.pBuffer = m_strStackBuffer;
|
||||
m_sStringData.StringLength = 0;
|
||||
m_sStringData.BufferSize = countof(m_strStackBuffer);
|
||||
m_sStringData.ReadOnly = false;
|
||||
m_sStringData.ReferenceCount = -1;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_strStackBuffer, 0, sizeof(m_strStackBuffer));
|
||||
#else
|
||||
m_strStackBuffer[0] = '\0';
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// stack string types
|
||||
typedef StackString<64> TinyString;
|
||||
typedef StackString<256> SmallString;
|
||||
typedef StackString<512> LargeString;
|
||||
typedef StackString<512> PathString;
|
||||
|
||||
// empty string global
|
||||
extern const String EmptyString;
|
||||
|
||||
template<typename... T>
|
||||
void String::AppendFmtString(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
void String::PrependFmtString(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
TinyString str;
|
||||
fmt::vformat_to(std::back_inserter(str), fmt, fmt::make_format_args(args...));
|
||||
PrependString(str);
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
void String::Fmt(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
Clear();
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
Reference in New Issue
Block a user