#pragma once #include "CoreTypes.h" #include "TypeTraits/TypeTraits.h" #include "Templates/Utility.h" #include "Ranges/Utility.h" #include "Ranges/View.h" #include "Algorithms/Basic.h" #include "Containers/StaticArray.h" #include "Numerics/Math.h" #include "Strings/Char.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) /** A concept specifies a type is a format description string context. */ template concept CFormatStringContext = CInputRange && CCharType && CSameAs, CharType> && requires(T& Context, TRangeIterator Iter, size_t Index) { /** Set the iterator of the context. */ Context.AdvanceTo(MoveTemp(Iter)); /** @return The next automatic index. */ { Context.GetNextIndex() } -> CSameAs; /** @return true if the manual index is valid. */ { Context.CheckIndex(Index) } -> CBooleanTestable; }; /** A concept specifies a type is a format output context. */ template concept CFormatObjectContext = COutputRange && CCharType && requires(T& Context, TRangeIterator Iter, size_t Index) { /** Set the iterator of the context. */ Context.AdvanceTo(MoveTemp(Iter)); /** Visit the format argument by index, and the argument is always like const&. */ { Context.Visit([](const auto&) { return 0; }, Index) } -> CIntegral; /** Visit the format argument by index, and the argument is always like const&. */ { Context.template Visit([](const auto&) { return 0; }, Index) } -> CIntegral; }; /** * A template class that defines the formatting rules for a specific type. * * @tparam T - The type of object being formatted. * @tparam CharType - The character type of the formatting target. */ template requires (CSameAs, T>) class TFormatter { public: static_assert(sizeof(T) == -1, "The type is not formattable."); FORCEINLINE constexpr TFormatter() = delete; FORCEINLINE constexpr TFormatter(const TFormatter&) = delete; FORCEINLINE constexpr TFormatter(TFormatter&&) = delete; FORCEINLINE constexpr TFormatter& operator=(const TFormatter&) = delete; FORCEINLINE constexpr TFormatter& operator=(TFormatter&&) = delete; /** * Parses the format description string from the context. * Assert that the format description string is valid. * * @return The iterator that points to the first unmatched character. */ template CTX> FORCEINLINE constexpr TRangeIterator Parse(CTX& Context); /** * Formats the object and writes the result to the context. * Do not assert that the output range is always large enough, and return directly if it is insufficient. * Specify, unlike visiting arguments from the context which is always like const&, * the object argument is a forwarding reference. * * @return The iterator that points to the next position of the output. */ template CTX> requires (CSameAs, U>) FORCEINLINE constexpr TRangeIterator Format(U&& Object, CTX& Context) const; }; NAMESPACE_PRIVATE_BEGIN template class TFormatStringContext { public: FORCEINLINE constexpr TFormatStringContext(I InFirst, S InLast) : First(MoveTemp(InFirst)), Last(InLast), AutomaticIndex(0) { } NODISCARD FORCEINLINE constexpr I Begin() requires (!CCopyable) { return MoveTemp(First); } NODISCARD FORCEINLINE constexpr I Begin() const requires ( CCopyable) { return First; } NODISCARD FORCEINLINE constexpr S End() const { return Last; } NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedSentinelFor) { return Last - First; } NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return First == Last; } FORCEINLINE constexpr void AdvanceTo(I Iter) { First = MoveTemp(Iter); } NODISCARD FORCEINLINE constexpr size_t GetNextIndex() { bool bIsValid = AutomaticIndex < sizeof...(Ts) && AutomaticIndex != INDEX_NONE; checkf(bIsValid, TEXT("Illegal automatic indexing. Already entered manual indexing mode.")); if (!bIsValid) return INDEX_NONE; return AutomaticIndex++; } NODISCARD FORCEINLINE constexpr bool CheckIndex(size_t Index) { bool bIsValid = AutomaticIndex == 0 || AutomaticIndex == INDEX_NONE; checkf(bIsValid, TEXT("Illegal manual indexing. Already entered automatic indexing mode.")); if (!bIsValid) return false; AutomaticIndex = INDEX_NONE; return Index < sizeof...(Ts); } private: NO_UNIQUE_ADDRESS I First; NO_UNIQUE_ADDRESS S Last; size_t AutomaticIndex = 0; }; static_assert(CFormatStringContext, ISentinelFor>>>); template class TFormatObjectContext { public: FORCEINLINE constexpr TFormatObjectContext(I InFirst, S InLast, Ts&... Args) : First(MoveTemp(InFirst)), Last(InLast), ArgsTuple(Args...) { } NODISCARD FORCEINLINE constexpr I Begin() requires (!CCopyable) { return MoveTemp(First); } NODISCARD FORCEINLINE constexpr I Begin() const requires ( CCopyable) { return First; } NODISCARD FORCEINLINE constexpr S End() const { return Last; } NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedSentinelFor) { return Last - First; } NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return First == Last; } FORCEINLINE constexpr void AdvanceTo(I Iter) { First = MoveTemp(Iter); } template requires (((sizeof...(Ts) >= 1) && ... && CInvocable) && CCommonReference...>) FORCEINLINE constexpr decltype(auto) Visit(F&& Func, size_t Index) const { return ArgsTuple.Visit(Forward(Func), Index); } template requires ((sizeof...(Ts) >= 1) && ... && CInvocableResult) FORCEINLINE constexpr Ret Visit(F&& Func, size_t Index) const { return ArgsTuple.template Visit(Forward(Func), Index); } private: NO_UNIQUE_ADDRESS I First; NO_UNIQUE_ADDRESS S Last; TTuple ArgsTuple; }; static_assert(CFormatObjectContext, ISentinelFor>, int>>); NAMESPACE_PRIVATE_END /** A concept specifies a type is formattable by the 'Algorithms::Format()'. */ template concept CFormattable = CCharType && CSemiregular, CharType>> && requires(TFormatter, CharType>& Formatter, T& Object, NAMESPACE_PRIVATE::TFormatStringContext< IInputIterator, ISentinelFor< IInputIterator>, T> FormatStringContext, NAMESPACE_PRIVATE::TFormatObjectContext, ISentinelFor>, T> FormatObjectContext) { { Formatter.Parse ( FormatStringContext) } -> CSameAs< IInputIterator>; { Formatter.Format(Object, FormatObjectContext) } -> CSameAs>; }; NAMESPACE_BEGIN(Algorithms) /** * Formats the objects and writes the result to the output range. * Assert that the format description string is valid. * If the output range is insufficient, return directly without asserting. * * @param Output - The output range to write the result. * @param Fmt - The format description string. * @param Args - The objects to format. * * @return The iterator that points to the next position of the output. */ template > R2, CFormattable>... Ts> requires (CBorrowedRange) FORCEINLINE constexpr TRangeIterator Format(R2&& Output, R1&& Fmt, Ts&&... Args) { if constexpr (CSizedRange) { checkf(Algorithms::Distance(Fmt) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Fmt).")); } if constexpr (CSizedRange) { checkf(Algorithms::Distance(Output) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Output).")); } using FCharType = TRangeElement; using FCharTraits = TChar; using FFormatStringContext = NAMESPACE_PRIVATE::TFormatStringContext, TRangeSentinel, Ts...>; using FFormatObjectContext = NAMESPACE_PRIVATE::TFormatObjectContext, TRangeSentinel, Ts...>; FFormatStringContext FormatStringContext(Ranges::Begin(Fmt ), Ranges::End(Fmt )); FFormatObjectContext FormatObjectContext(Ranges::Begin(Output), Ranges::End(Output), Args...); auto FmtIter = Ranges::Begin(FormatStringContext); auto FmtSent = Ranges::End (FormatStringContext); auto OutIter = Ranges::Begin(FormatObjectContext); auto OutSent = Ranges::End (FormatObjectContext); // If the output range is insufficient. if (OutIter == OutSent) UNLIKELY return OutIter; TTuple, FCharType>...> Formatters; // For each character in the format string. for (FCharType Char; FmtIter != FmtSent; ++FmtIter) { Char = *FmtIter; // If the character may be a replacement field. if (Char == LITERAL(FCharType, '{')) { if (++FmtIter == FmtSent) UNLIKELY { checkf(false, TEXT("Illegal format string. Unmatched '{' in format string.")); break; } Char = *FmtIter; // If the character just an escaped '{'. if (Char == LITERAL(FCharType, '{')) { if (OutIter == OutSent) UNLIKELY return OutIter; *OutIter++ = LITERAL(FCharType, '{'); continue; } // If available replacement fields. if constexpr (sizeof...(Ts) >= 1) { size_t Index; // If the replacement field has a manual index. if (Char != LITERAL(FCharType, ':') && Char != LITERAL(FCharType, '}')) { Index = 0; bool bIsValid = true; do { const uint Digit = FCharTraits::ToDigit(Char); if (Digit > 10) bIsValid = false; Index = Index * 10 + Digit; if (++FmtIter == FmtSent) UNLIKELY break; Char = *FmtIter; } while (Char != LITERAL(FCharType, ':') && Char != LITERAL(FCharType, '}')); // If the index string contains illegal characters or the index is out of range. if (!bIsValid || !FormatStringContext.CheckIndex(Index)) UNLIKELY { checkf(false, TEXT("Illegal index. Please check the replacement field.")); break; } } // If the replacement field need automatic indexing. else { Index = FormatStringContext.GetNextIndex(); if (Index == INDEX_NONE) { checkf(false, TEXT("Illegal index. Please check the replacement field.")); break; } } // Jump over the ':' character. if (Char == LITERAL(FCharType, ':')) ++FmtIter; FormatStringContext.AdvanceTo(MoveTemp(FmtIter)); // Parse the format description string. FmtIter = Formatters.Visit([&FormatStringContext](auto& Formatter) -> decltype(FmtIter) { return Formatter.Parse(FormatStringContext); }, Index); if (FmtIter == FmtSent || *FmtIter != LITERAL(FCharType, '}')) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); break; } FormatObjectContext.AdvanceTo(MoveTemp(OutIter)); auto FormatHandler = [&](TIndexSequence) { TTuple...> Visitor; return Visitor.Visit([&](TConstant) { check(ConstantIndex == Index); return Formatters.template GetValue().Format(ForwardAsTuple(Forward(Args)...).template GetValue(), FormatObjectContext); } , Index); }; // Format the object and write the result to the context. OutIter = FormatHandler(TIndexSequenceFor()); } else { checkf(false, TEXT("Illegal index. Please check the replacement field.")); break; } } // If the character just an escaped '}'. else if (Char == LITERAL(FCharType, '}')) { // Confirm the character is an escaped '}'. if (++FmtIter != FmtSent && *FmtIter == LITERAL(FCharType, '}')) { if (OutIter == OutSent) UNLIKELY return OutIter; *OutIter++ = LITERAL(FCharType, '}'); continue; } checkf(false, TEXT("Illegal format string. Missing '{' in format string.")); break; } // If the output range is insufficient. else if (OutIter == OutSent) UNLIKELY return OutIter; // If the character is not a replacement field. else *OutIter++ = Char; } return OutIter; } /** * Formats the objects and writes the result to the output range. * Assert that the format description string is valid. * If the output range is insufficient, return directly without asserting. * * @param OutputFirst - The iterator of the output range to write the result. * @param OutputLast - The sentinel of the output range to write the result. * @param FmtFirst - The iterator of the format description string. * @param FmtLast - The sentinel of the format description string. * @param Args - The objects to format. * * @return The iterator that points to the next position of the output. */ template S1, COutputIterator> I2, CSentinelFor S2, CFormattable>... Ts> FORCEINLINE constexpr I2 Format(I2 OutputFirst, S2 OutputLast, I1 FmtFirst, S1 FmtLast, Ts&&... Args) { if constexpr (CSizedSentinelFor) { checkf(FmtFirst - FmtLast <= 0, TEXT("Illegal range iterator. Please check HaystackFirst <= HaystackLast.")); } if constexpr (CSizedSentinelFor) { checkf(OutputFirst - OutputLast <= 0, TEXT("Illegal range iterator. Please check NeedleFirst <= NeedleLast.")); } return Algorithms::Format(Ranges::View(MoveTemp(OutputFirst), OutputLast), Ranges::View(MoveTemp(FmtFirst), FmtLast), Forward(Args)...); } NAMESPACE_END(Algorithms) /** * A formatter the null-terminated string. * * The syntax of format specifications is: * * [Fill And Align] [Width] [Precision] [Type] * * 1. The fill and align part: * * [Fill Character] * * i. Fill Character: The character is used to fill width of the object. It is optional and cannot be '{' or '}'. * It should be representable as a single unicode otherwise it is undefined behavior. * * ii. Align Option: The character is used to indicate the direction of alignment. * * - '<': Align the formatted argument to the left of the available space * by inserting n fill characters after the formatted argument. * - '^': Align the formatted argument to the center of the available space * by inserting n fill characters around the formatted argument. * If cannot absolute centering, offset to the left. * - '>': Align the formatted argument ro the right of the available space * by inserting n fill characters before the formatted argument. * * 2. The width part: * * - 'N': The number is used to specify the minimum field width of the object. * N should be an unsigned non-zero decimal number. * - '{N}': Dynamically determine the minimum field width of the object. * N should be a valid index of the format integer argument. * N is optional, and the default value is automatic indexing. * * 3. The precision part: * * - '.N': The number is used to specify the maximum field width of the object. * N should be an unsigned non-zero decimal number. * - '.{N}': Dynamically determine the maximum field width of the object. * N should be a valid index of the format integer argument. * N is optional, and the default value is automatic indexing. * * 4. The type indicators part: * * - none: Indicates the as-is formatting. * - 'S': Indicates the as-is formatting. * - '!': Indicates uppercase formatting. * - 's': Indicates lowercase formatting. * - '?': Indicates escape formatting. */ template class TFormatter { private: using FCharType = T; using FCharTraits = TChar; using FFillCharacter = TStaticArray; public: template CTX> constexpr TRangeIterator Parse(CTX& Context) { auto Iter = Ranges::Begin(Context); auto Sent = Ranges::End (Context); // Set the default values. { FillUnitLength = 1; FillCharacter[0] = LITERAL(FCharType, ' '); AlignOption = LITERAL(FCharType, '<'); MinFieldWidth = 0; MaxFieldWidth = -1; bDynamicMin = false; bDynamicMax = false; bLowercase = false; bUppercase = false; bEscape = false; } // If the format description string is empty. if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; FCharType Char = *Iter; ++Iter; // Try to parse the fill and align part. // This code assumes that the format string does not contain multi-unit characters, except for fill character. // If the fill character is multi-unit. if (!FCharTraits::IsValid(Char)) { FillUnitLength = 1; FillCharacter[0] = Char; while (true) { if (Iter == Sent) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } Char = *Iter; ++Iter; // If the fill character ends. if (FillUnitLength == FCharTraits::MaxCodeUnitLength || FCharTraits::IsValid(Char)) break; FillCharacter[FillUnitLength++] = Char; } if (Char != LITERAL(FCharType, '<') && Char != LITERAL(FCharType, '^') && Char != LITERAL(FCharType, '>')) UNLIKELY { checkf(false, TEXT("Illegal format string. The fill character is not representable as a single unicode.")); return Iter; } AlignOption = Char; if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } // If the fill character is single-unit. else do { if (Iter == Sent) break; if (*Iter != LITERAL(FCharType, '<') && *Iter != LITERAL(FCharType, '^') && *Iter != LITERAL(FCharType, '>')) break; FillUnitLength = 1; FillCharacter[0] = Char; Char = *Iter; ++Iter; AlignOption = Char; if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } while (false); // Try to parse the width part. { if (Char == LITERAL(FCharType, '{')) { bDynamicMin = true; MinFieldWidth = INDEX_NONE; if (Iter == Sent) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } Char = *Iter; ++Iter; } if ((bDynamicMin || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char)) { MinFieldWidth = FCharTraits::ToDigit(Char); while (true) { if (Iter == Sent) { checkf(!bDynamicMin, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } if (!bDynamicMin && *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; const uint Digit = FCharTraits::ToDigit(Char); if (Digit > 10) break; MinFieldWidth = MinFieldWidth * 10 + Digit; } } if (bDynamicMin) { if (Char != LITERAL(FCharType, '}')) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } do { // Try to automatic indexing. if (MinFieldWidth == INDEX_NONE) { MinFieldWidth = Context.GetNextIndex(); if (MinFieldWidth == INDEX_NONE) UNLIKELY { checkf(false, TEXT("Illegal index. Please check the field width.")); } else break; } // Try to manual indexing. else if (!Context.CheckIndex(MinFieldWidth)) UNLIKELY { checkf(false, TEXT("Illegal index. Please check the field width.")); } else break; bDynamicMin = false; MinFieldWidth = 0; } while (false); if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } } // Try to parse the precision part. if (Char == LITERAL(FCharType, '.')) { if (Iter == Sent) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing precision in format string.")); return Iter; } Char = *Iter; ++Iter; if (Char == LITERAL(FCharType, '{')) { bDynamicMax = true; MaxFieldWidth = INDEX_NONE; if (Iter == Sent) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } Char = *Iter; ++Iter; } if ((bDynamicMax || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char)) { MaxFieldWidth = FCharTraits::ToDigit(Char); while (true) { if (Iter == Sent) { checkf(!bDynamicMax, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } if (!bDynamicMax && *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; const uint Digit = FCharTraits::ToDigit(Char); if (Digit > 10) break; MaxFieldWidth = MaxFieldWidth * 10 + Digit; } } else if (!bDynamicMax) { checkf(false, TEXT("Illegal format string. Missing precision in format string.")); return Iter; } if (bDynamicMax) { if (Char != LITERAL(FCharType, '}')) UNLIKELY { checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } do { // Try to automatic indexing. if (MaxFieldWidth == INDEX_NONE) { MaxFieldWidth = Context.GetNextIndex(); if (MaxFieldWidth == INDEX_NONE) UNLIKELY { checkf(false, TEXT("Illegal index. Please check the precision.")); } else break; } // Try to manual indexing. else if (!Context.CheckIndex(MaxFieldWidth)) UNLIKELY { checkf(false, TEXT("Illegal index. Please check the precision.")); } else break; bDynamicMax = false; MaxFieldWidth = -1; } while (false); if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } } // Try to parse the type indicators part. if (Char == LITERAL(FCharType, 'S')) { if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } else if (Char == LITERAL(FCharType, '!')) { bUppercase = true; if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } else if (Char == LITERAL(FCharType, 's')) { bLowercase = true; if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } else if (Char == LITERAL(FCharType, '?')) { bEscape = true; if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; } checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); return Iter; } template CTX> constexpr TRangeIterator Format(const FCharType* Object, CTX& Context) const { auto Iter = Ranges::Begin(Context); auto Sent = Ranges::End (Context); size_t MinDynamicField = MinFieldWidth; size_t MaxDynamicField = MaxFieldWidth; if (bDynamicMin) MinDynamicField = Context.Visit([](U&& Value) -> size_t { if constexpr (CIntegral) { checkf(Value > 0, TEXT("Illegal format argument. The dynamic width argument must be a unsigned non-zero number.")); return Math::Max(Value, 1); } else { checkf(false, TEXT("Illegal format argument. The dynamic width argument must be an integral.")); return 0; } } , MinFieldWidth); if (bDynamicMax) MaxDynamicField = Context.Visit([](U&& Value) -> size_t { if constexpr (CIntegral) { checkf(Value > 0, TEXT("Illegal format argument. The dynamic precision argument must be a unsigned non-zero number.")); return Math::Max(Value, 1); } else { checkf(false, TEXT("Illegal format argument. The dynamic precision argument must be an integral.")); return 0; } } , MaxFieldWidth); size_t LeftPadding = 0; size_t RightPadding = 0; // Estimate the field width. if (MinDynamicField != 0) { // If escape formatting is enabled, add quotes characters. size_t FieldWidth = bEscape ? 2 : 0; for (const FCharType* Ptr = Object; *Ptr != LITERAL(FCharType, '\0'); ++Ptr) { if (bEscape) { switch (const FCharType Char = *Ptr) { case LITERAL(FCharType, '\"'): FieldWidth += 2; break; case LITERAL(FCharType, '\\'): FieldWidth += 2; break; case LITERAL(FCharType, '\a'): FieldWidth += 2; break; case LITERAL(FCharType, '\b'): FieldWidth += 2; break; case LITERAL(FCharType, '\f'): FieldWidth += 2; break; case LITERAL(FCharType, '\n'): FieldWidth += 2; break; case LITERAL(FCharType, '\r'): FieldWidth += 2; break; case LITERAL(FCharType, '\t'): FieldWidth += 2; break; case LITERAL(FCharType, '\v'): FieldWidth += 2; break; default: { // Use '\x00' format for other non-printable characters. if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char)) { FieldWidth += 2 + sizeof(FCharType) * 2; } else ++FieldWidth; } } } else ++FieldWidth; } const size_t PaddingWidth = MinDynamicField - Math::Min(FieldWidth, MinDynamicField, MaxDynamicField); if (AlignOption == LITERAL(FCharType, '<')) RightPadding = PaddingWidth; else if (AlignOption == LITERAL(FCharType, '>')) LeftPadding = PaddingWidth; else if (AlignOption == LITERAL(FCharType, '^')) { LeftPadding = Math::DivAndFloor(PaddingWidth, 2); RightPadding = PaddingWidth - LeftPadding; } } // Write the left padding. for (size_t Index = 0; Index != LeftPadding; ++Index) { for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex) { if (Iter == Sent) UNLIKELY return Iter; *Iter++ = FillCharacter[Jndex]; } } // Write the left quote. if (bEscape) { if (Iter == Sent) UNLIKELY return Iter; *Iter++ = LITERAL(FCharType, '\"'); } const FCharType* Ptr = Object - 1; // Write the object, include escaped quotes in the counter. for (size_t Index = bEscape ? 1 : 0; Index != MaxDynamicField; ++Index) { FCharType Char = *++Ptr; if (Char == LITERAL(FCharType, '\0')) break; if (Iter == Sent) UNLIKELY return Iter; // Convert the character case. if (bLowercase) Char = FCharTraits::ToLower(Char); else if (bUppercase) Char = FCharTraits::ToUpper(Char); if (bEscape) { switch (Char) { case LITERAL(FCharType, '\"'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\"'); break; case LITERAL(FCharType, '\\'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\\'); break; case LITERAL(FCharType, '\a'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'a'); break; case LITERAL(FCharType, '\b'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'b'); break; case LITERAL(FCharType, '\f'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'f'); break; case LITERAL(FCharType, '\n'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'n'); break; case LITERAL(FCharType, '\r'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'r'); break; case LITERAL(FCharType, '\t'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 't'); break; case LITERAL(FCharType, '\v'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'v'); break; default: { // Use '\x00' format for other non-printable characters. if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char)) { *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'x' ); using FUnsignedT = TMakeUnsigned; constexpr size_t DigitNum = sizeof(FCharType) * 2; FUnsignedT IntValue = static_cast(Char); FCharType Buffer[DigitNum]; for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex) { Buffer[DigitNum - Jndex - 1] = FCharTraits::FromDigit(IntValue & 0xF); IntValue >>= 4; } check(IntValue == 0); for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex) { if (Iter == Sent) UNLIKELY return Iter; *Iter++ = Buffer[Jndex]; } } else *Iter++ = Char; } } } else *Iter++ = Char; } // Write the right quote, if the field width is enough. if (bEscape && *Ptr == LITERAL(FCharType, '\0')) { if (Iter == Sent) UNLIKELY return Iter; *Iter++ = LITERAL(FCharType, '\"'); } // Write the right padding. for (size_t Index = 0; Index != RightPadding; ++Index) { for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex) { if (Iter == Sent) UNLIKELY return Iter; *Iter++ = FillCharacter[Jndex]; } } return Iter; } private: size_t FillUnitLength = 1; FFillCharacter FillCharacter = { LITERAL(FCharType, ' ') }; FCharType AlignOption = LITERAL(FCharType, '<'); size_t MinFieldWidth = 0; size_t MaxFieldWidth = -1; bool bDynamicMin = false; bool bDynamicMax = false; bool bLowercase = false; bool bUppercase = false; bool bEscape = false; }; template class TFormatter : public TFormatter { }; template class TFormatter< T[N], T> : public TFormatter { }; template class TFormatter : public TFormatter { }; static_assert(CFormattable< char*>); static_assert(CFormattable); static_assert(CFormattable< char[256]>); static_assert(CFormattable); NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END