diff options
author | babenko <babenko@yandex-team.com> | 2022-08-26 10:04:55 +0300 |
---|---|---|
committer | babenko <babenko@yandex-team.com> | 2022-08-26 10:04:55 +0300 |
commit | af6b0b93037268a9ca1343dd09629cf910c944d9 (patch) | |
tree | 609b08bbb2cd7498d55b6f7a92aaf1a4d51d003c | |
parent | d40e0a72d30643f29b6fe0dee30d6d686f563e3f (diff) | |
download | ydb-af6b0b93037268a9ca1343dd09629cf910c944d9.tar.gz |
Natively support %x and %X in Format
-rw-r--r-- | library/cpp/yt/string/CMakeLists.txt | 1 | ||||
-rw-r--r-- | library/cpp/yt/string/format-inl.h | 115 | ||||
-rw-r--r-- | library/cpp/yt/string/format.cpp | 110 | ||||
-rw-r--r-- | library/cpp/yt/string/string.cpp | 118 | ||||
-rw-r--r-- | library/cpp/yt/string/string.h | 9 | ||||
-rw-r--r-- | library/cpp/yt/string/unittests/format_ut.cpp | 24 |
6 files changed, 289 insertions, 88 deletions
diff --git a/library/cpp/yt/string/CMakeLists.txt b/library/cpp/yt/string/CMakeLists.txt index 757c10151f..4ee8e8324a 100644 --- a/library/cpp/yt/string/CMakeLists.txt +++ b/library/cpp/yt/string/CMakeLists.txt @@ -20,4 +20,5 @@ target_sources(cpp-yt-string PRIVATE ${CMAKE_SOURCE_DIR}/library/cpp/yt/string/enum.cpp ${CMAKE_SOURCE_DIR}/library/cpp/yt/string/guid.cpp ${CMAKE_SOURCE_DIR}/library/cpp/yt/string/string.cpp + ${CMAKE_SOURCE_DIR}/library/cpp/yt/string/format.cpp ) diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h index 5484d4a216..c437d7ab34 100644 --- a/library/cpp/yt/string/format-inl.h +++ b/library/cpp/yt/string/format-inl.h @@ -13,6 +13,8 @@ #include <library/cpp/yt/misc/enum.h> +#include <util/system/platform.h> + #include <cctype> #include <optional> @@ -20,7 +22,7 @@ namespace NYT { //////////////////////////////////////////////////////////////////////////////// -static const char GenericSpecSymbol = 'v'; +static constexpr char GenericSpecSymbol = 'v'; inline bool IsQuotationSpecSymbol(char symbol) { @@ -91,8 +93,8 @@ inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBu builder->AppendString("\\t"); } else if (ch < PrintableASCIILow || ch > PrintableASCIIHigh) { builder->AppendString("\\x"); - builder->AppendChar(Int2Hex[static_cast<ui8>(ch) >> 4]); - builder->AppendChar(Int2Hex[static_cast<ui8>(ch) & 0xf]); + builder->AppendChar(IntToHexLowercase[static_cast<ui8>(ch) >> 4]); + builder->AppendChar(IntToHexLowercase[static_cast<ui8>(ch) & 0xf]); } else if ((singleQuotes && ch == '\'') || (doubleQuotes && ch == '\"')) { builder->AppendChar('\\'); builder->AppendChar(ch); @@ -121,6 +123,12 @@ inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringB FormatValue(builder, TStringBuf(value), format); } +// char* +inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(value), format); +} + // char inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf format) { @@ -438,91 +446,54 @@ auto FormatValue(TStringBuilderBase* builder, const TValue& value, TStringBuf fo return TValueFormatter<TValue>::Do(builder, value, format); } +namespace NDetail { + template <class TValue> void FormatValueViaSprintf( TStringBuilderBase* builder, TValue value, TStringBuf format, - TStringBuf genericSpec) -{ - constexpr int MaxFormatSize = 64; - constexpr int SmallResultSize = 64; - - auto copyFormat = [] (char* destination, const char* source, int length) { - int position = 0; - for (int index = 0; index < length; ++index) { - if (IsQuotationSpecSymbol(source[index])) { - continue; - } - destination[position] = source[index]; - ++position; - } - return destination + position; - }; - - char formatBuf[MaxFormatSize]; - YT_VERIFY(format.length() >= 1 && format.length() <= MaxFormatSize - 2); // one for %, one for \0 - formatBuf[0] = '%'; - if (format[format.length() - 1] == GenericSpecSymbol) { - char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length() - 1); - ::memcpy(formatEnd, genericSpec.begin(), genericSpec.length()); - formatEnd[genericSpec.length()] = '\0'; - } else { - char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length()); - *formatEnd = '\0'; - } - - char* result = builder->Preallocate(SmallResultSize); - size_t resultSize = ::snprintf(result, SmallResultSize, formatBuf, value); - if (resultSize >= SmallResultSize) { - result = builder->Preallocate(resultSize + 1); - YT_VERIFY(::snprintf(result, resultSize + 1, formatBuf, value) == static_cast<int>(resultSize)); - } - builder->Advance(resultSize); -} + TStringBuf genericSpec); template <class TValue> -char* WriteIntToBufferBackwards(char* buffer, TValue value); +void FormatIntValue( + TStringBuilderBase* builder, + TValue value, + TStringBuf format, + TStringBuf genericSpec); -template <class TValue> -void FormatValueViaHelper(TStringBuilderBase* builder, TValue value, TStringBuf format, TStringBuf genericSpec) -{ - if (format == TStringBuf("v")) { - const int MaxResultSize = 64; - char buffer[MaxResultSize]; - char* end = buffer + MaxResultSize; - char* start = WriteIntToBufferBackwards(end, value); - builder->AppendString(TStringBuf(start, end)); - } else { - FormatValueViaSprintf(builder, value, format, genericSpec); - } -} +} // namespace NDetail #define XX(valueType, castType, genericSpec) \ inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ { \ - FormatValueViaHelper(builder, static_cast<castType>(value), format, genericSpec); \ - } - -XX(i8, int, TStringBuf("d")) -XX(ui8, unsigned int, TStringBuf("u")) -XX(i16, int, TStringBuf("d")) -XX(ui16, unsigned int, TStringBuf("u")) -XX(i32, int, TStringBuf("d")) -XX(ui32, unsigned int, TStringBuf("u")) -XX(long, long, TStringBuf("ld")) -XX(unsigned long, unsigned long, TStringBuf("lu")) + NYT::NDetail::FormatIntValue(builder, static_cast<castType>(value), format, genericSpec); \ + } + +XX(i8, i32, TStringBuf("d")) +XX(ui8, ui32, TStringBuf("u")) +XX(i16, i32, TStringBuf("d")) +XX(ui16, ui32, TStringBuf("u")) +XX(i32, i32, TStringBuf("d")) +XX(ui32, ui32, TStringBuf("u")) +#ifdef _win_ +XX(long long, i64, TStringBuf("lld")) +XX(unsigned long long, ui64, TStringBuf("llu")) +#else +XX(long, i64, TStringBuf("ld")) +XX(unsigned long, ui64, TStringBuf("lu")) +#endif #undef XX #define XX(valueType, castType, genericSpec) \ inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ { \ - FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \ + NYT::NDetail::FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \ } -XX(double, double, TStringBuf("lf")) -XX(float, float, TStringBuf("f")) +XX(double, double, TStringBuf("lf")) +XX(float, float, TStringBuf("f")) #undef XX @@ -530,7 +501,7 @@ XX(float, float, TStringBuf("f")) template <class T> void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf format) { - FormatValueViaSprintf(builder, value, format, TStringBuf("p")); + NYT::NDetail::FormatValueViaSprintf(builder, static_cast<const void*>(value), format, TStringBuf("p")); } // TDuration (specialize for performance reasons) @@ -548,6 +519,8 @@ inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf //////////////////////////////////////////////////////////////////////////////// +namespace NDetail { + template <class TArgFormatter> void FormatImpl( TStringBuilderBase* builder, @@ -649,6 +622,8 @@ void FormatImpl( } } +} // namespace NDetail + //////////////////////////////////////////////////////////////////////////////// template <class T> @@ -716,7 +691,7 @@ void Format( TArgs&&... args) { TArgFormatterImpl<0, TArgs...> argFormatter(args...); - FormatImpl(builder, format, argFormatter); + NYT::NDetail::FormatImpl(builder, format, argFormatter); } template <size_t Length, class... TArgs> diff --git a/library/cpp/yt/string/format.cpp b/library/cpp/yt/string/format.cpp new file mode 100644 index 0000000000..6c824e8ba3 --- /dev/null +++ b/library/cpp/yt/string/format.cpp @@ -0,0 +1,110 @@ +#include "format.h" +#include "string.h" + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +template <class TValue> +void FormatValueViaSprintf( + TStringBuilderBase* builder, + TValue value, + TStringBuf format, + TStringBuf genericSpec) +{ + constexpr int MaxFormatSize = 64; + constexpr int SmallResultSize = 64; + + auto copyFormat = [] (char* destination, const char* source, int length) { + int position = 0; + for (int index = 0; index < length; ++index) { + if (IsQuotationSpecSymbol(source[index])) { + continue; + } + destination[position] = source[index]; + ++position; + } + return destination + position; + }; + + char formatBuf[MaxFormatSize]; + YT_VERIFY(format.length() >= 1 && format.length() <= MaxFormatSize - 2); // one for %, one for \0 + formatBuf[0] = '%'; + if (format[format.length() - 1] == GenericSpecSymbol) { + char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length() - 1); + ::memcpy(formatEnd, genericSpec.begin(), genericSpec.length()); + formatEnd[genericSpec.length()] = '\0'; + } else { + char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length()); + *formatEnd = '\0'; + } + + char* result = builder->Preallocate(SmallResultSize); + size_t resultSize = ::snprintf(result, SmallResultSize, formatBuf, value); + if (resultSize >= SmallResultSize) { + result = builder->Preallocate(resultSize + 1); + YT_VERIFY(::snprintf(result, resultSize + 1, formatBuf, value) == static_cast<int>(resultSize)); + } + builder->Advance(resultSize); +} + +#define XX(type) \ + template \ + void FormatValueViaSprintf( \ + TStringBuilderBase* builder, \ + type value, \ + TStringBuf format, \ + TStringBuf genericSpec); + +XX(i32) +XX(ui32) +XX(i64) +XX(ui64) +XX(float) +XX(double) +XX(const void*) + +#undef XX + +template <class TValue> +void FormatIntValue( + TStringBuilderBase* builder, + TValue value, + TStringBuf format, + TStringBuf genericSpec) +{ + if (format == TStringBuf("v")) { + constexpr int MaxResultSize = 64; + char buffer[MaxResultSize]; + char* end = buffer + MaxResultSize; + char* start = WriteDecIntToBufferBackwards(end, value); + builder->AppendString(TStringBuf(start, end)); + } else if (format == TStringBuf("x") || format == TStringBuf("X")) { + constexpr int MaxResultSize = 64; + char buffer[MaxResultSize]; + char* end = buffer + MaxResultSize; + char* start = WriteHexIntToBufferBackwards(end, value, format[0] == 'X'); + builder->AppendString(TStringBuf(start, end)); + } else { + FormatValueViaSprintf(builder, value, format, genericSpec); + } +} + +#define XX(type) \ + template \ + void FormatIntValue( \ + TStringBuilderBase* builder, \ + type value, \ + TStringBuf format, \ + TStringBuf genericSpec); + +XX(i32) +XX(ui32) +XX(i64) +XX(ui64) + +#undef XX + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/library/cpp/yt/string/string.cpp b/library/cpp/yt/string/string.cpp index 7440ac3fdd..a96f212cdf 100644 --- a/library/cpp/yt/string/string.cpp +++ b/library/cpp/yt/string/string.cpp @@ -105,7 +105,7 @@ TString Trim(const TString& str, const TString& whitespaces) namespace { -ui16 DecimalDigits2[100] = { +const ui16 DecimalDigits2[100] = { 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, 12593, 12849, 13105, 13361, 13617, 13873, 14129, 14385, 14641, 12338, 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, @@ -119,7 +119,7 @@ ui16 DecimalDigits2[100] = { }; template <class T> -char* WriteSignedIntToBufferBackwardsImpl(char* ptr, T value, TStringBuf min) +char* WriteSignedDecIntToBufferBackwardsImpl(char* ptr, T value, TStringBuf min) { if (value == 0) { --ptr; @@ -141,8 +141,8 @@ char* WriteSignedIntToBufferBackwardsImpl(char* ptr, T value, TStringBuf min) } while (value >= 10) { - i64 rem = value % 100; - i64 quot = value / 100; + auto rem = value % 100; + auto quot = value / 100; ptr -= 2; ::memcpy(ptr, &DecimalDigits2[rem], 2); value = quot; @@ -162,7 +162,7 @@ char* WriteSignedIntToBufferBackwardsImpl(char* ptr, T value, TStringBuf min) } template <class T> -char* WriteUnsignedIntToBufferBackwardsImpl(char* ptr, T value) +char* WriteUnsignedDecIntToBufferBackwardsImpl(char* ptr, T value) { if (value == 0) { --ptr; @@ -171,8 +171,8 @@ char* WriteUnsignedIntToBufferBackwardsImpl(char* ptr, T value) } while (value >= 10) { - i64 rem = value % 100; - i64 quot = value / 100; + auto rem = value % 100; + auto quot = value / 100; ptr -= 2; ::memcpy(ptr, &DecimalDigits2[rem], 2); value = quot; @@ -189,27 +189,115 @@ char* WriteUnsignedIntToBufferBackwardsImpl(char* ptr, T value) } // namespace template <> -char* WriteIntToBufferBackwards(char* ptr, i32 value) +char* WriteDecIntToBufferBackwards(char* ptr, i32 value) { - return WriteSignedIntToBufferBackwardsImpl(ptr, value, TStringBuf("-2147483647")); + return WriteSignedDecIntToBufferBackwardsImpl(ptr, value, TStringBuf("-2147483647")); } template <> -char* WriteIntToBufferBackwards(char* ptr, i64 value) +char* WriteDecIntToBufferBackwards(char* ptr, i64 value) { - return WriteSignedIntToBufferBackwardsImpl(ptr, value, TStringBuf("-9223372036854775808")); + return WriteSignedDecIntToBufferBackwardsImpl(ptr, value, TStringBuf("-9223372036854775808")); } template <> -char* WriteIntToBufferBackwards(char* ptr, ui32 value) +char* WriteDecIntToBufferBackwards(char* ptr, ui32 value) { - return WriteUnsignedIntToBufferBackwardsImpl(ptr, value); + return WriteUnsignedDecIntToBufferBackwardsImpl(ptr, value); } template <> -char* WriteIntToBufferBackwards(char* ptr, ui64 value) +char* WriteDecIntToBufferBackwards(char* ptr, ui64 value) { - return WriteUnsignedIntToBufferBackwardsImpl(ptr, value); + return WriteUnsignedDecIntToBufferBackwardsImpl(ptr, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +template <class T> +char* WriteSignedHexIntToBufferBackwardsImpl(char* ptr, T value, bool uppercase, TStringBuf min) +{ + if (value == 0) { + --ptr; + *ptr = '0'; + return ptr; + } + + // The negative value handling code below works incorrectly for min values. + if (value == std::numeric_limits<T>::min()) { + ptr -= min.length(); + ::memcpy(ptr, min.begin(), min.length()); + return ptr; + } + + bool negative = false; + if (value < 0) { + negative = true; + value = -value; + } + + while (value != 0) { + auto rem = value & 0xf; + auto quot = value >> 4; + --ptr; + *ptr = uppercase ? IntToHexUppercase[rem] : IntToHexLowercase[rem]; + value = quot; + } + + if (negative) { + --ptr; + *ptr = '-'; + } + + return ptr; +} + +template <class T> +char* WriteUnsignedHexIntToBufferBackwardsImpl(char* ptr, T value, bool uppercase) +{ + if (value == 0) { + --ptr; + *ptr = '0'; + return ptr; + } + + while (value != 0) { + auto rem = value & 0xf; + auto quot = value >> 4; + --ptr; + *ptr = uppercase ? IntToHexUppercase[rem] : IntToHexLowercase[rem]; + value = quot; + } + + return ptr; +} + +} // namespace + +template <> +char* WriteHexIntToBufferBackwards(char* ptr, i32 value, bool uppercase) +{ + return WriteSignedHexIntToBufferBackwardsImpl(ptr, value, uppercase, TStringBuf("-80000000")); +} + +template <> +char* WriteHexIntToBufferBackwards(char* ptr, i64 value, bool uppercase) +{ + return WriteSignedHexIntToBufferBackwardsImpl(ptr, value, uppercase, TStringBuf("-8000000000000000")); +} + +template <> +char* WriteHexIntToBufferBackwards(char* ptr, ui32 value, bool uppercase) +{ + return WriteUnsignedHexIntToBufferBackwardsImpl(ptr, value, uppercase); +} + +template <> +char* WriteHexIntToBufferBackwards(char* ptr, ui64 value, bool uppercase) +{ + return WriteUnsignedHexIntToBufferBackwardsImpl(ptr, value, uppercase); } //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/string.h b/library/cpp/yt/string/string.h index ae6c99caab..04eb115cb2 100644 --- a/library/cpp/yt/string/string.h +++ b/library/cpp/yt/string/string.h @@ -36,7 +36,8 @@ static constexpr TStringBuf DefaultRangeEllipsisFormat = "..."; // ASCII characters from 0x20 = ' ' to 0x7e = '~' are printable. static constexpr char PrintableASCIILow = 0x20; static constexpr char PrintableASCIIHigh = 0x7e; -static constexpr TStringBuf Int2Hex = "0123456789abcdef"; +static constexpr TStringBuf IntToHexLowercase = "0123456789abcdef"; +static constexpr TStringBuf IntToHexUppercase = "0123456789ABCDEF"; //! Joins a range of items into a string intermixing them with the delimiter. /*! @@ -196,7 +197,11 @@ TString Trim(const TString& str, const TString& whitespaces); //! Implemented for |[u]i(32|64)|. template <class T> -char* WriteIntToBufferBackwards(char* ptr, T value); +char* WriteDecIntToBufferBackwards(char* ptr, T value); + +//! Implemented for |[u]i(32|64)|. +template <class T> +char* WriteHexIntToBufferBackwards(char* ptr, T value, bool uppercase); //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/unittests/format_ut.cpp b/library/cpp/yt/string/unittests/format_ut.cpp index ee069bb2c0..20ec6392b5 100644 --- a/library/cpp/yt/string/unittests/format_ut.cpp +++ b/library/cpp/yt/string/unittests/format_ut.cpp @@ -74,7 +74,7 @@ TEST(TFormatTest, Strings) EXPECT_EQ(100, std::ssize(Format("%100v", "abc"))); } -TEST(TFormatTest, Integers) +TEST(TFormatTest, DecIntegers) { EXPECT_EQ("123", Format("%d", 123)); EXPECT_EQ("123", Format("%v", 123)); @@ -101,6 +101,28 @@ TEST(TFormatTest, Integers) EXPECT_EQ("18446744073709551615", Format("%v", std::numeric_limits<ui64>::max())); } +TEST(TFormatTest, HexIntegers) +{ + EXPECT_EQ("7b", Format("%x", 123)); + EXPECT_EQ("7B", Format("%X", 123)); + + EXPECT_EQ("02a", Format("%03x", 42)); + EXPECT_EQ("2a", Format("%01x", 42)); + + EXPECT_EQ("7fffffff", Format("%x", std::numeric_limits<i32>::max())); + EXPECT_EQ("-80000000", Format("%x", std::numeric_limits<i32>::min())); + + EXPECT_EQ("0", Format("%x", 0U)); + EXPECT_EQ("0", Format("%X", 0U)); + EXPECT_EQ("ffffffff", Format("%x", std::numeric_limits<ui32>::max())); + + EXPECT_EQ("7fffffffffffffff", Format("%x", std::numeric_limits<i64>::max())); + EXPECT_EQ("-8000000000000000", Format("%x", std::numeric_limits<i64>::min())); + + EXPECT_EQ("0", Format("%x", 0ULL)); + EXPECT_EQ("ffffffffffffffff", Format("%x", std::numeric_limits<ui64>::max())); +} + TEST(TFormatTest, Floats) { EXPECT_EQ("3.14", Format("%.2f", 3.1415F)); |