diff options
Diffstat (limited to 'src/zencore/string.cpp')
| -rw-r--r-- | src/zencore/string.cpp | 154 |
1 files changed, 150 insertions, 4 deletions
diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 358722b0b..34519b83b 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -9,6 +9,7 @@ #include <inttypes.h> #include <math.h> #include <stdio.h> +#include <charconv> #include <exception> #include <ostream> #include <stdexcept> @@ -54,15 +55,23 @@ namespace zen { bool ToString(std::span<char> Buffer, uint64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size()) + { + return false; + } + *Ptr = '\0'; return true; } bool ToString(std::span<char> Buffer, int64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size()) + { + return false; + } + *Ptr = '\0'; return true; } @@ -381,6 +390,34 @@ NiceNumGeneral(uint64_t Num, std::span<char> Buffer, NicenumFormat Format) } size_t +ThousandsToBuffer(uint64_t Num, std::span<char> Buffer) +{ + // Format into a temporary buffer without separators + char Tmp[24]; + int Len = snprintf(Tmp, sizeof(Tmp), "%llu", (unsigned long long)Num); + + // Insert comma separators + int SepCount = (Len - 1) / 3; + int TotalLen = Len + SepCount; + ZEN_ASSERT(TotalLen < (int)Buffer.size()); + + int Src = Len - 1; + int Dst = TotalLen; + Buffer[Dst--] = '\0'; + + for (int i = 0; Src >= 0; i++) + { + if (i > 0 && i % 3 == 0) + { + Buffer[Dst--] = ','; + } + Buffer[Dst--] = Tmp[Src--]; + } + + return TotalLen; +} + +size_t NiceNumToBuffer(uint64_t Num, std::span<char> Buffer) { return NiceNumGeneral(Num, Buffer, kNicenum1024); @@ -515,6 +552,40 @@ template class StringBuilderImpl<wchar_t>; ////////////////////////////////////////////////////////////////////////// void +StringBuilderBase::AppendPaddedInt(int64_t Value, int MinWidth) +{ + char Buf[24]; + char* End = Buf + sizeof(Buf); + char* Ptr = End; + bool Negative = Value < 0; + uint64_t Abs = Negative ? uint64_t(-Value) : uint64_t(Value); + do + { + *--Ptr = '0' + char(Abs % 10); + Abs /= 10; + } while (Abs > 0); + while ((End - Ptr) < MinWidth) + { + *--Ptr = '0'; + } + if (Negative) + { + *--Ptr = '-'; + } + AppendRange(Ptr, End); +} + +void +StringBuilderBase::AppendFill(char C, size_t Count) +{ + EnsureCapacity(Count); + std::memset(m_CurPos, C, Count); + m_CurPos += Count; +} + +////////////////////////////////////////////////////////////////////////// + +void UrlDecode(std::string_view InUrl, StringBuilderBase& OutUrl) { std::string_view::size_type i = 0; @@ -1279,6 +1350,81 @@ TEST_CASE("hidesensitivestring") CHECK_EQ(HideSensitiveString("1234567890123456789"sv), "1234XXXX..."sv); } +TEST_CASE("CompactString.default") +{ + CompactString S; + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); + CHECK(S.ToView() == std::string_view()); + CHECK(S.c_str()[0] == '\0'); +} + +TEST_CASE("CompactString.empty") +{ + CompactString S(std::string_view("")); + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); +} + +TEST_CASE("CompactString.short") +{ + CompactString S(std::string_view("hello")); + CHECK(!S.IsEmpty()); + CHECK(S.Size() == 5); + CHECK(S.ToView() == std::string_view("hello")); +} + +TEST_CASE("CompactString.sentinel_boundary") +{ + // 254 chars — largest value that fits in the prefix byte + std::string Str254(254, 'x'); + std::string_view View254(Str254); + CompactString S(View254); + CHECK(S.Size() == 254); + CHECK(S.ToView() == View254); +} + +TEST_CASE("CompactString.sentinel_exact") +{ + // 255 chars — hits the 0xFF sentinel, falls back to strlen + std::string Str255(255, 'y'); + std::string_view View255(Str255); + CompactString S(View255); + CHECK(S.Size() == 255); + CHECK(S.ToView() == View255); +} + +TEST_CASE("CompactString.long") +{ + // Well beyond the sentinel + std::string Str512(512, 'z'); + std::string_view View512(Str512); + CompactString S(View512); + CHECK(S.Size() == 512); + CHECK(S.ToView() == View512); +} + +TEST_CASE("CompactString.move") +{ + CompactString A(std::string_view("test")); + CompactString B(std::move(A)); + CHECK(A.IsEmpty()); + CHECK(B.ToView() == std::string_view("test")); + + CompactString C(std::string_view("first")); + CompactString D(std::string_view("second")); + D = std::move(C); + CHECK(C.IsEmpty()); + CHECK(D.ToView() == std::string_view("first")); +} + +TEST_CASE("CompactString.implicit_conversion") +{ + CompactString S(std::string_view("view")); + std::string_view V = S; + CHECK(V == std::string_view("view")); +} + TEST_SUITE_END(); void |