diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/yt/string/format-inl.h | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/yt/string/format-inl.h')
-rw-r--r-- | library/cpp/yt/string/format-inl.h | 744 |
1 files changed, 744 insertions, 0 deletions
diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h new file mode 100644 index 0000000000..5484d4a216 --- /dev/null +++ b/library/cpp/yt/string/format-inl.h @@ -0,0 +1,744 @@ +#ifndef FORMAT_INL_H_ +#error "Direct inclusion of this file is not allowed, include format.h" +// For the sake of sane code completion. +#include "format.h" +#endif + +#include "enum.h" +#include "string.h" + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/small_containers/compact_vector.h> + +#include <library/cpp/yt/misc/enum.h> + +#include <cctype> +#include <optional> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +static const char GenericSpecSymbol = 'v'; + +inline bool IsQuotationSpecSymbol(char symbol) +{ + return symbol == 'Q' || symbol == 'q'; +} + +// TStringBuf +inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf format) +{ + if (!format) { + builder->AppendString(value); + return; + } + + // Parse alignment. + bool alignLeft = false; + const char* current = format.begin(); + if (*current == '-') { + alignLeft = true; + ++current; + } + + bool hasAlign = false; + int alignSize = 0; + while (*current >= '0' && *current <= '9') { + hasAlign = true; + alignSize = 10 * alignSize + (*current - '0'); + if (alignSize > 1000000) { + builder->AppendString(TStringBuf("<alignment overflow>")); + return; + } + ++current; + } + + int padding = 0; + bool padLeft = false; + bool padRight = false; + if (hasAlign) { + padding = alignSize - value.size(); + if (padding < 0) { + padding = 0; + } + padLeft = !alignLeft; + padRight = alignLeft; + } + + bool singleQuotes = false; + bool doubleQuotes = false; + while (current < format.end()) { + if (*current == 'q') { + singleQuotes = true; + } else if (*current == 'Q') { + doubleQuotes = true; + } + ++current; + } + + if (padLeft) { + builder->AppendChar(' ', padding); + } + + if (singleQuotes || doubleQuotes) { + for (const char* valueCurrent = value.begin(); valueCurrent < value.end(); ++valueCurrent) { + char ch = *valueCurrent; + if (ch == '\n') { + builder->AppendString("\\n"); + } else if (ch == '\t') { + 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]); + } else if ((singleQuotes && ch == '\'') || (doubleQuotes && ch == '\"')) { + builder->AppendChar('\\'); + builder->AppendChar(ch); + } else { + builder->AppendChar(ch); + } + } + } else { + builder->AppendString(value); + } + + if (padRight) { + builder->AppendChar(' ', padding); + } +} + +// TString +inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(value), format); +} + +// const char* +inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(value), format); +} + +// char +inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(&value, 1), format); +} + +// bool +inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf format) +{ + // Parse custom flags. + bool lowercase = false; + const char* current = format.begin(); + while (current != format.end()) { + if (*current == 'l') { + ++current; + lowercase = true; + } else if (IsQuotationSpecSymbol(*current)) { + ++current; + } else + break; + } + + auto str = lowercase + ? (value ? TStringBuf("true") : TStringBuf("false")) + : (value ? TStringBuf("True") : TStringBuf("False")); + + builder->AppendString(str); +} + +// Fallback to ToString +struct TToStringFallbackValueFormatterTag +{ }; + +template <class TValue, class = void> +struct TValueFormatter +{ + static TToStringFallbackValueFormatterTag Do(TStringBuilderBase* builder, const TValue& value, TStringBuf format) + { + using ::ToString; + FormatValue(builder, ToString(value), format); + return {}; + } +}; + +// Enum +template <class TEnum> +struct TValueFormatter<TEnum, typename std::enable_if<TEnumTraits<TEnum>::IsEnum>::type> +{ + static void Do(TStringBuilderBase* builder, TEnum value, TStringBuf format) + { + // Parse custom flags. + bool lowercase = false; + const char* current = format.begin(); + while (current != format.end()) { + if (*current == 'l') { + ++current; + lowercase = true; + } else if (IsQuotationSpecSymbol(*current)) { + ++current; + } else { + break; + } + } + + FormatEnum(builder, value, lowercase); + } +}; + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TBegin TFormattableView<TRange, TFormatter>::begin() const +{ + return RangeBegin; +} + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TEnd TFormattableView<TRange, TFormatter>::end() const +{ + return RangeEnd; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeFormattableView( + const TRange& range, + TFormatter&& formatter) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter)}; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeShrunkFormattableView( + const TRange& range, + TFormatter&& formatter, + size_t limit) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter), limit}; +} + +template <class TRange, class TFormatter> +void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('['); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item); + ++index; + } + builder->AppendChar(']'); +} + +template <class TRange, class TFormatter> +void FormatKeyValueRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('{'); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item.first); + builder->AppendString(DefaultKeyValueDelimiter); + formatter(builder, item.second); + ++index; + } + builder->AppendChar('}'); +} + +// TFormattableView +template <class TRange, class TFormatter> +struct TValueFormatter<TFormattableView<TRange, TFormatter>> +{ + static void Do(TStringBuilderBase* builder, const TFormattableView<TRange, TFormatter>& range, TStringBuf /*format*/) + { + FormatRange(builder, range, range.Formatter, range.Limit); + } +}; + +template <class TFormatter> +TFormatterWrapper<TFormatter> MakeFormatterWrapper( + TFormatter&& formatter) +{ + return TFormatterWrapper<TFormatter>{ + .Formatter = std::move(formatter) + }; +} + +// TFormatterWrapper +template <class TFormatter> +struct TValueFormatter<TFormatterWrapper<TFormatter>> +{ + static void Do(TStringBuilderBase* builder, const TFormatterWrapper<TFormatter>& wrapper, TStringBuf /*format*/) + { + wrapper.Formatter(builder); + } +}; + +// std::vector +template <class T, class TAllocator> +struct TValueFormatter<std::vector<T, TAllocator>> +{ + static void Do(TStringBuilderBase* builder, const std::vector<T, TAllocator>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// TCompactVector +template <class T, unsigned N> +struct TValueFormatter<TCompactVector<T, N>> +{ + static void Do(TStringBuilderBase* builder, const TCompactVector<T, N>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::set +template <class T> +struct TValueFormatter<std::set<T>> +{ + static void Do(TStringBuilderBase* builder, const std::set<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::map +template <class K, class V> +struct TValueFormatter<std::map<K, V>> +{ + static void Do(TStringBuilderBase* builder, const std::map<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::multimap +template <class K, class V> +struct TValueFormatter<std::multimap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const std::multimap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashSet +template <class T> +struct TValueFormatter<THashSet<T>> +{ + static void Do(TStringBuilderBase* builder, const THashSet<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMultiSet +template <class T> +struct TValueFormatter<THashMultiSet<T>> +{ + static void Do(TStringBuilderBase* builder, const THashMultiSet<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMap +template <class K, class V> +struct TValueFormatter<THashMap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const THashMap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMultiMap +template <class K, class V> +struct TValueFormatter<THashMultiMap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const THashMultiMap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// TEnumIndexedVector +template <class E, class T> +struct TValueFormatter<TEnumIndexedVector<E, T>> +{ + static void Do(TStringBuilderBase* builder, const TEnumIndexedVector<E, T>& collection, TStringBuf format) + { + builder->AppendChar('{'); + bool firstItem = true; + for (const auto& index : TEnumTraits<E>::GetDomainValues()) { + if (!firstItem) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + FormatValue(builder, index, format); + builder->AppendString(": "); + FormatValue(builder, collection[index], format); + firstItem = false; + } + builder->AppendChar('}'); + } +}; + +// std::pair +template <class T1, class T2> +struct TValueFormatter<std::pair<T1, T2>> +{ + static void Do(TStringBuilderBase* builder, const std::pair<T1, T2>& value, TStringBuf format) + { + builder->AppendChar('{'); + FormatValue(builder, value.first, format); + builder->AppendString(TStringBuf(", ")); + FormatValue(builder, value.second, format); + builder->AppendChar('}'); + } +}; + +// std::optional +inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*format*/) +{ + builder->AppendString(TStringBuf("<null>")); +} + +template <class T> +struct TValueFormatter<std::optional<T>> +{ + static void Do(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf format) + { + if (value) { + FormatValue(builder, *value, format); + } else { + FormatValue(builder, std::nullopt, format); + } + } +}; + +template <class TValue> +auto FormatValue(TStringBuilderBase* builder, const TValue& value, TStringBuf format) -> + decltype(TValueFormatter<TValue>::Do(builder, value, format)) +{ + return TValueFormatter<TValue>::Do(builder, value, format); +} + +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); +} + +template <class TValue> +char* WriteIntToBufferBackwards(char* buffer, TValue value); + +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); + } +} + +#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")) + +#undef XX + +#define XX(valueType, castType, genericSpec) \ + inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ + { \ + FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \ + } + +XX(double, double, TStringBuf("lf")) +XX(float, float, TStringBuf("f")) + +#undef XX + +// Pointer +template <class T> +void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf format) +{ + FormatValueViaSprintf(builder, value, format, TStringBuf("p")); +} + +// TDuration (specialize for performance reasons) +inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*format*/) +{ + builder->AppendFormat("%vus", value.MicroSeconds()); +} + +// TInstant (specialize for TFormatTraits) +inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf format) +{ + // TODO(babenko): optimize + builder->AppendFormat("%v", ToString(value), format); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TArgFormatter> +void FormatImpl( + TStringBuilderBase* builder, + TStringBuf format, + const TArgFormatter& argFormatter) +{ + size_t argIndex = 0; + auto current = format.begin(); + while (true) { + // Scan verbatim part until stop symbol. + auto verbatimBegin = current; + auto verbatimEnd = verbatimBegin; + while (verbatimEnd != format.end() && *verbatimEnd != '%') { + ++verbatimEnd; + } + + // Copy verbatim part, if any. + size_t verbatimSize = verbatimEnd - verbatimBegin; + if (verbatimSize > 0) { + builder->AppendString(TStringBuf(verbatimBegin, verbatimSize)); + } + + // Handle stop symbol. + current = verbatimEnd; + if (current == format.end()) { + break; + } + + YT_ASSERT(*current == '%'); + ++current; + + if (*current == '%') { + // Verbatim %. + builder->AppendChar('%'); + ++current; + } else { + // Scan format part until stop symbol. + auto argFormatBegin = current; + auto argFormatEnd = argFormatBegin; + bool singleQuotes = false; + bool doubleQuotes = false; + + while ( + argFormatEnd != format.end() && + *argFormatEnd != GenericSpecSymbol && // value in generic format + *argFormatEnd != 'd' && // others are standard specifiers supported by printf + *argFormatEnd != 'i' && + *argFormatEnd != 'u' && + *argFormatEnd != 'o' && + *argFormatEnd != 'x' && + *argFormatEnd != 'X' && + *argFormatEnd != 'f' && + *argFormatEnd != 'F' && + *argFormatEnd != 'e' && + *argFormatEnd != 'E' && + *argFormatEnd != 'g' && + *argFormatEnd != 'G' && + *argFormatEnd != 'a' && + *argFormatEnd != 'A' && + *argFormatEnd != 'c' && + *argFormatEnd != 's' && + *argFormatEnd != 'p' && + *argFormatEnd != 'n') + { + if (*argFormatEnd == 'q') { + singleQuotes = true; + } else if (*argFormatEnd == 'Q') { + doubleQuotes = true; + } + ++argFormatEnd; + } + + // Handle end of format string. + if (argFormatEnd != format.end()) { + ++argFormatEnd; + } + + // 'n' means 'nothing'; skip the argument. + if (*argFormatBegin != 'n') { + // Format argument. + TStringBuf argFormat(argFormatBegin, argFormatEnd); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } + argFormatter(argIndex++, builder, argFormat); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } + } + + current = argFormatEnd; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TFormatTraits +{ + static constexpr bool HasCustomFormatValue = !std::is_same_v< + decltype(FormatValue( + static_cast<TStringBuilderBase*>(nullptr), + *static_cast<const T*>(nullptr), + TStringBuf())), + TToStringFallbackValueFormatterTag>; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <size_t IndexBase, class... TArgs> +struct TArgFormatterImpl; + +template <size_t IndexBase> +struct TArgFormatterImpl<IndexBase> +{ + void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*format*/) const + { + builder->AppendString(TStringBuf("<missing argument>")); + } +}; + +template <size_t IndexBase, class THeadArg, class... TTailArgs> +struct TArgFormatterImpl<IndexBase, THeadArg, TTailArgs...> +{ + explicit TArgFormatterImpl(const THeadArg& headArg, const TTailArgs&... tailArgs) + : HeadArg(headArg) + , TailFormatter(tailArgs...) + { } + + const THeadArg& HeadArg; + TArgFormatterImpl<IndexBase + 1, TTailArgs...> TailFormatter; + + void operator() (size_t index, TStringBuilderBase* builder, TStringBuf format) const + { + YT_ASSERT(index >= IndexBase); + if (index == IndexBase) { + FormatValue(builder, HeadArg, format); + } else { + TailFormatter(index, builder, format); + } + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <size_t Length, class... TArgs> +void Format( + TStringBuilderBase* builder, + const char (&format)[Length], + TArgs&&... args) +{ + Format(builder, TStringBuf(format, Length - 1), std::forward<TArgs>(args)...); +} + +template <class... TArgs> +void Format( + TStringBuilderBase* builder, + TStringBuf format, + TArgs&&... args) +{ + TArgFormatterImpl<0, TArgs...> argFormatter(args...); + FormatImpl(builder, format, argFormatter); +} + +template <size_t Length, class... TArgs> +TString Format( + const char (&format)[Length], + TArgs&&... args) +{ + TStringBuilder builder; + Format(&builder, format, std::forward<TArgs>(args)...); + return builder.Flush(); +} + +template <class... TArgs> +TString Format( + TStringBuf format, + TArgs&&... args) +{ + TStringBuilder builder; + Format(&builder, format, std::forward<TArgs>(args)...); + return builder.Flush(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT |