#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 "guid.h" #include "enum.h" #include "string.h" #include #include #include #include #include #include #include #include #include #include #include #if __cplusplus >= 202302L #include #endif #ifdef __cpp_lib_source_location #include #endif // __cpp_lib_source_location namespace NYT { //////////////////////////////////////////////////////////////////////////////// inline void FormatValue(TStringBuilderBase* builder, const TStringBuilder& value, TStringBuf /*spec*/) { builder->AppendString(value.GetBuffer()); } //////////////////////////////////////////////////////////////////////////////// template TString ToStringViaBuilder(const T& value, TStringBuf spec) { TStringBuilder builder; FormatValue(&builder, value, spec); return builder.Flush(); } //////////////////////////////////////////////////////////////////////////////// // Compatibility for users of NYT::ToString(nyt_type). template 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 TString ToStringIgnoringFormatValue(const T& t) { TString s; ::TStringOutput o(s); o << t; return s; } //////////////////////////////////////////////////////////////////////////////// // 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 void FormatValueViaSprintf( TStringBuilderBase* builder, TValue value, TStringBuf spec, TStringBuf genericSpec); template 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. // Sadly, clang is bugged and thus we must do implementation by hand // if we want to use this concept in class specializations. template constexpr bool CKnownRange = false; template requires requires (T* t) { [] (std::vector*) {} (t); } constexpr bool CKnownRange = true; template constexpr bool CKnownRange> = true; template constexpr bool CKnownRange> = true; template constexpr bool CKnownRange> = true; template requires requires (T* t) { [] (std::set*) {} (t); } constexpr bool CKnownRange = true; template requires requires (T* t) { [] (std::multiset*) {} (t); } constexpr bool CKnownRange = true; template constexpr bool CKnownRange> = true; template constexpr bool CKnownRange> = true; //////////////////////////////////////////////////////////////////////////////// template constexpr bool CKnownKVRange = false; template requires requires (T* t) { [] (std::map*) {} (t); } constexpr bool CKnownKVRange = true; template requires requires (T* t) { [] (std::multimap*) {} (t); } constexpr bool CKnownKVRange = true; template constexpr bool CKnownKVRange> = true; template constexpr bool CKnownKVRange> = true; // TODO(arkady-e1ppa): Uncomment me when // https://github.com/llvm/llvm-project/issues/58534 is shipped. // template // concept CKnownRange = // requires (R r) { [] (std::vector) { } (r); } || // requires (R r) { [] (std::span) { } (r); } || // requires (R r) { [] (TCompactVector) { } (r); } || // requires (R r) { [] (std::set) { } (r); } || // requires (R r) { [] (THashSet) { } (r); } || // requires (R r) { [] (THashMultiSet) { } (r); }; // //////////////////////////////////////////////////////////////////////////////// // template // concept CKnownKVRange = // requires (R r) { [] (std::map) { } (r); } || // requires (R r) { [] (std::multimap) { } (r); } || // requires (R r) { [] (THashMap) { } (r); } || // requires (R r) { [] (THashMultiMap) { } (r); }; } // namespace NDetail //////////////////////////////////////////////////////////////////////////////// template void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits::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 void FormatKeyValueRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits::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 concept CFormattableRange = NDetail::CKnownRange && CFormattable; template concept CFormattableKVRange = NDetail::CKnownKVRange && CFormattable && CFormattable; //////////////////////////////////////////////////////////////////////////////// // Specializations of TFormatArg for ranges template requires CFormattableRange> struct TFormatArg : public TFormatArgBase { using TUnderlying = typename std::remove_cvref_t::value_type; static constexpr auto ConversionSpecifiers = TFormatArg::ConversionSpecifiers; static constexpr auto FlagSpecifiers = TFormatArg::FlagSpecifiers; }; //////////////////////////////////////////////////////////////////////////////// template typename TFormattableView::TBegin TFormattableView::begin() const { return RangeBegin; } template typename TFormattableView::TEnd TFormattableView::end() const { return RangeEnd; } template TFormattableView MakeFormattableView( const TRange& range, TFormatter&& formatter) { return TFormattableView>{range.begin(), range.end(), std::forward(formatter)}; } template TFormattableView MakeShrunkFormattableView( const TRange& range, TFormatter&& formatter, size_t limit) { return TFormattableView>{ range.begin(), range.end(), std::forward(formatter), limit}; } template TFormatterWrapper MakeFormatterWrapper( TFormatter&& formatter) { return TFormatterWrapper{ .Formatter = std::move(formatter) }; } template TLazyMultiValueFormatter::TLazyMultiValueFormatter( TStringBuf format, TArgs&&... args) : Format_(format) , Args_(std::forward(args)...) { } template auto MakeLazyMultiValueFormatter(TStringBuf format, TArgs&&... args) { return TLazyMultiValueFormatter(format, std::forward(args)...); } //////////////////////////////////////////////////////////////////////////////// // Non-container objects. #define XX(valueType, castType, genericSpec) \ inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf spec) \ { \ NYT::NDetail::FormatIntValue(builder, static_cast(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(value), spec, genericSpec); \ } XX(double, double, TStringBuf("lf")) XX(float, float, TStringBuf("f")) #undef XX // Pointer template void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf spec) { NYT::NDetail::FormatPointerValue(builder, static_cast(value), spec); } // TStringBuf inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf spec) { if (!spec) { builder->AppendString(value); return; } // Parse alignment. bool alignLeft = false; const char* current = spec.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("")); 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; bool escape = false; while (current < spec.end()) { switch (*current++) { case 'q': singleQuotes = true; break; case 'Q': doubleQuotes = true; break; case 'h': escape = true; break; } } if (padLeft) { builder->AppendChar(' ', padding); } if (singleQuotes || doubleQuotes || escape) { 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 == '\\') { builder->AppendString("\\\\"); } else if (ch < PrintableASCIILow || ch > PrintableASCIIHigh) { builder->AppendString("\\x"); builder->AppendChar(IntToHexLowercase[static_cast(ch) >> 4]); builder->AppendChar(IntToHexLowercase[static_cast(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 spec) { FormatValue(builder, TStringBuf(value), spec); } // const char* inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf spec) { FormatValue(builder, TStringBuf(value), spec); } template inline void FormatValue(TStringBuilderBase* builder, const char (&value)[N], TStringBuf spec) { FormatValue(builder, TStringBuf(value), spec); } // char* inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf spec) { 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 #ifdef __cpp_lib_source_location // std::source_location inline void FormatValue(TStringBuilderBase* builder, const std::source_location& location, TStringBuf /*spec*/) { if (location.file_name() != nullptr) { builder->AppendFormat( "%v:%v:%v", location.file_name(), location.line(), location.column()); } else { builder->AppendString(""); } } #endif // __cpp_lib_source_location // TSourceLocation inline void FormatValue(TStringBuilderBase* builder, const TSourceLocation& location, TStringBuf /*spec*/) { if (location.GetFileName() != nullptr) { builder->AppendFormat( "%v:%v", location.GetFileName(), location.GetLine()); } else { builder->AppendString(""); } } // std::monostate inline void FormatValue(TStringBuilderBase* builder, const std::monostate&, TStringBuf /*spec*/) { builder->AppendString(TStringBuf("")); } // std::variant template requires (CFormattable && ...) void FormatValue(TStringBuilderBase* builder, const std::variant& variant, TStringBuf spec) { [&] (std::index_sequence) { ([&] { if (variant.index() == Ids) { FormatValue(builder, std::get(variant), spec); return false; } return true; } () && ...); } (std::index_sequence_for()); } // char inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf spec) { FormatValue(builder, TStringBuf(&value, 1), spec); } // bool inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf spec) { // 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; } auto str = lowercase ? (value ? TStringBuf("true") : TStringBuf("false")) : (value ? TStringBuf("True") : TStringBuf("False")); builder->AppendString(str); } // TDuration inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*spec*/) { builder->AppendFormat("%vus", value.MicroSeconds()); } // TInstant inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf spec) { // TODO(babenko): Optimize. FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec); } // Enum template requires (TEnumTraits::IsEnum) void FormatValue(TStringBuilderBase* builder, TEnum value, TStringBuf spec) { // 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 requires (std::is_enum_v && !TEnumTraits::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 void FormatValue(TStringBuilderBase* builder, const TMaybe& value, TStringBuf spec); // std::optional template void FormatValue(TStringBuilderBase* builder, const std::optional& value, TStringBuf spec); // std::pair template void FormatValue(TStringBuilderBase* builder, const std::pair& value, TStringBuf spec); // std::tuple template void FormatValue(TStringBuilderBase* builder, const std::tuple& value, TStringBuf spec); // TEnumIndexedArray template void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray& collection, TStringBuf spec); // One-valued ranges template void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec); // Two-valued ranges template void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec); // FormattableView template void FormatValue( TStringBuilderBase* builder, const TFormattableView& formattableView, TStringBuf spec); // TFormatterWrapper template void FormatValue( TStringBuilderBase* builder, const TFormatterWrapper& wrapper, TStringBuf spec); // TLazyMultiValueFormatter template void FormatValue( TStringBuilderBase* builder, const TLazyMultiValueFormatter& value, TStringBuf /*spec*/); // TMaybe template void FormatValue(TStringBuilderBase* builder, const TMaybe& value, TStringBuf spec) { FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec); } // std::optional: nullopt inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*spec*/) { builder->AppendString(TStringBuf("")); } // std::optional: generic T template void FormatValue(TStringBuilderBase* builder, const std::optional& value, TStringBuf spec) { if (value.has_value()) { FormatValue(builder, *value, spec); } else { FormatValue(builder, std::nullopt, spec); } } // std::pair template void FormatValue(TStringBuilderBase* builder, const std::pair& value, TStringBuf spec) { builder->AppendChar('{'); FormatValue(builder, value.first, spec); builder->AppendString(TStringBuf(", ")); FormatValue(builder, value.second, spec); builder->AppendChar('}'); } // std::tuple template void FormatValue(TStringBuilderBase* builder, const std::tuple& value, TStringBuf spec) { builder->AppendChar('{'); [&] (std::index_sequence) { ([&] { FormatValue(builder, std::get(value), spec); if constexpr (Idx != sizeof...(Ts)) { builder->AppendString(TStringBuf(", ")); } } (), ...); } (std::index_sequence_for()); builder->AppendChar('}'); } // TEnumIndexedArray template void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray& collection, TStringBuf spec) { builder->AppendChar('{'); bool firstItem = true; for (const auto& index : TEnumTraits::GetDomainValues()) { if (!firstItem) { builder->AppendString(DefaultJoinToStringDelimiter); } FormatValue(builder, index, spec); builder->AppendString(": "); FormatValue(builder, collection[index], spec); firstItem = false; } builder->AppendChar('}'); } // One-valued ranges template void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec) { NYT::FormatRange(builder, collection, TSpecBoundFormatter(spec)); } // Two-valued ranges template void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf /*spec*/) { NYT::FormatKeyValueRange(builder, collection, TDefaultFormatter()); } // FormattableView template void FormatValue( TStringBuilderBase* builder, const TFormattableView& formattableView, TStringBuf /*spec*/) { NYT::FormatRange(builder, formattableView, formattableView.Formatter, formattableView.Limit); } // TFormatterWrapper template void FormatValue( TStringBuilderBase* builder, const TFormatterWrapper& wrapper, TStringBuf /*spec*/) { wrapper.Formatter(builder); } // TLazyMultiValueFormatter template void FormatValue( TStringBuilderBase* builder, const TLazyMultiValueFormatter& value, TStringBuf /*spec*/) { std::apply( [&] (TInnerArgs&&... args) { builder->AppendFormat(value.Format_, std::forward(args)...); }, value.Args_); } //////////////////////////////////////////////////////////////////////////////// namespace NDetail { template class TValueFormatter; template class TValueFormatter { public: void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*spec*/) const { builder->AppendString(TStringBuf("")); } }; template class TValueFormatter { public: explicit TValueFormatter(const THead& head, const TTail&... tail) noexcept : Head_(head) , TailFormatter_(tail...) { } void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const { YT_ASSERT(index >= HeadPos); if (index == HeadPos) { FormatValue(builder, Head_, spec); } else { TailFormatter_(index, builder, spec); } } private: const THead& Head_; TValueFormatter TailFormatter_; }; //////////////////////////////////////////////////////////////////////////////// template class TRangeFormatter { public: template requires std::constructible_from, TArgs...> explicit TRangeFormatter(TArgs&&... args) noexcept : Span_(std::forward(args)...) { } void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const { if (index >= Span_.size()) { builder->AppendString(TStringBuf("")); } else { FormatValue(builder, *(Span_.begin() + index), spec); } } private: std::span Span_; }; //////////////////////////////////////////////////////////////////////////////// template concept CFormatter = CInvocable; //////////////////////////////////////////////////////////////////////////////// template void RunFormatter( TStringBuilderBase* builder, TStringBuf format, const TFormatter& formatter) { size_t argIndex = 0; auto current = std::begin(format); auto end = std::end(format); while (true) { // Scan verbatim part until stop symbol. auto verbatimBegin = current; auto verbatimEnd = std::find(current, end, IntroductorySymbol); // 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 == end) { break; } YT_ASSERT(*current == IntroductorySymbol); ++current; if (*current == IntroductorySymbol) { // Verbatim %. builder->AppendChar(IntroductorySymbol); ++current; continue; } // 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; } // Handle end of format string. if (argFormatEnd != 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('"'); } formatter(argIndex++, builder, argFormat); if (singleQuotes) { builder->AppendChar('\''); } if (doubleQuotes) { builder->AppendChar('"'); } } current = argFormatEnd; } } } // namespace NDetail //////////////////////////////////////////////////////////////////////////////// template void Format(TStringBuilderBase* builder, TFormatString format, TArgs&&... args) { // NB(arkady-e1ppa): "if constexpr" is done in order to prevent // compiler from emitting "No matching function to call" // when arguments are not formattable. // Compiler would crash in TFormatString ctor // anyway (e.g. program would not compile) but // for some reason it does look ahead and emits // a second error. if constexpr ((CFormattable && ...)) { NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...); NYT::NDetail::RunFormatter(builder, format.Get(), formatter); } } template TString Format(TFormatString format, TArgs&&... args) { TStringBuilder builder; Format(&builder, format, std::forward(args)...); return builder.Flush(); } //////////////////////////////////////////////////////////////////////////////// template void FormatVector( TStringBuilderBase* builder, const char (&format)[Length], const TVector& vec) { NYT::NDetail::TRangeFormatter formatter(vec); NYT::NDetail::RunFormatter(builder, format, formatter); } template void FormatVector( TStringBuilderBase* builder, TStringBuf format, const TVector& vec) { NYT::NDetail::TRangeFormatter formatter(vec); NYT::NDetail::RunFormatter(builder, format, formatter); } template TString FormatVector( const char (&format)[Length], const TVector& vec) { TStringBuilder builder; FormatVector(&builder, format, vec); return builder.Flush(); } template TString FormatVector( TStringBuf format, const TVector& vec) { TStringBuilder builder; FormatVector(&builder, format, vec); return builder.Flush(); } //////////////////////////////////////////////////////////////////////////////// } // namespace NYT #include // util/string/cast.h extension for yt and std types only // TODO(arkady-e1ppa): Abolish ::ToString in // favour of either NYT::ToString or // automatic formatting wherever it is needed. namespace NPrivate { //////////////////////////////////////////////////////////////////////////////// template requires ( (NYT::NDetail::IsNYTName() || NYT::NDetail::IsStdName()) && NYT::CFormattable) struct TToString { static TString Cvt(const T& t) { return NYT::ToStringViaBuilder(t); } }; //////////////////////////////////////////////////////////////////////////////// } // namespace NPrivate