diff options
| author | Stefan Boberg <[email protected]> | 2023-05-02 10:01:47 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-05-02 10:01:47 +0200 |
| commit | 075d17f8ada47e990fe94606c3d21df409223465 (patch) | |
| tree | e50549b766a2f3c354798a54ff73404217b4c9af /src/zencore/string.cpp | |
| parent | fix: bundle shouldn't append content zip to zen (diff) | |
| download | zen-075d17f8ada47e990fe94606c3d21df409223465.tar.xz zen-075d17f8ada47e990fe94606c3d21df409223465.zip | |
moved source directories into `/src` (#264)
* moved source directories into `/src`
* updated bundle.lua for new `src` path
* moved some docs, icon
* removed old test trees
Diffstat (limited to 'src/zencore/string.cpp')
| -rw-r--r-- | src/zencore/string.cpp | 1004 |
1 files changed, 1004 insertions, 0 deletions
diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp new file mode 100644 index 000000000..ad6ee78fc --- /dev/null +++ b/src/zencore/string.cpp @@ -0,0 +1,1004 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zencore/memory.h> +#include <zencore/string.h> +#include <zencore/testing.h> + +#include <inttypes.h> +#include <math.h> +#include <stdio.h> +#include <exception> +#include <ostream> +#include <stdexcept> + +#include <utf8.h> + +template<typename u16bit_iterator> +void +utf16to8_impl(u16bit_iterator StartIt, u16bit_iterator EndIt, ::zen::StringBuilderBase& OutString) +{ + while (StartIt != EndIt) + { + uint32_t cp = utf8::internal::mask16(*StartIt++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) + { + uint32_t trail_surrogate = utf8::internal::mask16(*StartIt++); + cp = (cp << 10) + trail_surrogate + utf8::internal::SURROGATE_OFFSET; + } + OutString.AppendCodepoint(cp); + } +} + +template<typename u32bit_iterator> +void +utf32to8_impl(u32bit_iterator StartIt, u32bit_iterator EndIt, ::zen::StringBuilderBase& OutString) +{ + for (; StartIt != EndIt; ++StartIt) + { + wchar_t cp = *StartIt; + OutString.AppendCodepoint(cp); + } +} + +////////////////////////////////////////////////////////////////////////// + +namespace zen { + +bool +ToString(std::span<char> Buffer, uint64_t Num) +{ + snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num); + + return true; +} +bool +ToString(std::span<char> Buffer, int64_t Num) +{ + snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num); + + return true; +} + +////////////////////////////////////////////////////////////////////////// + +const char* +FilepathFindExtension(const std::string_view& Path, const char* ExtensionToMatch) +{ + const size_t PathLen = Path.size(); + + if (ExtensionToMatch) + { + size_t ExtLen = strlen(ExtensionToMatch); + + if (ExtLen > PathLen) + return nullptr; + + const char* PathExtension = Path.data() + PathLen - ExtLen; + + if (StringEquals(PathExtension, ExtensionToMatch)) + return PathExtension; + + return nullptr; + } + + if (PathLen == 0) + return nullptr; + + // Look for extension introducer ('.') + + for (int64_t i = PathLen - 1; i >= 0; --i) + { + if (Path[i] == '.') + return Path.data() + i; + } + + return nullptr; +} + +////////////////////////////////////////////////////////////////////////// + +void +Utf8ToWide(const char8_t* Str8, WideStringBuilderBase& OutString) +{ + Utf8ToWide(std::u8string_view(Str8), OutString); +} + +void +Utf8ToWide(const std::string_view& Str8, WideStringBuilderBase& OutString) +{ + Utf8ToWide(std::u8string_view{reinterpret_cast<const char8_t*>(Str8.data()), Str8.size()}, OutString); +} + +std::wstring +Utf8ToWide(const std::string_view& Wstr) +{ + ExtendableWideStringBuilder<128> String; + Utf8ToWide(Wstr, String); + + return String.c_str(); +} + +void +Utf8ToWide(const std::u8string_view& Str8, WideStringBuilderBase& OutString) +{ + const char* str = (const char*)Str8.data(); + const size_t strLen = Str8.size(); + + const char* endStr = str + strLen; + size_t ByteCount = 0; + size_t CurrentOutChar = 0; + + for (; str != endStr; ++str) + { + unsigned char Data = static_cast<unsigned char>(*str); + + if (!(Data & 0x80)) + { + // ASCII + OutString.Append(wchar_t(Data)); + continue; + } + else if (!ByteCount) + { + // Start of multi-byte sequence. Figure out how + // many bytes we're going to consume + + size_t Count = 0; + + for (size_t Temp = Data; Temp & 0x80; Temp <<= 1) + ++Count; + + ByteCount = Count - 1; + CurrentOutChar = Data & (0xff >> (Count + 1)); + } + else + { + --ByteCount; + + if ((Data & 0xc0) != 0x80) + { + break; + } + + CurrentOutChar = (CurrentOutChar << 6) | (Data & 0x3f); + + if (!ByteCount) + { + OutString.Append(wchar_t(CurrentOutChar)); + CurrentOutChar = 0; + } + } + } +} + +void +WideToUtf8(const wchar_t* Wstr, StringBuilderBase& OutString) +{ + WideToUtf8(std::wstring_view{Wstr}, OutString); +} + +void +WideToUtf8(const std::wstring_view& Wstr, StringBuilderBase& OutString) +{ +#if ZEN_SIZEOF_WCHAR_T == 2 + utf16to8_impl(begin(Wstr), end(Wstr), OutString); +#else + utf32to8_impl(begin(Wstr), end(Wstr), OutString); +#endif +} + +std::string +WideToUtf8(const wchar_t* Wstr) +{ + ExtendableStringBuilder<128> String; + WideToUtf8(std::wstring_view{Wstr}, String); + + return String.c_str(); +} + +std::string +WideToUtf8(const std::wstring_view Wstr) +{ + ExtendableStringBuilder<128> String; + WideToUtf8(std::wstring_view{Wstr.data(), Wstr.size()}, String); + + return String.c_str(); +} + +////////////////////////////////////////////////////////////////////////// + +enum NicenumFormat +{ + kNicenum1024 = 0, // Print kilo, mega, tera, peta, exa.. + kNicenumBytes = 1, // Print single bytes ("13B"), kilo, mega, tera... + kNicenumTime = 2, // Print nanosecs, microsecs, millisecs, seconds... + kNicenumRaw = 3, // Print the raw number without any formatting + kNicenumRawTime = 4 // Same as RAW, but print dashes ('-') for zero. +}; + +namespace { + static const char* UnitStrings[3][7] = { + /* kNicenum1024 */ {"", "K", "M", "G", "T", "P", "E"}, + /* kNicenumBytes */ {"B", "K", "M", "G", "T", "P", "E"}, + /* kNicenumTime */ {"ns", "us", "ms", "s", "?", "?", "?"}}; + + static const int UnitsLen[] = { + /* kNicenum1024 */ 6, + /* kNicenumBytes */ 6, + /* kNicenumTime */ 3}; + + static const uint64_t KiloUnit[] = { + /* kNicenum1024 */ 1024, + /* kNicenumBytes */ 1024, + /* kNicenumTime */ 1000}; +} // namespace + +/* + * Convert a number to an appropriately human-readable output. + */ +int +NiceNumGeneral(uint64_t Num, std::span<char> Buffer, NicenumFormat Format) +{ + switch (Format) + { + case kNicenumRaw: + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, (uint64_t)Num); + + case kNicenumRawTime: + if (Num > 0) + { + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, (uint64_t)Num); + } + else + { + return snprintf(Buffer.data(), Buffer.size(), "%s", "-"); + } + break; + + case kNicenum1024: + case kNicenumBytes: + case kNicenumTime: + default: + break; + } + + // Bring into range and select unit + + int Index = 0; + uint64_t n = Num; + + { + const uint64_t Unit = KiloUnit[Format]; + const int maxIndex = UnitsLen[Format]; + + while (n >= Unit && Index < maxIndex) + { + n /= Unit; + Index++; + } + } + + const char* u = UnitStrings[Format][Index]; + + if ((Index == 0) || ((Num % (uint64_t)powl((int)KiloUnit[Format], Index)) == 0)) + { + /* + * If this is an even multiple of the base, always display + * without any decimal precision. + */ + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64 "%s", (uint64_t)n, u); + } + else + { + /* + * We want to choose a precision that reflects the best choice + * for fitting in 5 characters. This can get rather tricky when + * we have numbers that are very close to an order of magnitude. + * For example, when displaying 10239 (which is really 9.999K), + * we want only a single place of precision for 10.0K. We could + * develop some complex heuristics for this, but it's much + * easier just to try each combination in turn. + */ + + int StrLen = 0; + + for (int i = 2; i >= 0; i--) + { + double Value = (double)Num / (uint64_t)powl((int)KiloUnit[Format], Index); + + /* + * Don't print floating point values for time. Note, + * we use floor() instead of round() here, since + * round can result in undesirable results. For + * example, if "num" is in the range of + * 999500-999999, it will print out "1000us". This + * doesn't happen if we use floor(). + */ + if (Format == kNicenumTime) + { + StrLen = snprintf(Buffer.data(), Buffer.size(), "%d%s", (unsigned int)floor(Value), u); + + if (StrLen <= 5) + break; + } + else + { + StrLen = snprintf(Buffer.data(), Buffer.size(), "%.*f%s", i, Value, u); + + if (StrLen <= 5) + break; + } + } + + return StrLen; + } +} + +size_t +NiceNumToBuffer(uint64_t Num, std::span<char> Buffer) +{ + return NiceNumGeneral(Num, Buffer, kNicenum1024); +} + +size_t +NiceBytesToBuffer(uint64_t Num, std::span<char> Buffer) +{ + return NiceNumGeneral(Num, Buffer, kNicenumBytes); +} + +size_t +NiceByteRateToBuffer(uint64_t Num, uint64_t ElapsedMs, std::span<char> Buffer) +{ + size_t n = 0; + + if (ElapsedMs) + { + n = NiceNumGeneral(Num * 1000 / ElapsedMs, Buffer, kNicenumBytes); + } + else + { + Buffer[n++] = '0'; + Buffer[n++] = 'B'; + } + + Buffer[n++] = '/'; + Buffer[n++] = 's'; + Buffer[n++] = '\0'; + + return n; +} + +size_t +NiceLatencyNsToBuffer(uint64_t Nanos, std::span<char> Buffer) +{ + return NiceNumGeneral(Nanos, Buffer, kNicenumTime); +} + +size_t +NiceTimeSpanMsToBuffer(uint64_t Millis, std::span<char> Buffer) +{ + if (Millis < 1000) + { + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64 "ms", Millis); + } + else if (Millis < 10000) + { + return snprintf(Buffer.data(), Buffer.size(), "%.2fs", Millis / 1000.0); + } + else if (Millis < 60000) + { + return snprintf(Buffer.data(), Buffer.size(), "%.1fs", Millis / 1000.0); + } + else if (Millis < 60 * 60000) + { + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64 "m%02" PRIu64 "s", Millis / 60000, (Millis / 1000) % 60); + } + else + { + return snprintf(Buffer.data(), Buffer.size(), "%" PRIu64 "h%02" PRIu64 "m", Millis / 3600000, (Millis / 60000) % 60); + } +} + +////////////////////////////////////////////////////////////////////////// + +template<typename C> +StringBuilderImpl<C>::~StringBuilderImpl() +{ + if (m_IsDynamic) + { + FreeBuffer(m_Base, m_End - m_Base); + } +} + +template<typename C> +void +StringBuilderImpl<C>::Extend(size_t extraCapacity) +{ + if (!m_IsExtendable) + { + Fail("exceeded capacity"); + } + + const size_t oldCapacity = m_End - m_Base; + const size_t newCapacity = NextPow2(oldCapacity + extraCapacity); + + C* newBase = (C*)AllocBuffer(newCapacity); + + size_t pos = m_CurPos - m_Base; + memcpy(newBase, m_Base, pos * sizeof(C)); + + if (m_IsDynamic) + { + FreeBuffer(m_Base, oldCapacity); + } + + m_Base = newBase; + m_CurPos = newBase + pos; + m_End = newBase + newCapacity; + m_IsDynamic = true; +} + +template<typename C> +void* +StringBuilderImpl<C>::AllocBuffer(size_t byteCount) +{ + return Memory::Alloc(byteCount * sizeof(C)); +} + +template<typename C> +void +StringBuilderImpl<C>::FreeBuffer(void* buffer, size_t byteCount) +{ + ZEN_UNUSED(byteCount); + + Memory::Free(buffer); +} + +template<typename C> +[[noreturn]] void +StringBuilderImpl<C>::Fail(const char* reason) +{ + throw std::runtime_error(reason); +} + +// Instantiate templates once + +template class StringBuilderImpl<char>; +template class StringBuilderImpl<wchar_t>; + +////////////////////////////////////////////////////////////////////////// +// +// Unit tests +// + +#if ZEN_WITH_TESTS + +TEST_CASE("niceNum") +{ + char Buffer[16]; + + SUBCASE("raw") + { + NiceNumGeneral(1, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "1")); + + NiceNumGeneral(10, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "10")); + + NiceNumGeneral(100, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "100")); + + NiceNumGeneral(1000, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "1000")); + + NiceNumGeneral(10000, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "10000")); + + NiceNumGeneral(100000, Buffer, kNicenumRaw); + CHECK(StringEquals(Buffer, "100000")); + } + + SUBCASE("1024") + { + NiceNumGeneral(1, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1")); + + NiceNumGeneral(10, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "10")); + + NiceNumGeneral(100, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "100")); + + NiceNumGeneral(1000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1000")); + + NiceNumGeneral(10000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.77K")); + + NiceNumGeneral(100000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "97.7K")); + + NiceNumGeneral(1000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "977K")); + + NiceNumGeneral(10000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.54M")); + + NiceNumGeneral(100000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "95.4M")); + + NiceNumGeneral(1000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "954M")); + + NiceNumGeneral(10000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.31G")); + + NiceNumGeneral(100000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "93.1G")); + + NiceNumGeneral(1000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "931G")); + + NiceNumGeneral(10000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.09T")); + + NiceNumGeneral(100000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "90.9T")); + + NiceNumGeneral(1000000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "909T")); + + NiceNumGeneral(10000000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "8.88P")); + + NiceNumGeneral(100000000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "88.8P")); + + NiceNumGeneral(1000000000000000000, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "888P")); + + NiceNumGeneral(10000000000000000000ull, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "8.67E")); + + // pow2 + + NiceNumGeneral(0, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "0")); + + NiceNumGeneral(1, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1")); + + NiceNumGeneral(1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1K")); + + NiceNumGeneral(1024 * 1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1M")); + + NiceNumGeneral(1024 * 1024 * 1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1G")); + + NiceNumGeneral(1024llu * 1024 * 1024 * 1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1T")); + + NiceNumGeneral(1024llu * 1024 * 1024 * 1024 * 1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1P")); + + NiceNumGeneral(1024llu * 1024 * 1024 * 1024 * 1024 * 1024, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1E")); + + // pow2-1 + + NiceNumGeneral(1023, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "1023")); + + NiceNumGeneral(2047, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "2.00K")); + + NiceNumGeneral(9 * 1024 - 1, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.00K")); + + NiceNumGeneral(10 * 1024 - 1, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "10.0K")); + + NiceNumGeneral(10 * 1024 - 5, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "10.0K")); + + NiceNumGeneral(10 * 1024 - 6, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.99K")); + + NiceNumGeneral(10 * 1024 - 10, Buffer, kNicenum1024); + CHECK(StringEquals(Buffer, "9.99K")); + } + + SUBCASE("time") + { + NiceNumGeneral(1, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "1ns")); + + NiceNumGeneral(100, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "100ns")); + + NiceNumGeneral(1000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "1us")); + + NiceNumGeneral(10000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "10us")); + + NiceNumGeneral(100000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "100us")); + + NiceNumGeneral(1000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "1ms")); + + NiceNumGeneral(10000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "10ms")); + + NiceNumGeneral(100000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "100ms")); + + NiceNumGeneral(1000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "1s")); + + NiceNumGeneral(10000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "10s")); + + NiceNumGeneral(100000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "100s")); + + NiceNumGeneral(1000000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "1000s")); + + NiceNumGeneral(10000000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "10000s")); + + NiceNumGeneral(100000000000000, Buffer, kNicenumTime); + CHECK(StringEquals(Buffer, "100000s")); + } + + SUBCASE("bytes") + { + NiceNumGeneral(1, Buffer, kNicenumBytes); + CHECK(StringEquals(Buffer, "1B")); + + NiceNumGeneral(10, Buffer, kNicenumBytes); + CHECK(StringEquals(Buffer, "10B")); + + NiceNumGeneral(100, Buffer, kNicenumBytes); + CHECK(StringEquals(Buffer, "100B")); + + NiceNumGeneral(1000, Buffer, kNicenumBytes); + CHECK(StringEquals(Buffer, "1000B")); + + NiceNumGeneral(10000, Buffer, kNicenumBytes); + CHECK(StringEquals(Buffer, "9.77K")); + } + + SUBCASE("byteRate") + { + NiceByteRateToBuffer(1, 1, Buffer); + CHECK(StringEquals(Buffer, "1000B/s")); + + NiceByteRateToBuffer(1000, 1000, Buffer); + CHECK(StringEquals(Buffer, "1000B/s")); + + NiceByteRateToBuffer(1024, 1, Buffer); + CHECK(StringEquals(Buffer, "1000K/s")); + + NiceByteRateToBuffer(1024, 1000, Buffer); + CHECK(StringEquals(Buffer, "1K/s")); + } + + SUBCASE("timespan") + { + NiceTimeSpanMsToBuffer(1, Buffer); + CHECK(StringEquals(Buffer, "1ms")); + + NiceTimeSpanMsToBuffer(900, Buffer); + CHECK(StringEquals(Buffer, "900ms")); + + NiceTimeSpanMsToBuffer(1000, Buffer); + CHECK(StringEquals(Buffer, "1.00s")); + + NiceTimeSpanMsToBuffer(1900, Buffer); + CHECK(StringEquals(Buffer, "1.90s")); + + NiceTimeSpanMsToBuffer(19000, Buffer); + CHECK(StringEquals(Buffer, "19.0s")); + + NiceTimeSpanMsToBuffer(60000, Buffer); + CHECK(StringEquals(Buffer, "1m00s")); + + NiceTimeSpanMsToBuffer(600000, Buffer); + CHECK(StringEquals(Buffer, "10m00s")); + + NiceTimeSpanMsToBuffer(3600000, Buffer); + CHECK(StringEquals(Buffer, "1h00m")); + + NiceTimeSpanMsToBuffer(36000000, Buffer); + CHECK(StringEquals(Buffer, "10h00m")); + + NiceTimeSpanMsToBuffer(360000000, Buffer); + CHECK(StringEquals(Buffer, "100h00m")); + } +} + +void +string_forcelink() +{ +} + +TEST_CASE("StringBuilder") +{ + StringBuilder<64> sb; + + SUBCASE("Empty init") + { + const char* str = sb.c_str(); + + CHECK(StringLength(str) == 0); + } + + SUBCASE("Append single character") + { + sb.Append('a'); + + const char* str = sb.c_str(); + CHECK(StringLength(str) == 1); + CHECK(str[0] == 'a'); + + sb.Append('b'); + str = sb.c_str(); + CHECK(StringLength(str) == 2); + CHECK(str[0] == 'a'); + CHECK(str[1] == 'b'); + } + + SUBCASE("Append string") + { + sb.Append("a"); + + const char* str = sb.c_str(); + CHECK(StringLength(str) == 1); + CHECK(str[0] == 'a'); + + sb.Append("b"); + str = sb.c_str(); + CHECK(StringLength(str) == 2); + CHECK(str[0] == 'a'); + CHECK(str[1] == 'b'); + + sb.Append("cdefghijklmnopqrstuvwxyz"); + CHECK(sb.Size() == 26); + + sb.Append("abcdefghijklmnopqrstuvwxyz"); + CHECK(sb.Size() == 52); + + sb.Append("abcdefghijk"); + CHECK(sb.Size() == 63); + } +} + +TEST_CASE("ExtendableStringBuilder") +{ + ExtendableStringBuilder<16> sb; + + SUBCASE("Empty init") + { + const char* str = sb.c_str(); + + CHECK(StringLength(str) == 0); + } + + SUBCASE("Short append") + { + sb.Append("abcd"); + CHECK(sb.IsDynamic() == false); + } + + SUBCASE("Short+long append") + { + sb.Append("abcd"); + CHECK(sb.IsDynamic() == false); + // This should trigger a dynamic buffer allocation since the required + // capacity exceeds the internal fixed buffer. + sb.Append("abcdefghijklmnopqrstuvwxyz"); + CHECK(sb.IsDynamic() == true); + CHECK(sb.Size() == 30); + CHECK(sb.Size() == StringLength(sb.c_str())); + } +} + +TEST_CASE("WideStringBuilder") +{ + WideStringBuilder<64> sb; + + SUBCASE("Empty init") + { + const wchar_t* str = sb.c_str(); + + CHECK(StringLength(str) == 0); + } + + SUBCASE("Append single character") + { + sb.Append(L'a'); + + const wchar_t* str = sb.c_str(); + CHECK(StringLength(str) == 1); + CHECK(str[0] == L'a'); + + sb.Append(L'b'); + str = sb.c_str(); + CHECK(StringLength(str) == 2); + CHECK(str[0] == L'a'); + CHECK(str[1] == L'b'); + } + + SUBCASE("Append string") + { + sb.Append(L"a"); + + const wchar_t* str = sb.c_str(); + CHECK(StringLength(str) == 1); + CHECK(str[0] == L'a'); + + sb.Append(L"b"); + str = sb.c_str(); + CHECK(StringLength(str) == 2); + CHECK(str[0] == L'a'); + CHECK(str[1] == L'b'); + + sb.Append(L"cdefghijklmnopqrstuvwxyz"); + CHECK(sb.Size() == 26); + + sb.Append(L"abcdefghijklmnopqrstuvwxyz"); + CHECK(sb.Size() == 52); + + sb.Append(L"abcdefghijk"); + CHECK(sb.Size() == 63); + } +} + +TEST_CASE("ExtendableWideStringBuilder") +{ + ExtendableWideStringBuilder<16> sb; + + SUBCASE("Empty init") + { + CHECK(sb.Size() == 0); + + const wchar_t* str = sb.c_str(); + CHECK(StringLength(str) == 0); + } + + SUBCASE("Short append") + { + sb.Append(L"abcd"); + CHECK(sb.IsDynamic() == false); + } + + SUBCASE("Short+long append") + { + sb.Append(L"abcd"); + CHECK(sb.IsDynamic() == false); + // This should trigger a dynamic buffer allocation since the required + // capacity exceeds the internal fixed buffer. + sb.Append(L"abcdefghijklmnopqrstuvwxyz"); + CHECK(sb.IsDynamic() == true); + CHECK(sb.Size() == 30); + CHECK(sb.Size() == StringLength(sb.c_str())); + } +} + +TEST_CASE("utf8") +{ + SUBCASE("utf8towide") + { + // TODO: add more extensive testing here - this covers a very small space + + WideStringBuilder<32> wout; + Utf8ToWide(u8"abcdefghi", wout); + CHECK(StringEquals(L"abcdefghi", wout.c_str())); + + wout.Reset(); + + Utf8ToWide(u8"abc���", wout); + CHECK(StringEquals(L"abc���", wout.c_str())); + } + + SUBCASE("widetoutf8") + { + // TODO: add more extensive testing here - this covers a very small space + + StringBuilder<32> out; + + WideToUtf8(L"abcdefghi", out); + CHECK(StringEquals("abcdefghi", out.c_str())); + + out.Reset(); + + WideToUtf8(L"abc���", out); + CHECK(StringEquals(u8"abc���", out.c_str())); + } +} + +TEST_CASE("filepath") +{ + CHECK(FilepathFindExtension("foo\\bar\\baz.txt", ".txt") != nullptr); + CHECK(FilepathFindExtension("foo\\bar\\baz.txt", ".zap") == nullptr); + + CHECK(FilepathFindExtension("foo\\bar\\baz.txt") != nullptr); + CHECK(FilepathFindExtension("foo\\bar\\baz.txt") == std::string_view(".txt")); + + CHECK(FilepathFindExtension(".txt") == std::string_view(".txt")); +} + +TEST_CASE("string") +{ + using namespace std::literals; + + SUBCASE("hash_djb2") + { + CHECK(HashStringAsLowerDjb2("AbcdZ"sv) == HashStringDjb2("abcdz"sv)); + CHECK(HashStringAsLowerDjb2("aBCd"sv) == HashStringDjb2("abcd"sv)); + CHECK(HashStringAsLowerDjb2("aBCd"sv) == HashStringDjb2(ToLower("aBCd"sv))); + } + + SUBCASE("tolower") + { + CHECK_EQ(ToLower("te!st"sv), "te!st"sv); + CHECK_EQ(ToLower("TE%St"sv), "te%st"sv); + } + + SUBCASE("StrCaseCompare") + { + CHECK(StrCaseCompare("foo", "FoO") == 0); + CHECK(StrCaseCompare("Bar", "bAs") < 0); + CHECK(StrCaseCompare("bAr", "Bas") < 0); + CHECK(StrCaseCompare("BBr", "Bar") > 0); + CHECK(StrCaseCompare("Bbr", "BAr") > 0); + CHECK(StrCaseCompare("foo", "FoO", 3) == 0); + CHECK(StrCaseCompare("Bar", "bAs", 3) < 0); + CHECK(StrCaseCompare("BBr", "Bar", 2) > 0); + } + + SUBCASE("ForEachStrTok") + { + const auto Tokens = "here,is,my,different,tokens"sv; + int32_t ExpectedTokenCount = 5; + int32_t TokenCount = 0; + StringBuilder<512> Sb; + + TokenCount = ForEachStrTok(Tokens, ',', [&Sb](const std::string_view& Token) { + if (Sb.Size()) + { + Sb << ","; + } + Sb << Token; + return true; + }); + + CHECK(TokenCount == ExpectedTokenCount); + CHECK(Sb.ToString() == Tokens); + + ExpectedTokenCount = 1; + const auto Str = "mosdef"sv; + + Sb.Reset(); + TokenCount = ForEachStrTok(Str, ' ', [&Sb](const std::string_view& Token) { + Sb << Token; + return true; + }); + CHECK(Sb.ToString() == Str); + CHECK(TokenCount == ExpectedTokenCount); + + ExpectedTokenCount = 0; + TokenCount = ForEachStrTok(""sv, ',', [](const std::string_view&) { return true; }); + CHECK(TokenCount == ExpectedTokenCount); + } +} + +#endif + +} // namespace zen |