aboutsummaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-06-07 13:13:50 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-06-07 13:27:39 +0300
commit327232940ecda2082cbcd9551049456522bbaffe (patch)
treeb9a4b9604260773016c98f1560f59e2d06ce535e /library
parent1924960f5dfa85e96d87306b3962f57862ab98c8 (diff)
downloadydb-327232940ecda2082cbcd9551049456522bbaffe.tar.gz
YT-21868: Refactor NYT::Format
NYT::Format had several problems: 1. There are too many ways to enable printing of T. Not all are equally good. You could specialize TValueFormatter, you could write an overload of FormatValue, you could write an overload of ToString, you could write an overload of operator << for special stream or you could specialize the Out function. 2. If you attempt to print T which cannot be printed, you get a linker error without a proper source location which is very frustrating to work with. 3. There is no static analysis of format string performed even when it is possible. 4. If you write FormatValue overload, you still have to write ToString overload if you want to use this function (and people tend to use it quite a bit, since it is defined for util types and enums. This pr addresses these issues to some extent. Relevant changes: 1. The only way to support NYT::Format is to define the FormatValue overload. Otherwise, you get a compile-time error. 2. Format overloads have changed: Now you have two options for general use: ``` TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args); TString Format(TRuntimeFormat fmt, TArgs&&... args); ``` Either overload checks if TArg has a FormatValue overload. TStaticFormat performs a compile-time check of flags and the argument count. It binds to any string literal and constexpr string/string_view (and TStringBuf). TRuntimeFormat has to be mentioned explicitly. Otherwise, you will get a compile-time error for using runtime variable as a format. 3(!!!). Types which name begins with NYT:: have a specialization of ToString function which uses FormatValue. Thus, if you write class in namespace NYT and define FormatValue, you get ToString automatically. If your type is not from namespace enclosing NYT, you can just call NYT::ToString for the same effect. This limitation was caused by the fact, that we cannot review all of the external projects code which might inherit from stl classes or adopt some other questionable code practises which may completely break the dispatching mechanism of ToString due to the specialization (there were such cases). Proper documentation of this library will be added soon, so that this interaction is made known. This limitation might be lifted later 77beb68082e10aaf48be1842aad8aba63f26c1bd
Diffstat (limited to 'library')
-rw-r--r--library/cpp/yt/logging/logger-inl.h2
-rw-r--r--library/cpp/yt/logging/logger.h2
-rw-r--r--library/cpp/yt/logging/static_analysis-inl.h13
-rw-r--r--library/cpp/yt/memory/ref.cpp18
-rw-r--r--library/cpp/yt/memory/ref.h10
-rw-r--r--library/cpp/yt/misc/wrapper_traits.h2
-rw-r--r--library/cpp/yt/string/format-inl.h1123
-rw-r--r--library/cpp/yt/string/format.h72
-rw-r--r--library/cpp/yt/string/format_analyser-inl.h43
-rw-r--r--library/cpp/yt/string/format_analyser.h69
-rw-r--r--library/cpp/yt/string/format_arg-inl.h67
-rw-r--r--library/cpp/yt/string/format_arg.h103
-rw-r--r--library/cpp/yt/string/format_string-inl.h56
-rw-r--r--library/cpp/yt/string/format_string.h61
-rw-r--r--library/cpp/yt/string/guid.cpp5
-rw-r--r--library/cpp/yt/string/guid.h1
-rw-r--r--library/cpp/yt/string/string_builder-inl.h54
-rw-r--r--library/cpp/yt/string/string_builder.h8
-rw-r--r--library/cpp/yt/string/unittests/enum_ut.cpp4
-rw-r--r--library/cpp/yt/string/unittests/format_ut.cpp42
-rw-r--r--library/cpp/yt/string/unittests/guid_ut.cpp2
-rw-r--r--library/cpp/yt/yson_string/convert.cpp3
-rw-r--r--library/cpp/yt/yson_string/string.cpp8
-rw-r--r--library/cpp/yt/yson_string/string.h6
24 files changed, 1075 insertions, 699 deletions
diff --git a/library/cpp/yt/logging/logger-inl.h b/library/cpp/yt/logging/logger-inl.h
index b20526532d..a334a37142 100644
--- a/library/cpp/yt/logging/logger-inl.h
+++ b/library/cpp/yt/logging/logger-inl.h
@@ -24,7 +24,7 @@ inline bool TLogger::IsAnchorUpToDate(const TLoggingAnchor& position) const
template <class... TArgs>
void TLogger::AddTag(const char* format, TArgs&&... args)
{
- AddRawTag(Format(format, std::forward<TArgs>(args)...));
+ AddRawTag(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...));
}
template <class TType>
diff --git a/library/cpp/yt/logging/logger.h b/library/cpp/yt/logging/logger.h
index c4a4e6adef..5cbd681876 100644
--- a/library/cpp/yt/logging/logger.h
+++ b/library/cpp/yt/logging/logger.h
@@ -22,7 +22,7 @@
#include <atomic>
-#if (__clang__ || __clang_major__ < 16)
+#if (!__clang__ || __clang_major__ < 16)
#define YT_DISABLE_FORMAT_STATIC_ANALYSIS
#endif
diff --git a/library/cpp/yt/logging/static_analysis-inl.h b/library/cpp/yt/logging/static_analysis-inl.h
index d4ec5343bc..13cd425d58 100644
--- a/library/cpp/yt/logging/static_analysis-inl.h
+++ b/library/cpp/yt/logging/static_analysis-inl.h
@@ -6,7 +6,7 @@
#include <library/cpp/yt/misc/preprocessor.h>
-#include <library/cpp/yt/string/format_analyser.h>
+#include <library/cpp/yt/string/format.h>
#include <string_view>
#include <variant> // monostate
@@ -20,6 +20,12 @@ template <class T>
struct TLoggerFormatArg
{ };
+// Required for TLoggerFormatArg to inherit CFormattable concept
+// from T.
+template <class T>
+ requires CFormattable<T>
+void FormatValue(TStringBuilderBase*, const TLoggerFormatArg<T>&, TStringBuf);
+
////////////////////////////////////////////////////////////////////////////////
// Stateless constexpr way of capturing arg types
@@ -32,7 +38,8 @@ struct TLoggerFormatArgs
// Used for macro conversion. Purposefully undefined.
template <class... TArgs>
-TLoggerFormatArgs<TArgs...> AsFormatArgs(TArgs&&...);
+TLoggerFormatArgs<std::remove_cvref_t<TArgs>...>
+AsFormatArgs(TArgs&&...);
////////////////////////////////////////////////////////////////////////////////
@@ -135,5 +142,5 @@ struct NYT::TFormatArg<NYT::NLogging::NDetail::TLoggerFormatArg<T>>
// "\"Value: %\" \"u\""
// Thus adding a \" \" sequence.
static constexpr auto FlagSpecifiers
- = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ NYT::TFormatArg<T>>();
+ = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ T>();
};
diff --git a/library/cpp/yt/memory/ref.cpp b/library/cpp/yt/memory/ref.cpp
index b5b636c9db..c01094846e 100644
--- a/library/cpp/yt/memory/ref.cpp
+++ b/library/cpp/yt/memory/ref.cpp
@@ -6,6 +6,8 @@
#include <library/cpp/yt/misc/port.h>
+#include <library/cpp/yt/string/format.h>
+
#include <util/system/info.h>
#include <util/system/align.h>
@@ -304,24 +306,24 @@ TSharedMutableRef TSharedMutableRef::MakeCopy(TRef ref, TRefCountedTypeCookie ta
////////////////////////////////////////////////////////////////////////////////
-TString ToString(TRef ref)
+void FormatValue(TStringBuilderBase* builder, const TRef& ref, TStringBuf spec)
{
- return TString(ref.Begin(), ref.End());
+ FormatValue(builder, TStringBuf{ref.Begin(), ref.End()}, spec);
}
-TString ToString(const TMutableRef& ref)
+void FormatValue(TStringBuilderBase* builder, const TMutableRef& ref, TStringBuf spec)
{
- return ToString(TRef(ref));
+ FormatValue(builder, TRef(ref), spec);
}
-TString ToString(const TSharedRef& ref)
+void FormatValue(TStringBuilderBase* builder, const TSharedRef& ref, TStringBuf spec)
{
- return ToString(TRef(ref));
+ FormatValue(builder, TRef(ref), spec);
}
-TString ToString(const TSharedMutableRef& ref)
+void FormatValue(TStringBuilderBase* builder, const TSharedMutableRef& ref, TStringBuf spec)
{
- return ToString(TRef(ref));
+ FormatValue(builder, TRef(ref), spec);
}
size_t GetPageSize()
diff --git a/library/cpp/yt/memory/ref.h b/library/cpp/yt/memory/ref.h
index 2177778a8f..2dfe861e75 100644
--- a/library/cpp/yt/memory/ref.h
+++ b/library/cpp/yt/memory/ref.h
@@ -4,6 +4,8 @@
#include "range.h"
#include "shared_range.h"
+#include <library/cpp/yt/string/format.h>
+
#include <type_traits>
namespace NYT {
@@ -381,10 +383,10 @@ private:
////////////////////////////////////////////////////////////////////////////////
-TString ToString(TRef ref);
-TString ToString(const TMutableRef& ref);
-TString ToString(const TSharedRef& ref);
-TString ToString(const TSharedMutableRef& ref);
+void FormatValue(TStringBuilderBase* builder, const TRef& ref, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TMutableRef& ref, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TSharedRef& ref, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TSharedMutableRef& ref, TStringBuf);
size_t GetPageSize();
size_t RoundUpToPage(size_t bytes);
diff --git a/library/cpp/yt/misc/wrapper_traits.h b/library/cpp/yt/misc/wrapper_traits.h
index 8a6d581419..1e000ebaff 100644
--- a/library/cpp/yt/misc/wrapper_traits.h
+++ b/library/cpp/yt/misc/wrapper_traits.h
@@ -1,5 +1,7 @@
#pragma once
+#include <util/generic/strbuf.h>
+
#include <concepts>
#include <utility>
diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h
index 4d9634039c..069538d84b 100644
--- a/library/cpp/yt/string/format-inl.h
+++ b/library/cpp/yt/string/format-inl.h
@@ -13,7 +13,11 @@
#include <library/cpp/yt/containers/enum_indexed_array.h>
+#include <library/cpp/yt/misc/concepts.h>
#include <library/cpp/yt/misc/enum.h>
+#include <library/cpp/yt/misc/wrapper_traits.h>
+
+#include <util/generic/maybe.h>
#include <util/system/platform.h>
@@ -21,28 +25,241 @@
#include <optional>
#include <span>
+#if __cplusplus >= 202302L
+ #include <filesystem>
+#endif
+
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-static constexpr char GenericSpecSymbol = 'v';
+// Helper functions for formatting.
+namespace NDetail {
+
+constexpr inline char IntroductorySymbol = '%';
+constexpr inline char GenericSpecSymbol = 'v';
inline bool IsQuotationSpecSymbol(char symbol)
{
return symbol == 'Q' || symbol == 'q';
}
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+void FormatValueViaSprintf(
+ TStringBuilderBase* builder,
+ TValue value,
+ TStringBuf spec,
+ TStringBuf genericSpec);
+
+template <class TValue>
+void FormatIntValue(
+ TStringBuilderBase* builder,
+ TValue value,
+ TStringBuf spec,
+ TStringBuf genericSpec);
+
+void FormatPointerValue(
+ TStringBuilderBase* builder,
+ const void* value,
+ TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Helper concepts for matching the correct overload.
+// NB(arkady-e1ppa): We prefer to hardcode the known types
+// so that someone doesn't accidentally implement the
+// "SimpleRange" concept and have a non-trivial
+// formatting procedure at the same time.
+
+template <class R>
+concept CKnownRange =
+ requires (R r) { [] <class... Ts> (std::vector<Ts...>) { } (r); } ||
+ requires (R r) { [] <class T, size_t E> (std::span<T, E>) { } (r); } ||
+ requires (R r) { [] <class T, size_t N> (TCompactVector<T, N>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (std::set<Ts...>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (THashSet<Ts...>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (THashMultiSet<Ts...>) { } (r); };
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class R>
+concept CKnownKVRange =
+ requires (R r) { [] <class... Ts> (std::map<Ts...>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (std::multimap<Ts...>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (THashMap<Ts...>) { } (r); } ||
+ requires (R r) { [] <class... Ts> (THashMultiMap<Ts...>) { } (r); };
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+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('}');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class R>
+concept CFormattableRange =
+ NDetail::CKnownRange<R> &&
+ CFormattable<typename R::value_type>;
+
+template <class R>
+concept CFormattableKVRange =
+ NDetail::CKnownKVRange<R> &&
+ CFormattable<typename R::key_type> &&
+ CFormattable<typename R::value_type>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+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 TFormatter>
+TFormatterWrapper<TFormatter> MakeFormatterWrapper(
+ TFormatter&& formatter)
+{
+ return TFormatterWrapper<TFormatter>{
+ .Formatter = std::move(formatter)
+ };
+}
+
+template <class... TArgs>
+TLazyMultiValueFormatter<TArgs...>::TLazyMultiValueFormatter(
+ TStringBuf format,
+ TArgs&&... args)
+ : Format_(format)
+ , Args_(std::forward<TArgs>(args)...)
+{ }
+
+template <class... TArgs>
+auto MakeLazyMultiValueFormatter(TStringBuf format, TArgs&&... args)
+{
+ return TLazyMultiValueFormatter<TArgs...>(format, std::forward<TArgs>(args)...);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Non-container objects.
+
+#define XX(valueType, castType, genericSpec) \
+ inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf spec) \
+ { \
+ NYT::NDetail::FormatIntValue(builder, static_cast<castType>(value), spec, 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"))
+XX(long, i64, TStringBuf(PRIdLEAST64))
+XX(long long, i64, TStringBuf(PRIdLEAST64))
+XX(unsigned long, ui64, TStringBuf(PRIuLEAST64))
+XX(unsigned long long, ui64, TStringBuf(PRIuLEAST64))
+
+#undef XX
+
+#define XX(valueType, castType, genericSpec) \
+ inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf spec) \
+ { \
+ NYT::NDetail::FormatValueViaSprintf(builder, static_cast<castType>(value), spec, genericSpec); \
+ }
+
+XX(double, double, TStringBuf("lf"))
+XX(float, float, TStringBuf("f"))
+
+#undef XX
+
+// Pointer
+template <class T>
+void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf spec)
+{
+ NYT::NDetail::FormatPointerValue(builder, static_cast<const void*>(value), spec);
+}
+
// TStringBuf
-inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf spec)
{
- if (!format) {
+ if (!spec) {
builder->AppendString(value);
return;
}
// Parse alignment.
bool alignLeft = false;
- const char* current = format.begin();
+ const char* current = spec.begin();
if (*current == '-') {
alignLeft = true;
++current;
@@ -75,7 +292,7 @@ inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBu
bool singleQuotes = false;
bool doubleQuotes = false;
bool escape = false;
- while (current < format.end()) {
+ while (current < spec.end()) {
switch (*current++) {
case 'q':
singleQuotes = true;
@@ -123,40 +340,66 @@ inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBu
}
// TString
-inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf spec)
{
- FormatValue(builder, TStringBuf(value), format);
+ FormatValue(builder, TStringBuf(value), spec);
}
// const char*
-inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf spec)
+{
+ FormatValue(builder, TStringBuf(value), spec);
+}
+
+template <size_t N>
+inline void FormatValue(TStringBuilderBase* builder, const char (&value)[N], TStringBuf spec)
{
- FormatValue(builder, TStringBuf(value), format);
+ FormatValue(builder, TStringBuf(value), spec);
}
// char*
-inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf spec)
{
- FormatValue(builder, TStringBuf(value), format);
+ FormatValue(builder, TStringBuf(value), spec);
}
+// std::string
+inline void FormatValue(TStringBuilderBase* builder, const std::string& value, TStringBuf spec)
+{
+ FormatValue(builder, TStringBuf(value), spec);
+}
+
+// std::string_view
+inline void FormatValue(TStringBuilderBase* builder, const std::string_view& value, TStringBuf spec)
+{
+ FormatValue(builder, TStringBuf(value), spec);
+}
+
+#if __cplusplus >= 202302L
+// std::filesystem::path
+inline void FormatValue(TStringBuilderBase* builder, const std::filesystem::path& value, TStringBuf spec)
+{
+ FormatValue(builder, std::string(value), spec);
+}
+#endif
+
// char
-inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf spec)
{
- FormatValue(builder, TStringBuf(&value, 1), format);
+ FormatValue(builder, TStringBuf(&value, 1), spec);
}
// bool
-inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf format)
+inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf spec)
{
// Parse custom flags.
bool lowercase = false;
- const char* current = format.begin();
- while (current != format.end()) {
+ const char* current = spec.begin();
+ while (current != spec.end()) {
if (*current == 'l') {
++current;
lowercase = true;
- } else if (IsQuotationSpecSymbol(*current)) {
+ } else if (NYT::NDetail::IsQuotationSpecSymbol(*current)) {
++current;
} else
break;
@@ -169,399 +412,316 @@ inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf form
builder->AppendString(str);
}
-// Fallback to ToString
-struct TToStringFallbackValueFormatterTag
-{ };
+// TDuration
+inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%vus", value.MicroSeconds());
+}
-template <class TValue, class = void>
-struct TValueFormatter
+// TInstant
+inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf spec)
{
- static TToStringFallbackValueFormatterTag Do(TStringBuilderBase* builder, const TValue& value, TStringBuf format)
- {
- using ::ToString;
- FormatValue(builder, ToString(value), format);
- return {};
- }
-};
+ // TODO(babenko): Optimize.
+ FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec);
+}
// Enum
template <class TEnum>
-struct TValueFormatter<TEnum, typename std::enable_if<TEnumTraits<TEnum>::IsEnum>::type>
+ requires (TEnumTraits<TEnum>::IsEnum)
+void FormatValue(TStringBuilderBase* builder, TEnum value, TStringBuf spec)
{
- 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;
- }
+ // Parse custom flags.
+ bool lowercase = false;
+ const char* current = spec.begin();
+ while (current != spec.end()) {
+ if (*current == 'l') {
+ ++current;
+ lowercase = true;
+ } else if (NYT::NDetail::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;
+ FormatEnum(builder, value, lowercase);
}
-template <class TRange, class TFormatter>
-typename TFormattableView<TRange, TFormatter>::TEnd TFormattableView<TRange, TFormatter>::end() const
-{
- return RangeEnd;
+template <class TArcadiaEnum>
+ requires (std::is_enum_v<TArcadiaEnum> && !TEnumTraits<TArcadiaEnum>::IsEnum)
+void FormatValue(TStringBuilderBase* builder, TArcadiaEnum value, TStringBuf /*spec*/)
+{
+ // NB(arkady-e1ppa): This can catch normal enums which
+ // just want to be serialized as numbers.
+ // Unfortunately, we have no way of determining that other than
+ // marking every relevant arcadia enum in the code by trait
+ // or writing their complete trait and placing such trait in
+ // every single file where it is formatted.
+ // We gotta figure something out but until that
+ // we will just have to make a string for such enums.
+ // If only arcadia enums provided compile-time check
+ // if enum is serializable :(((((.
+ builder->AppendString(NYT::ToStringIgnoringFormatValue(value));
}
+// Container objects.
+// NB(arkady-e1ppa): In order to support container combinations
+// we forward-declare them before defining.
+
+// TMaybe
+template <class T, class TPolicy>
+void FormatValue(TStringBuilderBase* builder, const TMaybe<T, TPolicy>& value, TStringBuf spec);
+
+// std::optional
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec);
+
+// std::pair
+template <class A, class B>
+void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec);
+
+// std::tuple
+template <class... Ts>
+void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec);
+
+// TEnumIndexedArray
+template <class E, class T>
+void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec);
+
+// One-valued ranges
+template <CFormattableRange TRange>
+void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec);
+
+// Two-valued ranges
+template <CFormattableKVRange TRange>
+void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec);
+
+// FormattableView
template <class TRange, class TFormatter>
-TFormattableView<TRange, TFormatter> MakeFormattableView(
- const TRange& range,
- TFormatter&& formatter)
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TFormattableView<TRange, TFormatter>& formattableView,
+ TStringBuf spec);
+
+// TFormatterWrapper
+template <class TFormatter>
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TFormatterWrapper<TFormatter>& wrapper,
+ TStringBuf spec);
+
+// TLazyMultiValueFormatter
+template <class... TArgs>
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TLazyMultiValueFormatter<TArgs...>& value,
+ TStringBuf /*spec*/);
+
+// TMaybe
+template <class T, class TPolicy>
+void FormatValue(TStringBuilderBase* builder, const TMaybe<T, TPolicy>& value, TStringBuf spec)
{
- return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter)};
+ FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec);
}
-template <class TRange, class TFormatter>
-TFormattableView<TRange, TFormatter> MakeShrunkFormattableView(
- const TRange& range,
- TFormatter&& formatter,
- size_t limit)
+// std::optional: nullopt
+inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*spec*/)
{
- return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter), limit};
+ builder->AppendString(TStringBuf("<null>"));
}
-template <class TRange, class TFormatter>
-void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max())
+// std::optional: generic T
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec)
{
- 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;
+ if (value.has_value()) {
+ FormatValue(builder, *value, spec);
+ } else {
+ FormatValue(builder, std::nullopt, spec);
}
- 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())
+// std::pair
+template <class A, class B>
+void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec)
{
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;
- }
+ FormatValue(builder, value.first, spec);
+ builder->AppendString(TStringBuf(", "));
+ FormatValue(builder, value.second, spec);
builder->AppendChar('}');
}
-// TFormattableView
-template <class TRange, class TFormatter>
-struct TValueFormatter<TFormattableView<TRange, TFormatter>>
+// std::tuple
+template <class... Ts>
+void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec)
{
- static void Do(TStringBuilderBase* builder, const TFormattableView<TRange, TFormatter>& range, TStringBuf /*format*/)
- {
- FormatRange(builder, range, range.Formatter, range.Limit);
- }
-};
+ builder->AppendChar('{');
-template <class TFormatter>
-TFormatterWrapper<TFormatter> MakeFormatterWrapper(
- TFormatter&& formatter)
-{
- return TFormatterWrapper<TFormatter>{
- .Formatter = std::move(formatter)
- };
+ [&] <size_t... Idx> (std::index_sequence<Idx...>) {
+ ([&] {
+ FormatValue(builder, std::get<Idx>(value), spec);
+ if constexpr (Idx != sizeof...(Ts)) {
+ builder->AppendString(TStringBuf(", "));
+ }
+ } (), ...);
+ } (std::index_sequence_for<Ts...>());
+
+ builder->AppendChar('}');
}
-// TFormatterWrapper
-template <class TFormatter>
-struct TValueFormatter<TFormatterWrapper<TFormatter>>
+// TEnumIndexedArray
+template <class E, class T>
+void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec)
{
- static void Do(TStringBuilderBase* builder, const TFormatterWrapper<TFormatter>& wrapper, TStringBuf /*format*/)
- {
- wrapper.Formatter(builder);
+ builder->AppendChar('{');
+ bool firstItem = true;
+ for (const auto& index : TEnumTraits<E>::GetDomainValues()) {
+ if (!firstItem) {
+ builder->AppendString(DefaultJoinToStringDelimiter);
+ }
+ FormatValue(builder, index, spec);
+ builder->AppendString(": ");
+ FormatValue(builder, collection[index], spec);
+ firstItem = false;
}
-};
+ builder->AppendChar('}');
+}
-// std::vector
-template <class T, class TAllocator>
-struct TValueFormatter<std::vector<T, TAllocator>>
+// One-valued ranges
+template <CFormattableRange TRange>
+void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf /*spec*/)
{
- static void Do(TStringBuilderBase* builder, const std::vector<T, TAllocator>& collection, TStringBuf /*format*/)
- {
- FormatRange(builder, collection, TDefaultFormatter());
- }
-};
+ NYT::FormatRange(builder, collection, TDefaultFormatter());
+}
-// std::span
-template <class T, size_t Extent>
-struct TValueFormatter<std::span<T, Extent>>
+// Two-valued ranges
+template <CFormattableKVRange TRange>
+void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf /*spec*/)
{
- static void Do(TStringBuilderBase* builder, const std::span<T, Extent>& collection, TStringBuf /*format*/)
- {
- FormatRange(builder, collection, TDefaultFormatter());
- }
-};
+ NYT::FormatKeyValueRange(builder, collection, TDefaultFormatter());
+}
-// TCompactVector
-template <class T, unsigned N>
-struct TValueFormatter<TCompactVector<T, N>>
+// FormattableView
+template <class TRange, class TFormatter>
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TFormattableView<TRange, TFormatter>& formattableView,
+ TStringBuf /*spec*/)
{
- static void Do(TStringBuilderBase* builder, const TCompactVector<T, N>& collection, TStringBuf /*format*/)
- {
- FormatRange(builder, collection, TDefaultFormatter());
- }
-};
+ NYT::FormatRange(builder, formattableView, formattableView.Formatter, formattableView.Limit);
+}
-// std::set
-template <class T>
-struct TValueFormatter<std::set<T>>
+// TFormatterWrapper
+template <class TFormatter>
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TFormatterWrapper<TFormatter>& wrapper,
+ TStringBuf /*spec*/)
{
- static void Do(TStringBuilderBase* builder, const std::set<T>& collection, TStringBuf /*format*/)
- {
- FormatRange(builder, collection, TDefaultFormatter());
- }
-};
+ wrapper.Formatter(builder);
+}
-// std::map
-template <class K, class V>
-struct TValueFormatter<std::map<K, V>>
+// TLazyMultiValueFormatter
+template <class... TArgs>
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TLazyMultiValueFormatter<TArgs...>& value,
+ TStringBuf /*spec*/)
{
- static void Do(TStringBuilderBase* builder, const std::map<K, V>& collection, TStringBuf /*format*/)
- {
- FormatKeyValueRange(builder, collection, TDefaultFormatter());
- }
-};
+ std::apply(
+ [&] <class... TInnerArgs> (TInnerArgs&&... args) {
+ builder->AppendFormat(value.Format_, std::forward<TInnerArgs>(args)...);
+ },
+ value.Args_);
+}
-// 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());
- }
-};
+namespace NDetail {
-// THashMultiSet
-template <class T>
-struct TValueFormatter<THashMultiSet<T>>
-{
- static void Do(TStringBuilderBase* builder, const THashMultiSet<T>& collection, TStringBuf /*format*/)
- {
- FormatRange(builder, collection, TDefaultFormatter());
- }
-};
+template <size_t HeadPos, class... TArgs>
+class TValueFormatter;
-// THashMap
-template <class K, class V>
-struct TValueFormatter<THashMap<K, V>>
+template <size_t HeadPos>
+class TValueFormatter<HeadPos>
{
- static void Do(TStringBuilderBase* builder, const THashMap<K, V>& collection, TStringBuf /*format*/)
+public:
+ void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*spec*/) const
{
- FormatKeyValueRange(builder, collection, TDefaultFormatter());
+ builder->AppendString(TStringBuf("<missing argument>"));
}
};
-// THashMultiMap
-template <class K, class V>
-struct TValueFormatter<THashMultiMap<K, V>>
+template <size_t HeadPos, class THead, class... TTail>
+class TValueFormatter<HeadPos, THead, TTail...>
{
- static void Do(TStringBuilderBase* builder, const THashMultiMap<K, V>& collection, TStringBuf /*format*/)
- {
- FormatKeyValueRange(builder, collection, TDefaultFormatter());
- }
-};
+public:
+ explicit TValueFormatter(const THead& head, const TTail&... tail) noexcept
+ : Head_(head)
+ , TailFormatter_(tail...)
+ { }
-// TEnumIndexedArray
-template <class E, class T>
-struct TValueFormatter<TEnumIndexedArray<E, T>>
-{
- static void Do(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf format)
+ void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const
{
- 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;
+ YT_ASSERT(index >= HeadPos);
+ if (index == HeadPos) {
+ FormatValue(builder, Head_, spec);
+ } else {
+ TailFormatter_(index, builder, spec);
}
- 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('}');
- }
+private:
+ const THead& Head_;
+ TValueFormatter<HeadPos + 1, TTail...> TailFormatter_;
};
-// std::optional
-inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*format*/)
-{
- builder->AppendString(TStringBuf("<null>"));
-}
+////////////////////////////////////////////////////////////////////////////////
-template <class T>
-struct TValueFormatter<std::optional<T>>
+template <class TRangeValue>
+class TRangeFormatter
{
- static void Do(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf format)
+public:
+ template <class... TArgs>
+ requires std::constructible_from<std::span<const TRangeValue>, TArgs...>
+ explicit TRangeFormatter(TArgs&&... args) noexcept
+ : Span_(std::forward<TArgs>(args)...)
+ { }
+
+ void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const
{
- if (value) {
- FormatValue(builder, *value, format);
+ if (index >= Span_.size()) {
+ builder->AppendString(TStringBuf("<missing argument>"));
} else {
- FormatValue(builder, std::nullopt, format);
+ FormatValue(builder, *(Span_.begin() + index), spec);
}
}
-};
-
-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);
-}
-
-namespace NDetail {
-
-template <class TValue>
-void FormatValueViaSprintf(
- TStringBuilderBase* builder,
- TValue value,
- TStringBuf format,
- TStringBuf genericSpec);
-
-template <class TValue>
-void FormatIntValue(
- TStringBuilderBase* builder,
- TValue value,
- TStringBuf format,
- TStringBuf genericSpec);
-
-void FormatPointerValue(
- TStringBuilderBase* builder,
- const void* value,
- TStringBuf format);
-
-} // namespace NDetail
-
-#define XX(valueType, castType, genericSpec) \
- inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \
- { \
- 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) \
- { \
- NYT::NDetail::FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \
- }
-XX(double, double, TStringBuf("lf"))
-XX(float, float, TStringBuf("f"))
+private:
+ std::span<const TRangeValue> Span_;
+};
-#undef XX
+////////////////////////////////////////////////////////////////////////////////
-// Pointer
template <class T>
-void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf format)
-{
- NYT::NDetail::FormatPointerValue(builder, static_cast<const void*>(value), format);
-}
-
-// 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);
-}
+concept CFormatter = CInvocable<T, void(size_t, TStringBuilderBase*, TStringBuf)>;
////////////////////////////////////////////////////////////////////////////////
-namespace NDetail {
-
-template <class TArgFormatter>
-void FormatImpl(
+template <CFormatter TFormatter>
+void RunFormatter(
TStringBuilderBase* builder,
- TStringBuf format,
- const TArgFormatter& argFormatter)
+ TStringBuf fmt,
+ const TFormatter& formatter)
{
size_t argIndex = 0;
- auto current = format.begin();
+ auto current = std::begin(fmt);
+ auto end = std::end(fmt);
while (true) {
// Scan verbatim part until stop symbol.
auto verbatimBegin = current;
- auto verbatimEnd = verbatimBegin;
- while (verbatimEnd != format.end() && *verbatimEnd != '%') {
- ++verbatimEnd;
- }
+ auto verbatimEnd = std::find(current, end, IntroductorySymbol);
// Copy verbatim part, if any.
size_t verbatimSize = verbatimEnd - verbatimBegin;
@@ -571,85 +731,86 @@ void FormatImpl(
// Handle stop symbol.
current = verbatimEnd;
- if (current == format.end()) {
+ if (current == end) {
break;
}
- YT_ASSERT(*current == '%');
+ YT_ASSERT(*current == IntroductorySymbol);
++current;
- if (*current == '%') {
+ if (*current == IntroductorySymbol) {
// Verbatim %.
- builder->AppendChar('%');
+ builder->AppendChar(IntroductorySymbol);
++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')
- {
- switch (*argFormatEnd) {
- case 'q':
- singleQuotes = true;
- break;
- case 'Q':
- doubleQuotes = true;
- break;
- case 'h':
- break;
- }
- ++argFormatEnd;
- }
+ continue;
+ }
- // Handle end of format string.
- if (argFormatEnd != format.end()) {
- ++argFormatEnd;
+ // Scan format part until stop symbol.
+ auto argFormatBegin = current;
+ auto argFormatEnd = argFormatBegin;
+ bool singleQuotes = false;
+ bool doubleQuotes = false;
+
+ while (
+ argFormatEnd != 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')
+ {
+ switch (*argFormatEnd) {
+ case 'q':
+ singleQuotes = true;
+ break;
+ case 'Q':
+ doubleQuotes = true;
+ break;
+ case 'h':
+ break;
}
+ ++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('"');
- }
- }
+ // Handle end of format string.
+ if (argFormatEnd != end) {
+ ++argFormatEnd;
+ }
- current = argFormatEnd;
+ // 'n' means 'nothing'; skip the argument.
+ if (*argFormatBegin != 'n') {
+ // Format argument.
+ TStringBuf argFormat(argFormatBegin, argFormatEnd);
+ if (singleQuotes) {
+ builder->AppendChar('\'');
+ }
+ if (doubleQuotes) {
+ builder->AppendChar('"');
+ }
+ formatter(argIndex++, builder, argFormat);
+ if (singleQuotes) {
+ builder->AppendChar('\'');
+ }
+ if (doubleQuotes) {
+ builder->AppendChar('"');
+ }
}
+
+ current = argFormatEnd;
}
}
@@ -658,186 +819,86 @@ void FormatImpl(
////////////////////////////////////////////////////////////////////////////////
template <class... TArgs>
-TLazyMultiValueFormatter<TArgs...>::TLazyMultiValueFormatter(
- TStringBuf format,
- TArgs&&... args)
- : Format_(format)
- , Args_(std::forward<TArgs>(args)...)
-{ }
-
-template <class... TArgs>
-void FormatValue(
- TStringBuilderBase* builder,
- const TLazyMultiValueFormatter<TArgs...>& value,
- TStringBuf /*format*/)
+void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args)
{
- std::apply(
- [&] <class... TInnerArgs> (TInnerArgs&&... args) {
- builder->AppendFormat(value.Format_, std::forward<TInnerArgs>(args)...);
- },
- value.Args_);
+ NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...);
+ NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter);
}
template <class... TArgs>
-auto MakeLazyMultiValueFormatter(TStringBuf format, TArgs&&... args)
-{
- return TLazyMultiValueFormatter<TArgs...>(format, std::forward<TArgs>(args)...);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-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 <typename TVectorElement>
-struct TSpanArgFormatterImpl
-{
- explicit TSpanArgFormatterImpl(std::span<TVectorElement> v)
- : Span_(v)
- { }
-
- explicit TSpanArgFormatterImpl(const std::vector<TVectorElement>& v)
- : Span_(v.begin(), v.size())
- { }
-
- explicit TSpanArgFormatterImpl(const TVector<TVectorElement>& v)
- : Span_(v.begin(), v.size())
- { }
-
- std::span<const TVectorElement> Span_;
-
- void operator() (size_t index, TStringBuilderBase* builder, TStringBuf format) const
- {
- if (index >= Span_.size()) {
- builder->AppendString(TStringBuf("<missing argument>"));
- } else {
- FormatValue(builder, *(Span_.begin() + index), 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)...);
+void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args)
+{
+ // NB(arkady-e1ppa): StaticFormat performs the
+ // formattability check of the args in a way
+ // that provides more useful information
+ // than a simple static_assert with conjunction.
+ // Additionally, the latter doesn't work properly
+ // for older clang version.
+ static constexpr auto argsChecker = [] {
+ TStaticFormat<TArgs...>::CheckFormattability();
+ return 42;
+ } ();
+ Y_UNUSED(argsChecker);
+
+ NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...);
+ NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter);
}
template <class... TArgs>
-void Format(
- TStringBuilderBase* builder,
- TStringBuf format,
- TArgs&&... args)
-{
- TArgFormatterImpl<0, TArgs...> argFormatter(args...);
- NYT::NDetail::FormatImpl(builder, format, argFormatter);
-}
-
-template <size_t Length, class... TArgs>
-TString Format(
- const char (&format)[Length],
- TArgs&&... args)
+TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args)
{
TStringBuilder builder;
- Format(&builder, format, std::forward<TArgs>(args)...);
+ Format(&builder, fmt, std::forward<TArgs>(args)...);
return builder.Flush();
}
template <class... TArgs>
-TString Format(
- TStringBuf format,
- TArgs&&... args)
+TString Format(TRuntimeFormat fmt, TArgs&&... args)
{
TStringBuilder builder;
- Format(&builder, format, std::forward<TArgs>(args)...);
+ Format(&builder, fmt, std::forward<TArgs>(args)...);
return builder.Flush();
}
+////////////////////////////////////////////////////////////////////////////////
+
template <size_t Length, class TVector>
void FormatVector(
TStringBuilderBase* builder,
- const char (&format)[Length],
+ const char (&fmt)[Length],
const TVector& vec)
{
- TSpanArgFormatterImpl formatter(vec);
- NYT::NDetail::FormatImpl(builder, format, formatter);
+ NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec);
+ NYT::NDetail::RunFormatter(builder, fmt, formatter);
}
template <class TVector>
void FormatVector(
TStringBuilderBase* builder,
- TStringBuf format,
+ TStringBuf fmt,
const TVector& vec)
{
- TSpanArgFormatterImpl formatter(vec);
- NYT::NDetail::FormatImpl(builder, format, formatter);
+ NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec);
+ NYT::NDetail::RunFormatter(builder, fmt, formatter);
}
template <size_t Length, class TVector>
TString FormatVector(
- const char (&format)[Length],
+ const char (&fmt)[Length],
const TVector& vec)
{
TStringBuilder builder;
- FormatVector(&builder, format, vec);
+ FormatVector(&builder, fmt, vec);
return builder.Flush();
}
template <class TVector>
TString FormatVector(
- TStringBuf format,
+ TStringBuf fmt,
const TVector& vec)
{
TStringBuilder builder;
- FormatVector(&builder, format, vec);
+ FormatVector(&builder, fmt, vec);
return builder.Flush();
}
diff --git a/library/cpp/yt/string/format.h b/library/cpp/yt/string/format.h
index 8e0d5f82e5..6463135f89 100644
--- a/library/cpp/yt/string/format.h
+++ b/library/cpp/yt/string/format.h
@@ -54,15 +54,15 @@ namespace NYT {
*
*/
-template <size_t Length, class... TArgs>
-void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args);
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args);
+void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args);
+template <class... TArgs>
+void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args);
-template <size_t Length, class... TArgs>
-TString Format(const char (&format)[Length], TArgs&&... args);
template <class... TArgs>
-TString Format(TStringBuf format, TArgs&&... args);
+TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args);
+template <class... TArgs>
+TString Format(TRuntimeFormat fmt, TArgs&&... args);
////////////////////////////////////////////////////////////////////////////////
@@ -101,6 +101,19 @@ struct TFormatterWrapper
TFormatter Formatter;
};
+// Allows insertion of text conditionally.
+// Usage:
+/*
+ NYT::Format(
+ "Value is %v%v",
+ 42,
+ MakeFormatterWrapper([&] (auto* builder) {
+ If (PossiblyMissingInfo_) {
+ builder->AppendString(", PossiblyMissingInfo: ");
+ FormatValue(builder, PossiblyMissingInfo_, "v");
+ }
+ }));
+ */
template <class TFormatter>
TFormatterWrapper<TFormatter> MakeFormatterWrapper(
TFormatter&& formatter);
@@ -114,7 +127,7 @@ template <class... TArgs>
void FormatValue(
TStringBuilderBase* builder,
const TLazyMultiValueFormatter<TArgs...>& value,
- TStringBuf /*format*/);
+ TStringBuf /*spec*/);
//! A wrapper for a bunch of values that formats them lazily on demand.
/*!
@@ -129,12 +142,19 @@ class TLazyMultiValueFormatter
: private TNonCopyable
{
public:
- TLazyMultiValueFormatter(TStringBuf format, TArgs&&... args);
-
+ TLazyMultiValueFormatter(TStringBuf fmt, TArgs&&... args);
+
+ // NB(arkady-e1ppa): We actually have to
+ // forward declare this method as above
+ // and friend-declare it as specialization
+ // here because clang is stupid and would
+ // treat this friend declartion as a hidden friend
+ // declaration which in turn is treated as a separate symbol
+ // causing linker to not find the actual definition.
friend void FormatValue<>(
TStringBuilderBase* builder,
const TLazyMultiValueFormatter& value,
- TStringBuf /*format*/);
+ TStringBuf /*spec*/);
private:
const TStringBuf Format_;
@@ -142,7 +162,37 @@ private:
};
template <class ... Args>
-auto MakeLazyMultiValueFormatter(TStringBuf format, Args&&... args);
+auto MakeLazyMultiValueFormatter(TStringBuf fmt, Args&&... args);
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ Example:
+
+ FormatVector("One: %v, Two: %v, Three: %v", {1, 2, 3})
+ => "One: 1, Two: 2, Three: 3"
+*/
+template <size_t Length, class TVector>
+void FormatVector(
+ TStringBuilderBase* builder,
+ const char (&fmt)[Length],
+ const TVector& vec);
+
+template <class TVector>
+void FormatVector(
+ TStringBuilderBase* builder,
+ TStringBuf fmt,
+ const TVector& vec);
+
+template <size_t Length, class TVector>
+TString FormatVector(
+ const char (&fmt)[Length],
+ const TVector& vec);
+
+template <class TVector>
+TString FormatVector(
+ TStringBuf fmt,
+ const TVector& vec);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/format_analyser-inl.h b/library/cpp/yt/string/format_analyser-inl.h
index a548a06072..ecc83ebccb 100644
--- a/library/cpp/yt/string/format_analyser-inl.h
+++ b/library/cpp/yt/string/format_analyser-inl.h
@@ -4,11 +4,11 @@
#include "format_analyser.h"
#endif
-namespace NYT {
+#include "format_arg.h"
-////////////////////////////////////////////////////////////////////////////////
+namespace NYT::NDetail {
-namespace NDetail {
+////////////////////////////////////////////////////////////////////////////////
consteval bool Contains(std::string_view sv, char symbol)
{
@@ -93,7 +93,9 @@ consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
template <class TArg>
consteval auto TFormatAnalyser::GetSpecifiers()
{
- static_assert(CFormattable<TArg>, "Your specialization of TFormatArg is broken");
+ if constexpr (!CFormattable<TArg>) {
+ CrashCompilerNotFormattable<TArg>("Your specialization of TFormatArg is broken");
+ }
return TSpecifiers{
.Conversion = std::string_view{
@@ -105,39 +107,6 @@ consteval auto TFormatAnalyser::GetSpecifiers()
};
}
-} // namespace NDetail
-
////////////////////////////////////////////////////////////////////////////////
-template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
-consteval auto TFormatArgBase::ExtendConversion()
-{
- static_assert(CFormattable<TFrom>);
- return AppendArrays<Hot, N, List, &TFrom::ConversionSpecifiers>();
-}
-
-template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
-consteval auto TFormatArgBase::ExtendFlags()
-{
- static_assert(CFormattable<TFrom>);
- return AppendArrays<Hot, N, List, &TFrom::FlagSpecifiers>();
-}
-
-template <bool Hot, size_t N, std::array<char, N> List, auto* From>
-consteval auto TFormatArgBase::AppendArrays()
-{
- auto& from = *From;
- return [] <size_t... I, size_t... J> (
- std::index_sequence<I...>,
- std::index_sequence<J...>) {
- if constexpr (Hot) {
- return std::array{List[J]..., from[I]...};
- } else {
- return std::array{from[I]..., List[J]...};
- }
- } (
- std::make_index_sequence<std::size(from)>(),
- std::make_index_sequence<N>());
- }
-
} // namespace NYT::NDetail
diff --git a/library/cpp/yt/string/format_analyser.h b/library/cpp/yt/string/format_analyser.h
index 4afe3f1ca8..9a05e3eb8c 100644
--- a/library/cpp/yt/string/format_analyser.h
+++ b/library/cpp/yt/string/format_analyser.h
@@ -1,14 +1,14 @@
#pragma once
+#include <util/generic/strbuf.h>
+
#include <array>
#include <string_view>
-namespace NYT {
+namespace NYT::NDetail {
////////////////////////////////////////////////////////////////////////////////
-namespace NDetail {
-
struct TFormatAnalyser
{
public:
@@ -18,6 +18,9 @@ public:
private:
// Non-constexpr function call will terminate compilation.
// Purposefully undefined and non-constexpr/consteval
+ template <class T>
+ static void CrashCompilerNotFormattable(std::string_view /*msg*/)
+ { /*Suppress "internal linkage but undefined" warning*/ }
static void CrashCompilerNotEnoughArguments(std::string_view msg);
static void CrashCompilerTooManyArguments(std::string_view msg);
static void CrashCompilerWrongTermination(std::string_view msg);
@@ -35,67 +38,9 @@ private:
static constexpr char IntroductorySymbol = '%';
};
-} // namespace NDetail
-
////////////////////////////////////////////////////////////////////////////////
-// Base used for flag checks for each type independently.
-// Use it for overrides.
-struct TFormatArgBase
-{
-public:
- // TODO(arkady-e1ppa): Consider more strict formatting rules.
- static constexpr std::array ConversionSpecifiers = {
- 'v', '1', 'c', 's', 'd', 'i', 'o',
- 'x', 'X', 'u', 'f', 'F', 'e', 'E',
- 'a', 'A', 'g', 'G', 'n', 'p'
- };
-
- static constexpr std::array FlagSpecifiers = {
- '-', '+', ' ', '#', '0',
- '1', '2', '3', '4', '5',
- '6', '7', '8', '9',
- '*', '.', 'h', 'l', 'j',
- 'z', 't', 'L', 'q', 'Q'
- };
-
- template <class T>
- static constexpr bool IsSpecifierList = requires (T t) {
- [] <size_t N> (std::array<char, N>) { } (t);
- };
-
- // Hot = |true| adds specifiers to the beggining
- // of a new array.
- template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
- static consteval auto ExtendConversion();
-
- template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
- static consteval auto ExtendFlags();
-
-private:
- template <bool Hot, size_t N, std::array<char, N> List, auto* From>
- static consteval auto AppendArrays();
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <class T>
-struct TFormatArg
- : public TFormatArgBase
-{ };
-
-// Semantic requirement:
-// Said field must be constexpr.
-template <class T>
-concept CFormattable = requires {
- TFormatArg<T>::ConversionSpecifiers;
- requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>;
-
- TFormatArg<T>::FlagSpecifiers;
- requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>;
-};
-
-} // namespace NYT
+} // namespace NYT::NDetail
#define FORMAT_ANALYSER_INL_H_
#include "format_analyser-inl.h"
diff --git a/library/cpp/yt/string/format_arg-inl.h b/library/cpp/yt/string/format_arg-inl.h
new file mode 100644
index 0000000000..82a77a1249
--- /dev/null
+++ b/library/cpp/yt/string/format_arg-inl.h
@@ -0,0 +1,67 @@
+#ifndef FORMAT_ARG_INL_H_
+#error "Direct inclusion of this file is not allowed, include format_arg.h"
+// For the sake of sane code completion.
+#include "format_arg.h"
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class T>
+constexpr std::string_view QualidName()
+{
+ constexpr size_t openBracketOffset = 5;
+ constexpr size_t closeBracketOffset = 1;
+ constexpr std::string_view func = __PRETTY_FUNCTION__;
+ constexpr auto left = std::find(std::begin(func), std::end(func), '[') + openBracketOffset;
+ return std::string_view(left, std::prev(std::end(func), closeBracketOffset));
+}
+
+template <class T>
+constexpr bool IsNYTName()
+{
+ constexpr auto qualidName = QualidName<T>();
+ return qualidName.find("NYT::") == 0;
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
+consteval auto TFormatArgBase::ExtendConversion()
+{
+ static_assert(std::same_as<TFrom, TFormatArgBase> || CFormattable<TFrom>);
+ return AppendArrays<Hot, N, List, &TFormatArg<TFrom>::ConversionSpecifiers>();
+}
+
+template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
+consteval auto TFormatArgBase::ExtendFlags()
+{
+ static_assert(std::same_as<TFrom, TFormatArgBase> || CFormattable<TFrom>);
+ return AppendArrays<Hot, N, List, &TFormatArg<TFrom>::FlagSpecifiers>();
+}
+
+template <bool Hot, size_t N, std::array<char, N> List, auto* From>
+consteval auto TFormatArgBase::AppendArrays()
+{
+ auto& from = *From;
+ return [] <size_t... I, size_t... J> (
+ std::index_sequence<I...>,
+ std::index_sequence<J...>) {
+ if constexpr (Hot) {
+ return std::array{List[J]..., from[I]...};
+ } else {
+ return std::array{from[I]..., List[J]...};
+ }
+ } (
+ std::make_index_sequence<std::size(from)>(),
+ std::make_index_sequence<N>());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/string/format_arg.h b/library/cpp/yt/string/format_arg.h
new file mode 100644
index 0000000000..8c4f354f98
--- /dev/null
+++ b/library/cpp/yt/string/format_arg.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <util/generic/strbuf.h>
+
+#include <array>
+#include <string_view>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStringBuilderBase;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class T>
+constexpr std::string_view QualidName();
+
+template <class T>
+constexpr bool IsNYTName();
+
+template <class T>
+concept CYtName = IsNYTName<T>();
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Base used for flag checks for each type independently.
+// Use it for overrides.
+struct TFormatArgBase
+{
+public:
+ // TODO(arkady-e1ppa): Consider more strict formatting rules.
+ static constexpr std::array ConversionSpecifiers = {
+ 'v', '1', 'c', 's', 'd', 'i', 'o',
+ 'x', 'X', 'u', 'f', 'F', 'e', 'E',
+ 'a', 'A', 'g', 'G', 'n', 'p'
+ };
+
+ static constexpr std::array FlagSpecifiers = {
+ '-', '+', ' ', '#', '0',
+ '1', '2', '3', '4', '5',
+ '6', '7', '8', '9',
+ '*', '.', 'h', 'l', 'j',
+ 'z', 't', 'L', 'q', 'Q'
+ };
+
+ template <class T>
+ static constexpr bool IsSpecifierList = requires (T t) {
+ [] <size_t N> (std::array<char, N>) { } (t);
+ };
+
+ // Hot = |true| adds specifiers to the beggining
+ // of a new array.
+ template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
+ static consteval auto ExtendConversion();
+
+ template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
+ static consteval auto ExtendFlags();
+
+private:
+ template <bool Hot, size_t N, std::array<char, N> List, auto* From>
+ static consteval auto AppendArrays();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TFormatArg
+ : public TFormatArgBase
+{ };
+
+// Ultimate customization point mechanism --- define an overload
+// of FormatValue in order to support formatting of your type.
+// Semantic requirement:
+// Said field must be constexpr.
+template <class T>
+concept CFormattable =
+ requires {
+ TFormatArg<T>::ConversionSpecifiers;
+ requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>;
+
+ TFormatArg<T>::FlagSpecifiers;
+ requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>;
+ } &&
+ requires (
+ TStringBuilderBase* builder,
+ const T& value,
+ TStringBuf spec
+ ) {
+ { FormatValue(builder, value, spec) } -> std::same_as<void>;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define FORMAT_ARG_INL_H_
+#include "format_arg-inl.h"
+#undef FORMAT_ARG_INL_H_
diff --git a/library/cpp/yt/string/format_string-inl.h b/library/cpp/yt/string/format_string-inl.h
new file mode 100644
index 0000000000..5187a2ac6b
--- /dev/null
+++ b/library/cpp/yt/string/format_string-inl.h
@@ -0,0 +1,56 @@
+#ifndef FORMAT_STRING_INL_H_
+#error "Direct inclusion of this file is not allowed, include format_string.h"
+// For the sake of sane code completion.
+#include "format_string.h"
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... TArgs>
+template <class T>
+ requires std::constructible_from<std::string_view, T>
+consteval TBasicStaticFormat<TArgs...>::TBasicStaticFormat(const T& fmt)
+ : Format_(fmt)
+{
+ CheckFormattability();
+#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
+ NDetail::TFormatAnalyser::ValidateFormat<std::remove_cvref_t<TArgs>...>(Format_);
+#endif
+}
+
+template <class... TArgs>
+TStringBuf TBasicStaticFormat<TArgs...>::Get() const noexcept
+{
+ return {Format_};
+}
+
+template <class... TArgs>
+consteval void TBasicStaticFormat<TArgs...>::CheckFormattability()
+{
+#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
+ using TTuple = std::tuple<std::remove_cvref_t<TArgs>...>;
+
+ [] <size_t... Idx> (std::index_sequence<Idx...>) {
+ ([] {
+ if constexpr (!CFormattable<std::tuple_element_t<Idx, TTuple>>) {
+ CrashCompilerClassIsNotFormattable<std::tuple_element_t<Idx, TTuple>>();
+ }
+ } (), ...);
+ } (std::index_sequence_for<TArgs...>());
+#endif
+}
+
+inline TRuntimeFormat::TRuntimeFormat(TStringBuf fmt)
+ : Format_(fmt)
+{ }
+
+inline TStringBuf TRuntimeFormat::Get() const noexcept
+{
+ return Format_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/string/format_string.h b/library/cpp/yt/string/format_string.h
new file mode 100644
index 0000000000..8eeab7d99d
--- /dev/null
+++ b/library/cpp/yt/string/format_string.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "format_analyser.h"
+
+#include <util/generic/strbuf.h>
+
+#if (!__clang__ || __clang_major__ < 16)
+ #define YT_DISABLE_FORMAT_STATIC_ANALYSIS
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This class used to properly bind to
+// string literals and allow compile-time parsing/checking
+// of those. If you need a runtime format, use TRuntimeFormat
+template <class... TArgs>
+class TBasicStaticFormat
+{
+public:
+ // Can be used to perform compile-time check of format.
+ template <class T>
+ requires std::constructible_from<std::string_view, T>
+ consteval TBasicStaticFormat(const T& fmt);
+
+ TStringBuf Get() const noexcept;
+
+ static consteval void CheckFormattability();
+
+private:
+ std::string_view Format_;
+
+ template <class T>
+ static void CrashCompilerClassIsNotFormattable();
+};
+
+// Explicitly create TRuntimeFormat if you wish to
+// use runtime/non-literal value as format.
+class TRuntimeFormat
+{
+public:
+ inline explicit TRuntimeFormat(TStringBuf fmt);
+
+ inline TStringBuf Get() const noexcept;
+
+private:
+ TStringBuf Format_;
+};
+
+// Used to properly infer template arguments if Format.
+template <class... TArgs>
+using TStaticFormat = TBasicStaticFormat<std::type_identity_t<TArgs>...>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define FORMAT_STRING_INL_H_
+#include "format_string-inl.h"
+#undef FORMAT_STRING_INL_H_
diff --git a/library/cpp/yt/string/guid.cpp b/library/cpp/yt/string/guid.cpp
index 6c133a9778..2a781d032d 100644
--- a/library/cpp/yt/string/guid.cpp
+++ b/library/cpp/yt/string/guid.cpp
@@ -11,11 +11,6 @@ void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/
builder->Advance(end - begin);
}
-TString ToString(TGuid guid)
-{
- return ToStringViaBuilder(guid);
-}
-
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT
diff --git a/library/cpp/yt/string/guid.h b/library/cpp/yt/string/guid.h
index 75edbce5db..de16993486 100644
--- a/library/cpp/yt/string/guid.h
+++ b/library/cpp/yt/string/guid.h
@@ -7,7 +7,6 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/);
-TString ToString(TGuid guid);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/string_builder-inl.h b/library/cpp/yt/string/string_builder-inl.h
index 414667954f..683a241c9c 100644
--- a/library/cpp/yt/string/string_builder-inl.h
+++ b/library/cpp/yt/string/string_builder-inl.h
@@ -6,6 +6,8 @@
#include <library/cpp/yt/assert/assert.h>
+#include <util/stream/str.h>
+
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
@@ -81,13 +83,13 @@ inline void TStringBuilderBase::Reset()
template <class... TArgs>
void TStringBuilderBase::AppendFormat(TStringBuf format, TArgs&& ... args)
{
- Format(this, format, std::forward<TArgs>(args)...);
+ Format(this, TRuntimeFormat{format}, std::forward<TArgs>(args)...);
}
template <size_t Length, class... TArgs>
void TStringBuilderBase::AppendFormat(const char (&format)[Length], TArgs&& ... args)
{
- Format(this, format, std::forward<TArgs>(args)...);
+ Format(this, TRuntimeFormat{format}, std::forward<TArgs>(args)...);
}
////////////////////////////////////////////////////////////////////////////////
@@ -131,4 +133,52 @@ TString ToStringViaBuilder(const T& value, TStringBuf spec)
////////////////////////////////////////////////////////////////////////////////
+// Compatibility for users of NYT::ToString(nyt_type).
+template <CFormattable T>
+TString ToString(const T& t)
+{
+ return ToStringViaBuilder(t);
+}
+
+// Sometime we want to implement
+// FormatValue using util's ToString
+// However, if we inside the FormatValue
+// we cannot just call the ToString since
+// in this scope T is already CFormattable
+// and ToString will call the very
+// FormatValue we are implementing,
+// causing an infinite recursion loop.
+// This method is basically a call to
+// util's ToString default implementation.
+template <class T>
+TString ToStringIgnoringFormatValue(const T& t)
+{
+ TString s;
+ ::TStringOutput o(s);
+ o << t;
+ return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
+
+#include <util/string/cast.h>
+
+// util/string/cast.h extension for yt types only
+// TODO(arkady-e1ppa): Abolish ::ToString in
+// favour of either NYT::ToString or
+// automatic formatting wherever it is needed.
+namespace NPrivate {
+
+template <class T>
+ requires (NYT::NDetail::CYtName<T> && NYT::CFormattable<T>)
+struct TToString<T, false>
+{
+ static TString Cvt(const T& t)
+ {
+ return NYT::ToStringViaBuilder(t);
+ }
+};
+
+} // namespace NPrivate
diff --git a/library/cpp/yt/string/string_builder.h b/library/cpp/yt/string/string_builder.h
index 4ac9020177..122e5d19a0 100644
--- a/library/cpp/yt/string/string_builder.h
+++ b/library/cpp/yt/string/string_builder.h
@@ -1,5 +1,7 @@
#pragma once
+#include "format_string.h"
+
#include <util/generic/string.h>
namespace NYT {
@@ -11,10 +13,10 @@ class TStringBuilderBase;
class TStringBuilder;
class TDelimitedStringBuilderWrapper;
-template <size_t Length, class... TArgs>
-void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args);
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args);
+void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args);
+template <class... TArgs>
+void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/unittests/enum_ut.cpp b/library/cpp/yt/string/unittests/enum_ut.cpp
index 2c368e85d4..be5387151a 100644
--- a/library/cpp/yt/string/unittests/enum_ut.cpp
+++ b/library/cpp/yt/string/unittests/enum_ut.cpp
@@ -12,8 +12,8 @@ namespace {
// Some compile-time sanity checks.
DEFINE_ENUM(ESample, (One)(Two));
-static_assert(TFormatTraits<ESample>::HasCustomFormatValue);
-static_assert(TFormatTraits<TEnumIndexedArray<ESample, int>>::HasCustomFormatValue);
+static_assert(CFormattable<ESample>);
+static_assert(CFormattable<TEnumIndexedArray<ESample, int>>);
DEFINE_ENUM(EColor,
(Red)
diff --git a/library/cpp/yt/string/unittests/format_ut.cpp b/library/cpp/yt/string/unittests/format_ut.cpp
index 80162747cc..14b783516c 100644
--- a/library/cpp/yt/string/unittests/format_ut.cpp
+++ b/library/cpp/yt/string/unittests/format_ut.cpp
@@ -4,6 +4,8 @@
#include <library/cpp/yt/small_containers/compact_vector.h>
+#include <util/generic/hash_set.h>
+
#include <limits>
namespace NYT {
@@ -12,31 +14,31 @@ namespace {
////////////////////////////////////////////////////////////////////////////////
// Some compile-time sanity checks.
-static_assert(TFormatTraits<int>::HasCustomFormatValue);
-static_assert(TFormatTraits<double>::HasCustomFormatValue);
-static_assert(TFormatTraits<void*>::HasCustomFormatValue);
-static_assert(TFormatTraits<const char*>::HasCustomFormatValue);
-static_assert(TFormatTraits<TStringBuf>::HasCustomFormatValue);
-static_assert(TFormatTraits<TString>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::span<int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::vector<int>>::HasCustomFormatValue);
+static_assert(CFormattable<int>);
+static_assert(CFormattable<double>);
+static_assert(CFormattable<void*>);
+static_assert(CFormattable<const char*>);
+static_assert(CFormattable<TStringBuf>);
+static_assert(CFormattable<TString>);
+static_assert(CFormattable<std::span<int>>);
+static_assert(CFormattable<std::vector<int>>);
// N.B. TCompactVector<int, 1> is not buildable on Windows
-static_assert(TFormatTraits<TCompactVector<int, 2>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::set<int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::map<int, int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::multimap<int, int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<THashSet<int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<THashMap<int, int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<THashMultiSet<int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::pair<int, int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<std::optional<int>>::HasCustomFormatValue);
-static_assert(TFormatTraits<TDuration>::HasCustomFormatValue);
-static_assert(TFormatTraits<TInstant>::HasCustomFormatValue);
+static_assert(CFormattable<TCompactVector<int, 2>>);
+static_assert(CFormattable<std::set<int>>);
+static_assert(CFormattable<std::map<int, int>>);
+static_assert(CFormattable<std::multimap<int, int>>);
+static_assert(CFormattable<THashSet<int>>);
+static_assert(CFormattable<THashMap<int, int>>);
+static_assert(CFormattable<THashMultiSet<int>>);
+static_assert(CFormattable<std::pair<int, int>>);
+static_assert(CFormattable<std::optional<int>>);
+static_assert(CFormattable<TDuration>);
+static_assert(CFormattable<TInstant>);
struct TUnformattable
{ };
-static_assert(!TFormatTraits<TUnformattable>::HasCustomFormatValue);
+static_assert(!CFormattable<TUnformattable>);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/unittests/guid_ut.cpp b/library/cpp/yt/string/unittests/guid_ut.cpp
index 4b5eebea16..ed02b87391 100644
--- a/library/cpp/yt/string/unittests/guid_ut.cpp
+++ b/library/cpp/yt/string/unittests/guid_ut.cpp
@@ -10,7 +10,7 @@ namespace {
////////////////////////////////////////////////////////////////////////////////
-static_assert(TFormatTraits<TGuid>::HasCustomFormatValue);
+static_assert(CFormattable<TGuid>);
TString CanonicalToString(TGuid value)
{
diff --git a/library/cpp/yt/yson_string/convert.cpp b/library/cpp/yt/yson_string/convert.cpp
index 51a646f0ac..6d78ea6c9d 100644
--- a/library/cpp/yt/yson_string/convert.cpp
+++ b/library/cpp/yt/yson_string/convert.cpp
@@ -234,7 +234,8 @@ TString ParseStringFromYsonString(const TYsonStringBuf& str)
throw TYsonLiteralParseException(ex, "Failed to decode string length");
}
if (length < 0) {
- throw TYsonLiteralParseException(Format("Negative string length ",
+ throw TYsonLiteralParseException(Format(
+ "Negative string length %v",
length));
}
if (static_cast<i64>(input.Avail()) != length) {
diff --git a/library/cpp/yt/yson_string/string.cpp b/library/cpp/yt/yson_string/string.cpp
index 164e450b72..ed2990a51b 100644
--- a/library/cpp/yt/yson_string/string.cpp
+++ b/library/cpp/yt/yson_string/string.cpp
@@ -197,14 +197,14 @@ void TYsonString::Load(IInputStream* s)
////////////////////////////////////////////////////////////////////////////////
-TString ToString(const TYsonString& yson)
+void FormatValue(TStringBuilderBase* builder, const TYsonString& yson, TStringBuf spec)
{
- return yson.ToString();
+ FormatValue(builder, yson.ToString(), spec);
}
-TString ToString(const TYsonStringBuf& yson)
+void FormatValue(TStringBuilderBase* builder, const TYsonStringBuf& yson, TStringBuf spec)
{
- return TString(yson.AsStringBuf());
+ FormatValue(builder, yson.AsStringBuf(), spec);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/yson_string/string.h b/library/cpp/yt/yson_string/string.h
index d9f036569f..e310405ebc 100644
--- a/library/cpp/yt/yson_string/string.h
+++ b/library/cpp/yt/yson_string/string.h
@@ -4,6 +4,8 @@
#include <library/cpp/yt/memory/ref.h>
+#include <library/cpp/yt/string/format.h>
+
#include <variant>
namespace NYT::NYson {
@@ -134,8 +136,8 @@ bool operator != (const TYsonString& lhs, const TYsonStringBuf& rhs);
bool operator != (const TYsonStringBuf& lhs, const TYsonString& rhs);
bool operator != (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs);
-TString ToString(const TYsonString& yson);
-TString ToString(const TYsonStringBuf& yson);
+void FormatValue(TStringBuilderBase* builder, const TYsonString& yson, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TYsonStringBuf& yson, TStringBuf spec);
////////////////////////////////////////////////////////////////////////////////