#pragma once // NOTE: This file is not intended to be included directly, it is included by 'String/String.h'. #include "Templates/Tuple.h" #include "Templates/Utility.h" #include "TypeTraits/TypeTraits.h" #include <cmath> #include <limits> #include <charconv> #pragma warning(push) #pragma warning(disable : 4146 4244) NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) // TODO: Refactor the conversion tool by more elegant way. // The conversion tool uses a string to describe the object format. // // The format string consists of the following parts: // // - A pair of braces: The object placeholder. // - A escaped brace: The brace is formatted or parsed as-is // - A general character: The character is formatted or parsed as-is. // - A space character: The character is formatted as-is or all leading space characters are consumed when parsing. // // About the object placeholder: // // Use the ':' character to separate the different layers of object placeholders, for a normal object he has only two layers, // for a string or a character he may have three layers to represent the format of the escape character, // for a container he may have many layers to represent the format of the elements. // // The first level is the object index. // The other levels are the object format, which is used to format or parse the object. // // The object format contains a common optional fill-and-align consisting of the following parts: // // i. A fill character: The character is used to fill width of the object. It is optional. // It should be representable as a single unicode otherwise it is undefined behavior. // ii. A alignment option: The character is used to indicate the direction of alignment. It is optional if it does not create ambiguity. // '<' for left, '>' for right, '^' for center. If cannot absolute centering, offset to the left. // iii. A width number: The number is used to specify the width of the object. // It should be a decimal number without any sign. // // The width is limits the minimum number of characters in formatting and the maximum number of characters in parsing. // The fill character is treated as a space character in parsing. // // After the fill-and-align, the object format contains type-specific options. // // Specially, only strings and characters that agree with the main character type are considered string values and character values. // // For string values: // // 1. The type indicators part: // // - none: Indicates the as-is formatting. // - 'S': Indicates uppercase formatting if case indicators is '!', otherwise as-is formatting. // - 's': Indicates lowercase formatting if case indicators is '!', otherwise as-is formatting. // // 2. The case indicators part: // // - none: Indicates the as-is formatting. // - '!': Indicates the case as the type indicators case. // // 3. The escape indicators part: // // - none: Indicates the as-is formatting. // - '?': Indicates the escape formatting. // // For character values: // // 1. The type indicators part: // // - none: Indicates the as-is formatting. // - 'C': Indicates uppercase formatting if case indicators is '!', otherwise as-is formatting. // - 'c': Indicates lowercase formatting if case indicators is '!', otherwise as-is formatting. // - 's' or 'S': Indicates that characters should be treated as strings. // See the string values section for additional formatting. // - 'B', 'D', 'O', 'X': Indicates that characters should be treated as integer values. // See the integer values section for additional formatting. // // 2. The case indicators part: // // - none: Indicates the as-is formatting. // - '!': Indicates the case as the type indicators case. // // 3. The escape indicators part: // // - none: Indicates the as-is formatting. // - '?': Indicates the escape formatting. // // For boolean values: // // 1. The type indicators part: // // - none or 'S': Indicates that boolean value should be treated as string 'True' or 'False'. // See the string values section for additional formatting. // - 'C': Indicates that boolean value should be treated as character 'T' or 'F'. // See the character values section for additional formatting. // - 'B', 'D', 'O', 'X': Indicates that boolean value should be treated as integer 1 or 0. // See the integer values section for additional formatting. // // For integer values: // // 1. The positive indicators part: // // - none or '-': Indicates hide the sign of the positive number. // - '+': Indicates show the '+' of the positive number. // - ' ': Indicates show the ' ' of the positive number. // // 2. The prefix indicators part: // // - none: Indicates hide the prefix of the number. // - '#': Indicates show the prefix of the number. Indicates auto-detect the base in parsing. // // 3. The '0' padded width indicators part: // // - none: Indicates that the '0' padded width is 0. // - '0N': Indicates that the '0' padded width is N. // // 4. The base indicators part: // // - none or '_0': Indicates decimal in formatting. Indicates auto-detect the base in parsing. // - '_N': Indicates that the base is N, between [2, 36]. // // 5. The type indicators part: // // - none or 'D': Indicates decimal formatting. Same as '_10I'. // - 'B': Indicates binary formatting. Same as '_2I'. // - 'O': Indicates octal formatting. Same as '_8I'. // - 'X': Indicates hexadecimal formatting. Same as '_16I'. // - 'I': Indicates specified formatting by base indicators. // // For floating-point values: // // 1. The positive indicators part: // // - none or '-': Indicates hide the sign of the positive number. // - '+': Indicates show the '+' of the positive number. // - ' ': Indicates show the ' ' of the positive number. // // 2. The prefix indicators part: // // - none: Indicates hide the prefix of the number. // - '#': Indicates show the prefix of the number. Indicates auto-detect the hex scientific in parsing. // // 3. The precision indicators part: // // - none: Indicates six decimal for fixed-point in formatting. Indicates auto-detect the precision in parsing. // - '.N': Indicates that the precision is N, It should be a decimal number without any sign. // // 4. The type indicators part: // // - none or 'F': Indicates fixed-point formatting. // - 'G': Indicates general formatting. // - 'E': Indicates scientific formatting. // - 'A': Indicates hex scientific formatting. // // For pointer values: // // 1. The type indicators part: // // - none or 'P': The pointer value is formatted as hexadecimal with prefix and fill-and-align. // Same as '#X'. The default width depends on the platform. // // For tuple values: // // 1. The type indicators part: // // - none: Indicates general formatting. Same as 'T(_, _)'. // - 'M': Indicates map formatting. Same as 'T_: _'. // - 'N': Indicates none formatting. Same as 'T__'. // - 'T': Indicates user-defined formatting. // // 2. The user-defined part: // // i. A begin string: Indicates the begin string of the tuple. Cannot contain '_' or ':' character. // ii. '_': Indicates a placeholder. // iii. A separator string: Indicates the separator string of the tuple. Cannot contain '_' character. // iv. '_': Indicates a placeholder. // v. An end string: Indicates the end string of the tuple. Cannot contain '_' or ':' character. // // For container values: // // 1. The type indicators part: // // - none: Indicates general formatting. Same as 'T[_, _]'. // - 'N': Indicates none formatting. Same as 'T__'. // - 'T': Indicates user-defined formatting. // // 2. The user-defined part: // // i. A begin string: Indicates the begin string of the container. Cannot contain '_' or ':' character. // ii. '_': Indicates a placeholder. // iii. A separator string: Indicates the separator string of the container. Cannot contain '_' character. // iv. '_': Indicates a placeholder. // v. An end string: Indicates the end string of the container. Cannot contain '_' or ':' character. // // For the type indicator part of the boolean, integer, and floating-point values, // The case of letter indicates the case of the first letter or number part, // and other parts can also be uppercase by appended the '!' mark. // // Specially, the case of letters is ignored by default in parsing, // and can be forced to match the required case by appending the '=' mark. // // Tuples of pointers and containers cannot be parsed. // // Examples: // // - '{:}': Parse the integer value in decimal without positive sign. // - '{:+D}': Parse the integer value in decimal with optional positive sign. // - '{:+#I}': Parse the integer value in any formatting. // - '{:}': Parse the floating-point value in fixed-point without positive sign. // - '{:+F}': Parse the floating-point value in fixed-point with optional positive sign. // - '{:+#G}': Parse the floating-point value in any formatting. // NOTE: These functions are recommended for debug programs. NAMESPACE_PRIVATE_BEGIN // In private, conversion tools use structured parameters to describe the object format. // The structured parameter is an object with specific public members: // // - DigitStyle: A signed integer that represents the letter case of the first part or the digit part. // Less than 0 for lowercase, greater than 0 for uppercase, 0 for default or any in parsing. // It is valid for boolean, integer and floating-point values. // // - OtherStyle: A signed integer that represents the letter case of the other part. // Less than 0 for lowercase, greater than 0 for uppercase, 0 for default or any in parsing. // It is valid for boolean, integer and floating-point values. // // - bSign: A boolean that represents whether to show the sign of the number if it is positive. // It is valid for integer and floating-point values. // // - bPrefix: A boolean that represents whether to show the prefix of the number. // Legal only when base is binary octal decimal and hexadecimal // For parsing, together with the following parameters, it also determines whether to automatically detect the base. // It is valid for integer and floating-point values. // // - Padding: A unsigned integer that represents the '0' padded width of the number. // It is valid for integer values. // // - Base: A unsigned integer that represents the base of the number, between [2, 36]. // However, when parsed and prefixed, 0 is allowed to indicate auto-detection. // It is valid for integer values. // // - bFixed: A boolean that represents whether to use the decimal fixed-point format. // - bScientific: A boolean that represents whether to use the decimal scientific format. // These two parameters together determine the format of the floating-point value. // When both are false, represents the hex scientific is format. // However, when parsed and prefixed, any values allows auto-detection hex scientific format. // It is valid for floating-point values. // // - Precision: A signed integer that represents the number of digits after the decimal point. Negative value means ignore. // For parsing, it is used to determine the maximum number of digits after the decimal point. // It is valid for floating-point values. template <CCharType T> struct TStringObjectFormatter { static bool Do(auto& Result, auto& Object, auto Param) { // ReSharper disable once CppInconsistentNaming using U = TRemoveCVRef<decltype(Object)>; if constexpr (!CConst<TRemoveReference<decltype(Object)>>) { checkf(false, TEXT("Unsafe formatting for a variable that is non-const.")); return false; } // Parse format string if parameter is TStringView, otherwise use the structured parameters directly. if constexpr (requires { { Param.Fmt } -> CConvertibleTo<TStringView<T>>; }) { TStringView<T> Fmt = Param.Fmt; // Parse the fill-and-align part and reserve the space for the result. auto ParseFillAndAlign = [&Result, &Fmt] { TStringView<T> FillCharacter = LITERAL(T, " "); T AlignmentOption = CIntegral<U> || CFloatingPoint<U> ? LITERAL(T, '>') : LITERAL(T, '<'); size_t AlignmentWidth = 0; // Parse the fill-and-align part of the object format. if (!Fmt.IsEmpty()) { size_t Index = Fmt.FindFirstOf(LITERAL(T, "123456789")); if (Index != INDEX_NONE) { // Create a temporary view to avoid modifying the original view. TStringView<T> TrimmedFmt = Fmt; TStringView<T> FillAndAlign = TrimmedFmt.First(Index); TrimmedFmt.RemovePrefix(Index); TStringView<T> View = TrimmedFmt.Substr(0, TrimmedFmt.FindFirstNotOf(LITERAL(T, "0123456789"))); TrimmedFmt.RemovePrefix(View.Num()); size_t PossibleWidth = View.template ToInt<size_t>(); bool bIsValid = true; if (!FillAndAlign.IsEmpty()) { if (FillAndAlign.Back() == LITERAL(T, '<')) { FillAndAlign.RemoveSuffix(1); AlignmentOption = LITERAL(T, '<'); } else if (FillAndAlign.Back() == LITERAL(T, '>')) { FillAndAlign.RemoveSuffix(1); AlignmentOption = LITERAL(T, '>'); } else if (FillAndAlign.Back() == LITERAL(T, '^')) { FillAndAlign.RemoveSuffix(1); AlignmentOption = LITERAL(T, '^'); } else { if (FillAndAlign.Num() != 1) { // If the string contains ASCII then it must not be represented as a single unicode. for (T Char : FillAndAlign) if (TChar<T>::IsASCII(Char)) bIsValid = false; } else if (FillAndAlign.Front() == LITERAL(T, '.')) bIsValid = false; // Ambiguously with the precision indicator. else if (FillAndAlign.Front() == LITERAL(T, '_')) bIsValid = false; // Ambiguously with the base indicator. } } if (bIsValid) { if (!FillAndAlign.IsEmpty()) FillCharacter = FillAndAlign; AlignmentWidth = PossibleWidth; Fmt = TrimmedFmt; } } } Result.Reserve(Result.Num() + AlignmentWidth * FillCharacter.Num()); return MakeTuple(FillCharacter, AlignmentOption, AlignmentWidth, Result.Num()); }; // Apply the fill-and-align part to the result. auto ApplyFillAndAlign = [&Result](auto FillAndAlign) { auto [FillCharacter, AlignmentOption, AlignmentWidth, OriginalNum] = FillAndAlign; const size_t AppendedNum = Result.Num() - OriginalNum; if (AlignmentWidth > AppendedNum) { size_t LeftWidth = 0; size_t RightWidth = 0; switch (AlignmentOption) { case LITERAL(T, '<'): RightWidth = AlignmentWidth - AppendedNum; break; case LITERAL(T, '>'): LeftWidth = AlignmentWidth - AppendedNum; break; case LITERAL(T, '^'): { LeftWidth = (AlignmentWidth - AppendedNum) / 2; RightWidth = AlignmentWidth - AppendedNum - LeftWidth; break; } default: check_no_entry(); } if (LeftWidth != 0) { Result.SetNum(Result.Num() + LeftWidth * FillCharacter.Num(), false); for (size_t Index = 0; Index != AppendedNum; ++Index) { Result[Result.Num() - Index - 1] = Result[OriginalNum + AppendedNum - Index - 1]; } for (size_t Index = 0; Index != LeftWidth * FillCharacter.Num(); ++Index) { Result[OriginalNum + Index] = FillCharacter[Index % FillCharacter.Num()]; } } if (RightWidth != 0) { for (size_t Index = 0; Index < RightWidth; ++Index) { Result += FillCharacter; } } } }; // Format the string value by format string. if constexpr (requires { TStringView(Object); }) { auto FillAndAlign = ParseFillAndAlign(); bool bNeedToCase = false; bool bStringLowercase = false; bool bNeedToEscape = false; bool bEscapeLowercase = false; if (Fmt.StartsWith(LITERAL(T, 'S'))) { bStringLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 's'))) { bStringLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '!'))) { bNeedToCase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '?'))) { bNeedToEscape = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); if (bNeedToEscape && Fmt.StartsWith(LITERAL(T, ':'))) { Fmt.RemovePrefix(1); if (Fmt.StartsWith(LITERAL(T, 'X'))) { bEscapeLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'x'))) { bEscapeLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); } if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } TStringView<T> String = Object; if (bNeedToEscape) Result += LITERAL(T, '\"'); if (bNeedToCase || bNeedToEscape) { for (T Char : String) { if (bNeedToCase) { if (bStringLowercase) Char = TChar<T>::ToLower(Char); else Char = TChar<T>::ToUpper(Char); } if (bNeedToEscape) { switch (Char) { case LITERAL(T, '\"'): Result += LITERAL(T, "\\\""); break; case LITERAL(T, '\\'): Result += LITERAL(T, "\\\\"); break; case LITERAL(T, '\a'): Result += LITERAL(T, "\\a"); break; case LITERAL(T, '\b'): Result += LITERAL(T, "\\b"); break; case LITERAL(T, '\f'): Result += LITERAL(T, "\\f"); break; case LITERAL(T, '\n'): Result += LITERAL(T, "\\n"); break; case LITERAL(T, '\r'): Result += LITERAL(T, "\\r"); break; case LITERAL(T, '\t'): Result += LITERAL(T, "\\t"); break; case LITERAL(T, '\v'): Result += LITERAL(T, "\\v"); break; default: { if (!TChar<T>::IsASCII(Char) || !TChar<T>::IsPrint(Char)) { Result += LITERAL(T, "\\x"); const TMakeUnsigned<T> IntValue = static_cast<TMakeUnsigned<T>>(Char); struct { int DigitStyle; unsigned Padding; unsigned Base; } DigitParam = { bEscapeLowercase ? -1 : 1, sizeof(T) * 2, 16}; verify(TStringObjectFormatter::Do(Result, IntValue, DigitParam)); } else Result += Char; } } } else Result += Char; } } else Result += String; if (bNeedToEscape) Result += LITERAL(T, '\"'); ApplyFillAndAlign(FillAndAlign); return true; } // Format the character value by format string. else if constexpr (CCharType<U>) { if (Fmt.FindFirstOf(LITERAL(T, "Ss")) != INDEX_NONE) { const TStringView<T> StringValue(&Object, 1); return TStringObjectFormatter::Do(Result, StringValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, "BbDdOoXxIi")) != INDEX_NONE) { const TMakeUnsigned<T> IntValue = static_cast<TMakeUnsigned<T>>(Object); return TStringObjectFormatter::Do(Result, IntValue, Param); } auto FillAndAlign = ParseFillAndAlign(); bool bNeedToCase = false; bool bStringLowercase = false; bool bNeedToEscape = false; bool bEscapeLowercase = false; if (Fmt.StartsWith(LITERAL(T, 'C'))) { bStringLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'c'))) { bStringLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '!'))) { bNeedToCase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '?'))) { bNeedToEscape = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); if (bNeedToEscape && Fmt.StartsWith(LITERAL(T, ':'))) { Fmt.RemovePrefix(1); if (Fmt.StartsWith(LITERAL(T, 'X'))) { bEscapeLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'x'))) { bEscapeLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); } if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } T Char = Object; if (bNeedToEscape) Result += LITERAL(T, '\''); if (bNeedToCase || bNeedToEscape) { if (bNeedToCase) { if (bStringLowercase) Char = TChar<T>::ToLower(Char); else Char = TChar<T>::ToUpper(Char); } if (bNeedToEscape) { switch (Char) { case LITERAL(T, '\''): Result += LITERAL(T, "\\\'"); break; case LITERAL(T, '\\'): Result += LITERAL(T, "\\\\"); break; case LITERAL(T, '\a'): Result += LITERAL(T, "\\a"); break; case LITERAL(T, '\b'): Result += LITERAL(T, "\\b"); break; case LITERAL(T, '\f'): Result += LITERAL(T, "\\f"); break; case LITERAL(T, '\n'): Result += LITERAL(T, "\\n"); break; case LITERAL(T, '\r'): Result += LITERAL(T, "\\r"); break; case LITERAL(T, '\t'): Result += LITERAL(T, "\\t"); break; case LITERAL(T, '\v'): Result += LITERAL(T, "\\v"); break; default: { if (!TChar<T>::IsASCII(Char) || !TChar<T>::IsPrint(Char)) { Result += LITERAL(T, "\\x"); const TMakeUnsigned<T> IntValue = static_cast<TMakeUnsigned<T>>(Char); struct { int DigitStyle; unsigned Padding; unsigned Base; } DigitParam = { bEscapeLowercase ? -1 : 1, sizeof(T) * 2, 16 }; verify(TStringObjectFormatter::Do(Result, IntValue, DigitParam)); } else Result += Char; } } } else Result += Char; } else Result += Char; if (bNeedToEscape) Result += LITERAL(T, '\''); ApplyFillAndAlign(FillAndAlign); return true; } // Format the boolean value by format string. else if constexpr (CSameAs<U, bool>) { if (Fmt.IsEmpty()) return TStringObjectFormatter::Do(Result, Object, Invalid); if (Fmt.FindFirstOf(LITERAL(T, 'S')) != INDEX_NONE) { const TStringView<T> StringValue = Object ? LITERAL(T, "True") : LITERAL(T, "False"); return TStringObjectFormatter::Do(Result, StringValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, 's')) != INDEX_NONE) { const TStringView<T> StringValue = Object ? LITERAL(T, "true") : LITERAL(T, "false"); return TStringObjectFormatter::Do(Result, StringValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, 'C')) != INDEX_NONE) { const T CharacterValue = Object ? LITERAL(T, 'T') : LITERAL(T, 'F'); return TStringObjectFormatter::Do(Result, CharacterValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, 'c')) != INDEX_NONE) { const T CharacterValue = Object ? LITERAL(T, 't') : LITERAL(T, 'f'); return TStringObjectFormatter::Do(Result, CharacterValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, "BbDdOoXxIi")) != INDEX_NONE) { const int IntValue = Object ? 1 : 0; return TStringObjectFormatter::Do(Result, IntValue, Param); } checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } // Format the integer value by format string. else if constexpr (CIntegral<U> && !CSameAs<U, bool>) { if (Fmt.IsEmpty()) return TStringObjectFormatter::Do(Result, Object, Invalid); auto FillAndAlign = ParseFillAndAlign(); T PositiveIndicator = LITERAL(T, '-'); bool bPrefix = false; unsigned Padding = 0; bool bHasBase = false; unsigned Base = 10; bool bDigitLowercase = false; bool bOtherLowercase = true; if (Fmt.StartsWith(LITERAL(T, '-'))) { PositiveIndicator = LITERAL(T, '-'); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, '+'))) { PositiveIndicator = LITERAL(T, '+'); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, ' '))) { PositiveIndicator = LITERAL(T, ' '); Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '#'))) { bPrefix = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '0')) && Fmt.Num() > 1 && TChar<T>::IsDigit(Fmt[1]) && Fmt[1] != LITERAL(T, '0')) { Fmt.RemovePrefix(1); TStringView<T> View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Padding = View.template ToInt<uint>(); } if (Fmt.StartsWith(LITERAL(T, '_')) && Fmt.Num() > 1 && TChar<T>::IsDigit(Fmt[1])) { Fmt.RemovePrefix(1); bHasBase = true; TStringView<T> View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Base = View.template ToInt<uint>(); } if ( Fmt.StartsWith(LITERAL(T, 'I'))) { bDigitLowercase = false; Fmt.RemovePrefix(1); } else if ( Fmt.StartsWith(LITERAL(T, 'i'))) { bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'D'))) { Base = 10; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'd'))) { Base = 10; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'B'))) { Base = 2; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'b'))) { Base = 2; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'O'))) { Base = 8; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'o'))) { Base = 8; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'X'))) { Base = 16; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (!bHasBase && Fmt.StartsWith(LITERAL(T, 'x'))) { Base = 16; bDigitLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '!'))) { bOtherLowercase = false; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } struct { int DigitStyle; int OtherStyle; T PositiveSign; bool bPrefix; unsigned Padding; unsigned Base; } IntParam = { bDigitLowercase ? -1 : 1, bOtherLowercase ? -1 : 1, PositiveIndicator, bPrefix, Padding, Base == 0 ? 10 : Base, }; verify(TStringObjectFormatter::Do(Result, Object, IntParam)); ApplyFillAndAlign(FillAndAlign); return true; } // Format the floating-point value by format string. else if constexpr (CFloatingPoint<U>) { if (Fmt.IsEmpty()) { struct { bool bFixed; bool bScientific; unsigned Precision; } FloatParam = { true, false, 6 }; return TStringObjectFormatter::Do(Result, Object, FloatParam); } auto FillAndAlign = ParseFillAndAlign(); T PositiveIndicator = LITERAL(T, '-'); bool bPrefix = false; int Precision = -1; bool bDigitLowercase = false; bool bOtherLowercase = true; bool bFixed = true; bool bScientific = false; if (Fmt.StartsWith(LITERAL(T, '-'))) { PositiveIndicator = LITERAL(T, '-'); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, '+'))) { PositiveIndicator = LITERAL(T, '+'); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, ' '))) { PositiveIndicator = LITERAL(T, ' '); Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '#'))) { bPrefix = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '.')) && Fmt.Num() > 1 && TChar<T>::IsDigit(Fmt[1])) { Fmt.RemovePrefix(1); TStringView<T> View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Precision = View.template ToInt<uint>(); } if (Fmt.StartsWith(LITERAL(T, 'F'))) { bFixed = true; bScientific = false; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'f'))) { bFixed = true; bScientific = false; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'G'))) { bFixed = true; bScientific = true; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'g'))) { bFixed = true; bScientific = true; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'E'))) { bFixed = false; bScientific = true; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'e'))) { bFixed = false; bScientific = true; bDigitLowercase = true; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'A'))) { bFixed = false; bScientific = false; bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'a'))) { bFixed = false; bScientific = false; bDigitLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '!'))) { bOtherLowercase = false; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } if (Precision == -1 && bFixed && !bScientific) Precision = 6; struct { bool bFixed; bool bScientific; int Precision; int DigitStyle; int OtherStyle; T PositiveSign; bool bPrefix; } FloatParam = { bFixed, bScientific, Precision, bDigitLowercase ? -1 : 1, bOtherLowercase ? -1 : 1, PositiveIndicator, bPrefix, }; verify(TStringObjectFormatter::Do(Result, Object, FloatParam)); ApplyFillAndAlign(FillAndAlign); return true; } // Format the pointer value by format string. else if constexpr (CNullPointer<U> || TPointerTraits<U>::bIsPointer) { void* Ptr = nullptr; if constexpr (!CNullPointer<U>) Ptr = ToAddress(Object); auto FillAndAlign = ParseFillAndAlign(); bool bDigitLowercase = false; bool bOtherLowercase = true; if (Fmt.StartsWith(LITERAL(T, 'P'))) { bDigitLowercase = false; Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'p'))) { bDigitLowercase = true; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '!'))) { bOtherLowercase = false; Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, '='))) Fmt.RemovePrefix(1); if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } const uintptr IntValue = reinterpret_cast<uintptr>(Ptr); struct { int DigitStyle; int OtherStyle; bool bPrefix; unsigned Padding; unsigned Base; } IntParam = { bDigitLowercase ? -1 : 1, bOtherLowercase ? -1 : 1, true, sizeof(uintptr) * 2, 16, }; verify(TStringObjectFormatter::Do(Result, IntValue, IntParam)); ApplyFillAndAlign(FillAndAlign); return true; } // Format the tuple value by format string. else if constexpr (CTTuple<U>) { auto FillAndAlign = ParseFillAndAlign(); TStringView<T> Begin = LITERAL(T, "("); TStringView<T> Separator = LITERAL(T, ", "); TStringView<T> End = LITERAL(T, ")"); if (Fmt.StartsWith(LITERAL(T, 'T')) || Fmt.StartsWith(LITERAL(T, 't'))) { Fmt.RemovePrefix(1); const size_t PlaceholderA = Fmt.FindFirstOf(LITERAL(T, '_')); const size_t PlaceholderB = Fmt.FindFirstOf(LITERAL(T, '_'), PlaceholderA + 1); if (PlaceholderA == INDEX_NONE || PlaceholderB == INDEX_NONE || PlaceholderA == PlaceholderB) { checkf(false, TEXT("Illegal format string. Expect placeholders.")); return false; } size_t UserDefinedEnd = Fmt.FindFirstOf(LITERAL(T, ':'), PlaceholderB + 1); if (UserDefinedEnd == INDEX_NONE) UserDefinedEnd = Fmt.Num(); Begin = Fmt.First(PlaceholderA); Separator = Fmt.Substr(PlaceholderA + 1, PlaceholderB - PlaceholderA - 1); End = Fmt.Substr(PlaceholderB + 1, UserDefinedEnd - PlaceholderB - 1); Fmt.RemovePrefix(UserDefinedEnd); } else if (Fmt.StartsWith(LITERAL(T, 'M'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ": "); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'm'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ": "); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'N'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ""); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'n'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ""); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } if (Object.IsEmpty()) { Result += Begin; Result += End; ApplyFillAndAlign(FillAndAlign); return true; } TString<T, TInlineAllocator<64>> Buffer; struct { TStringView<T> Fmt; } Empty = { LITERAL(T, "") }; bool bIsSuccessful = TStringObjectFormatter::Do(Buffer, Object.template GetValue<0>(), Empty); bIsSuccessful = [=, &Object, &Buffer]<size_t... Indices>(TIndexSequence<Indices...>) -> bool { return (bIsSuccessful && ... && (Buffer += Separator, TStringObjectFormatter::Do(Buffer, Object.template GetValue<Indices + 1>(), Empty))); } (TMakeIndexSequence<Object.Num() - 1>()); if (!bIsSuccessful) { checkf(false, TEXT("Failed to fully format tuple value.")); return false; } Result += Begin; Result += Buffer; Result += End; ApplyFillAndAlign(FillAndAlign); return true; } // Format the container value by format string. else if constexpr (requires { Ranges::Begin(Object); Ranges::End(Object); }) { auto FillAndAlign = ParseFillAndAlign(); TStringView<T> Begin = LITERAL(T, "["); TStringView<T> Separator = LITERAL(T, ", "); TStringView<T> End = LITERAL(T, "]"); TStringView<T> Subfmt = LITERAL(T, ""); if (Fmt.StartsWith(LITERAL(T, 'T')) || Fmt.StartsWith(LITERAL(T, 't'))) { Fmt.RemovePrefix(1); const size_t PlaceholderA = Fmt.FindFirstOf(LITERAL(T, '_')); const size_t PlaceholderB = Fmt.FindFirstOf(LITERAL(T, '_'), PlaceholderA + 1); if (PlaceholderA == INDEX_NONE || PlaceholderB == INDEX_NONE || PlaceholderA == PlaceholderB) { checkf(false, TEXT("Illegal format string. Expect placeholders.")); return false; } size_t UserDefinedEnd = Fmt.FindFirstOf(LITERAL(T, ':'), PlaceholderB + 1); if (UserDefinedEnd == INDEX_NONE) UserDefinedEnd = Fmt.Num(); Begin = Fmt.First(PlaceholderA); Separator = Fmt.Substr(PlaceholderA + 1, PlaceholderB - PlaceholderA - 1); End = Fmt.Substr(PlaceholderB + 1, UserDefinedEnd - PlaceholderB - 1); Fmt.RemovePrefix(UserDefinedEnd); } else if (Fmt.StartsWith(LITERAL(T, 'N'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ""); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } else if (Fmt.StartsWith(LITERAL(T, 'n'))) { Begin = LITERAL(T, ""); Separator = LITERAL(T, ""); End = LITERAL(T, ""); Fmt.RemovePrefix(1); } if (Fmt.StartsWith(LITERAL(T, ':'))) { Fmt.RemovePrefix(1); Subfmt = Fmt; Fmt = LITERAL(T, ""); } if (!Fmt.IsEmpty()) { checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); return false; } if (Ranges::Begin(Object) == Ranges::End(Object)) { Result += Begin; Result += End; ApplyFillAndAlign(FillAndAlign); return true; } TString<T, TInlineAllocator<64>> Buffer; struct { TStringView<T> Fmt; } ElementParam = { Subfmt }; // It is assumed that if the first element is successfully formatted, all elements will succeed. bool bIsSuccessful = TStringObjectFormatter::Do(Buffer, *Ranges::Begin(Object), ElementParam); if (!bIsSuccessful) { checkf(false, TEXT("Failed to fully format container value.")); return false; } Result += Begin; Result += Buffer; auto Sentinel = Ranges::End(Object); for (auto Iter = ++Ranges::Begin(Object); Iter != Sentinel; ++Iter) { Result += Separator; verify(TStringObjectFormatter::Do(Result, *Iter, ElementParam)); } Result += End; ApplyFillAndAlign(FillAndAlign); return true; } else static_assert(sizeof(U) == -1, "Unsupported object type."); } else { // Format the boolean value by structured parameters. if constexpr (CSameAs<U, bool>) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo<int>; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo<int>; }; if constexpr (bHasDigitStyle || bHasOtherStyle) { Result.Reserve(Result.Num() + 5); bool bDigitLowercase = false; if constexpr (bHasDigitStyle) bDigitLowercase = Param.DigitStyle < 0; bool bOtherLowercase = true; if constexpr (bHasOtherStyle) bOtherLowercase = Param.OtherStyle <= 0; if (bDigitLowercase) { if (Object) Result += LITERAL(T, 't'); else Result += LITERAL(T, 'f'); } else { if (Object) Result += LITERAL(T, 'T'); else Result += LITERAL(T, 'F'); } if (bOtherLowercase) { if (Object) Result += LITERAL(T, "RUE"); else Result += LITERAL(T, "ALSE"); } else { if (Object) Result += LITERAL(T, "rue"); else Result += LITERAL(T, "alse"); } return true; } if (Object) Result += LITERAL(T, "True"); else Result += LITERAL(T, "False"); return true; } // Format the integer value by structured parameters. else if constexpr (CIntegral<U> && !CSameAs<U, bool>) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo<int>; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo<int>; }; constexpr bool bHasSign = requires { { Param.PositiveSign } -> CConvertibleTo<T>; }; constexpr bool bHasPrefix = requires { { Param.bPrefix } -> CBooleanTestable; }; constexpr bool bHasBase = requires { { Param.Base } -> CConvertibleTo<unsigned>; }; constexpr bool bHasPadding = requires { { Param.Padding } -> CConvertibleTo<unsigned>; }; static_assert(TChar<T>::IsASCII()); // If the value should be formatted with prefix, the value must be binary, octal, decimal or hexadecimal. if constexpr (bHasPrefix && bHasBase) if (Param.bPrefix) { if (Param.Base != 2 && Param.Base != 8 && Param.Base != 10 && Param.Base != 16) { checkf(false, TEXT("Prefix is only supported for binary, octal, decimal and hexadecimal value.")); return false; } } using FUnsignedU = TMakeUnsigned<U>; FUnsignedU Unsigned = static_cast<FUnsignedU>(Object); bool bNegative = false; if constexpr (CSigned<U>) { if (Object < 0) { bNegative = true; Unsigned = static_cast<FUnsignedU>(-Unsigned); } } constexpr size_t BufferSize = sizeof(FUnsignedU) * 8 + 4; T Buffer[BufferSize]; T* DigitEnd = Buffer + BufferSize; T* Iter = Buffer + BufferSize; // Reverse append the digits to the buffer. if constexpr (bHasBase) { checkf(Param.Base >= 2 && Param.Base <= 36, TEXT("Illegal base.")); if constexpr (bHasDigitStyle) { const bool bLowercase = Param.DigitStyle < 0; switch (Param.Base) { case 0x02: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00001)); Unsigned >>= 1; } while (Unsigned != 0); break; case 0x04: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00011)); Unsigned >>= 2; } while (Unsigned != 0); break; case 0x08: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00111)); Unsigned >>= 3; } while (Unsigned != 0); break; case 0x10: do { *--Iter = TChar<T>::FromDigit(Unsigned & 0b01111, bLowercase); Unsigned >>= 4; } while (Unsigned != 0); break; case 0X20: do { *--Iter = TChar<T>::FromDigit(Unsigned & 0b11111, bLowercase); Unsigned >>= 5; } while (Unsigned != 0); break; case 3: case 5: case 6: case 7: case 9: case 10: do { *--Iter = static_cast<T>('0' + Unsigned % Param.Base); Unsigned = static_cast<FUnsignedU>(Unsigned / Param.Base); } while (Unsigned != 0); break; default: do { *--Iter = TChar<T>::FromDigit(Unsigned % Param.Base, bLowercase); Unsigned = static_cast<FUnsignedU>(Unsigned / Param.Base); } while (Unsigned != 0); break; } } else { switch (Param.Base) { case 0x02: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00001)); Unsigned >>= 1; } while (Unsigned != 0); break; case 0x04: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00011)); Unsigned >>= 2; } while (Unsigned != 0); break; case 0x08: do { *--Iter = static_cast<T>('0' + (Unsigned & 0b00111)); Unsigned >>= 3; } while (Unsigned != 0); break; case 0x10: do { *--Iter = TChar<T>::FromDigit(Unsigned & 0b01111); Unsigned >>= 4; } while (Unsigned != 0); break; case 0X20: do { *--Iter = TChar<T>::FromDigit(Unsigned & 0b11111); Unsigned >>= 5; } while (Unsigned != 0); break; case 3: case 5: case 6: case 7: case 9: case 10: do { *--Iter = static_cast<T>('0' + Unsigned % Param.Base); Unsigned = static_cast<FUnsignedU>(Unsigned / Param.Base); } while (Unsigned != 0); break; default: do { *--Iter = TChar<T>::FromDigit(Unsigned % Param.Base); Unsigned = static_cast<FUnsignedU>(Unsigned / Param.Base); } while (Unsigned != 0); break; } } } else do { *--Iter = static_cast<T>('0' + Unsigned % 10); Unsigned = static_cast<FUnsignedU>(Unsigned / 10); } while (Unsigned != 0); T* DigitBegin = Iter; // Handle the width parameter. if constexpr (bHasPadding) if (Param.Padding > DigitEnd - DigitBegin) { const size_t Padding = Param.Padding - (DigitEnd - DigitBegin); if (Param.Padding < sizeof(FUnsignedU) * 8) for (size_t Index = 0; Index != Padding; ++Index) *--Iter = LITERAL(T, '0'); } // Append the prefix to the buffer. if constexpr (bHasPrefix && bHasBase) if (Param.bPrefix && Param.Base != 10) { bool bOtherLowercase = true; if constexpr (bHasOtherStyle) bOtherLowercase = Param.OtherStyle <= 0; const T PrefixBin = bOtherLowercase ? LITERAL(T, 'b') : LITERAL(T, 'B'); const T PrefixHex = bOtherLowercase ? LITERAL(T, 'x') : LITERAL(T, 'X'); if (Param.Base == 2) { *--Iter = PrefixBin; *--Iter = LITERAL(T, '0'); } if (Param.Base == 8) { if (Object != 0) *--Iter = LITERAL(T, '0'); } if (Param.Base == 16) { *--Iter = PrefixHex; *--Iter = LITERAL(T, '0'); } } // Append the negative sign to the buffer. if constexpr (CSigned<U>) if (bNegative) *--Iter = LITERAL(T, '-'); // Append the positive sign to the buffer. if constexpr (bHasSign) if (!bNegative && Param.PositiveSign != LITERAL(T, '-')) *--Iter = Param.PositiveSign; // Handle the width parameter. if constexpr (bHasPadding) if (Param.Padding > DigitEnd - DigitBegin) { const size_t Padding = Param.Padding - (DigitEnd - DigitBegin); if (Param.Padding > sizeof(FUnsignedU) * 8) { Result.Reserve(Result.Num() + (DigitBegin - Iter) + Param.Padding); Result.Append(Iter, DigitBegin); for (size_t Index = 0; Index != Padding; ++Index) Result += LITERAL(T, '0'); Result.Append(DigitBegin, DigitEnd); return true; } } Result.Append(Iter, DigitEnd); return true; } // Format the floating-point value by structured parameters. else if constexpr (CFloatingPoint<U>) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo<int>; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo<int>; }; constexpr bool bHasSign = requires { { Param.PositiveSign } -> CConvertibleTo<T>; }; constexpr bool bHasPrefix = requires { { Param.bPrefix } -> CBooleanTestable; }; constexpr bool bHasPrecision = requires { { Param.Precision } -> CConvertibleTo<int>; }; constexpr bool bHasFormat = requires { { Param.bFixed } -> CBooleanTestable; { Param.bScientific } -> CBooleanTestable; }; NAMESPACE_STD::chars_format Format = NAMESPACE_STD::chars_format::general; if constexpr (bHasFormat) if ( Param.bFixed && !Param.bScientific) Format = NAMESPACE_STD::chars_format::fixed; if constexpr (bHasFormat) if (!Param.bFixed && Param.bScientific) Format = NAMESPACE_STD::chars_format::scientific; if constexpr (bHasFormat) if (!Param.bFixed && !Param.bScientific) Format = NAMESPACE_STD::chars_format::hex; constexpr size_t StartingBufferSize = 64; // Create a buffer with a starting size. TArray<char, TInlineAllocator<StartingBufferSize>> Buffer(StartingBufferSize / 2); // Formatting strings using the standard library until successful NAMESPACE_STD::to_chars_result ConvertResult; do { Buffer.SetNum(Buffer.Num() * 2); if constexpr (bHasPrecision) { if (Param.Precision >= 0) ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Object, Format, Param.Precision); else ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Object, Format); } else if constexpr (bHasFormat) ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Object, Format); else ConvertResult = NAMESPACE_STD::to_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Object); } while (ConvertResult.ec == NAMESPACE_STD::errc::value_too_large); // Set the buffer size to the number of characters written. Buffer.SetNum(ConvertResult.ptr - Buffer.GetData()); const bool bNegative = Buffer[0] == '-'; const char* Iter = Buffer.GetData() + (bNegative ? 1 : 0); bool bDigitLowercase = false; if constexpr (bHasDigitStyle) bDigitLowercase = Param.DigitStyle < 0; bool bOtherLowercase = true; if constexpr (bHasOtherStyle) bOtherLowercase = Param.OtherStyle <= 0; // Handle the infinity values. if (*Iter == 'i') { Result.Reserve(Result.Num() + 9); if (bNegative) Result.Append(LITERAL(T, "-")); // Append the positive sign to the buffer. else if constexpr (bHasSign) if (Param.PositiveSign != LITERAL(T, '-')) Result += Param.PositiveSign; if constexpr (bHasDigitStyle || bHasOtherStyle) { if (bDigitLowercase) Result += LITERAL(T, 'i'); else Result += LITERAL(T, 'I'); if (bOtherLowercase) Result += LITERAL(T, "nfinity"); else Result += LITERAL(T, "NFINITY"); return true; } Result += LITERAL(T, "Infinity"); return true; } // Handle the NaN values. if (*Iter == 'n') { Result.Reserve(Result.Num() + 4); if (bNegative) Result.Append(LITERAL(T, "-")); // Append the positive sign to the buffer. else if constexpr (bHasSign) if (Param.PositiveSign != LITERAL(T, '-')) Result += Param.PositiveSign; if constexpr (bHasDigitStyle || bHasOtherStyle) { if (bDigitLowercase) Result += LITERAL(T, 'n'); else Result += LITERAL(T, 'N'); if (bOtherLowercase) Result += LITERAL(T, "a"); else Result += LITERAL(T, "A"); if (bDigitLowercase) Result += LITERAL(T, 'n'); else Result += LITERAL(T, 'N'); return true; } Result += LITERAL(T, "NaN"); return true; } Result.Reserve(Result.Num() + Buffer.Num() + 4); // Append the positive sign to the buffer. if constexpr (bHasSign) if (Param.PositiveSign != LITERAL(T, '-')) Result += Param.PositiveSign; // Handle the prefix. if constexpr (bHasPrefix) if (Param.bPrefix) { if (Format == NAMESPACE_STD::chars_format::hex) { if (bOtherLowercase) Result += LITERAL(T, "0x"); else Result += LITERAL(T, "0X"); } } // Handle the lowercase or uppercase characters. if constexpr (bHasFormat || bHasDigitStyle || bHasOtherStyle) { const unsigned Base = Format == NAMESPACE_STD::chars_format::hex ? 16 : 10; if (Base == 16 && !bDigitLowercase) { for (char& Char : Buffer) if (FChar::ToDigit(Char) < Base) Char = FChar::ToUpper(Char); } if (!bOtherLowercase) { for (char& Char : Buffer) if (FChar::ToDigit(Char) >= Base) Char = FChar::ToUpper(Char); } } Result.Append(Buffer.Begin(), Buffer.End()); return true; } else static_assert(sizeof(U) == -1, "Unsupported object type."); } checkf(false, TEXT("Unsupported type for formatting.")); return false; } }; template <CCharType T, bool bIsFormat> struct TStringFormatOrParseHelper { static constexpr T LeftBrace = LITERAL(T, '{'); static constexpr T RightBrace = LITERAL(T, '}'); static inline const TStringView EscapeLeftBrace = LITERAL(T, "[{"); static inline const TStringView EscapeRightBrace = LITERAL(T, "}]"); static FORCEINLINE size_t Do(auto& Result, TStringView<T> Fmt, auto ArgsTuple) { size_t FormattedObjectNum = 0; size_t ArgsIndex = 0; auto ParseFormat = [&FormattedObjectNum, &ArgsIndex, ArgsTuple](auto& Self, auto& String, TStringView<T>& Fmt) -> bool { bool bIsFullyFormatted = true; while (!Fmt.IsEmpty()) { if (Fmt.StartsWith(EscapeLeftBrace)) { Fmt.RemovePrefix(EscapeLeftBrace.Num()); if constexpr (!bIsFormat) { if (!String.StartsWith(LeftBrace)) return false; String.RemovePrefix(1); } else String += LeftBrace; continue; } if (Fmt.StartsWith(EscapeRightBrace)) { Fmt.RemovePrefix(EscapeRightBrace.Num()); if constexpr (!bIsFormat) { if (!String.StartsWith(RightBrace)) return false; String.RemovePrefix(1); } else String += RightBrace; continue; } if (Fmt.StartsWith(LeftBrace)) { Fmt.RemovePrefix(1); int SubplaceholderNum = -1; size_t PlaceholderBegin = -1; size_t PlaceholderEnd = -1; // Find the end of the placeholder. do { while (true) { PlaceholderBegin = Fmt.FindFirstOf(LeftBrace, PlaceholderBegin + 1); if (PlaceholderBegin == INDEX_NONE) break; if (Fmt.First(PlaceholderBegin + 1).EndsWith(EscapeLeftBrace)) { ++PlaceholderBegin; ++SubplaceholderNum; } else break; } while (true) { PlaceholderEnd = Fmt.FindFirstOf(RightBrace, PlaceholderEnd + 1); if (PlaceholderEnd == INDEX_NONE) break; if (Fmt.Substr(PlaceholderEnd).StartsWith(EscapeRightBrace)) { ++PlaceholderEnd; ++SubplaceholderNum; } else break; } if (PlaceholderEnd == INDEX_NONE) { checkf(false, TEXT("Unmatched '{' in format string.")); if constexpr (bIsFormat) String += Fmt; Fmt = LITERAL(T, ""); return false; } ++SubplaceholderNum; } while (PlaceholderBegin != INDEX_NONE && PlaceholderBegin < PlaceholderEnd); TStringView Subfmt = Fmt.First(PlaceholderEnd); Fmt.RemovePrefix(PlaceholderEnd + 1); bool bIsSuccessful = true; // The subformat string size are usually smaller than 16. TString<T, TInlineAllocator<16>> FormattedSubfmt; // Recursively format the subformat string. if (SubplaceholderNum > 0) { if constexpr (bIsFormat) bIsSuccessful = Self(Self, FormattedSubfmt, Subfmt); else bIsSuccessful = TStringFormatOrParseHelper<T, true>::Do(FormattedSubfmt, Subfmt, ArgsTuple); Subfmt = FormattedSubfmt; } if (bIsSuccessful) { // Find the placeholder index delimiter. size_t IndexLength = Subfmt.FindFirstOf(LITERAL(T, ':')); if (IndexLength == INDEX_NONE) IndexLength = Subfmt.Num(); TStringView PlaceholderIndex = Subfmt.First(IndexLength); TStringView PlaceholderSubfmt = IndexLength != Subfmt.Num() ? Subfmt.Substr(IndexLength + 1) : LITERAL(T, ""); size_t Index; if (IndexLength != 0) { if (!PlaceholderIndex.template IsInteger<size_t>(10)) { checkf(false, TEXT("Invalid placeholder index.")); if constexpr (bIsFormat) { String += LeftBrace; String += Subfmt; String += RightBrace; bIsFullyFormatted = false; } else return false; continue; } else Index = PlaceholderIndex.template ToInt<size_t>(); } else Index = ArgsIndex++; checkf(Index < ArgsTuple.Num(), TEXT("Argument not found.")); bIsSuccessful = ArgsTuple.Visit( [&String, Subfmt = PlaceholderSubfmt](auto& Object) mutable { struct { TStringView<T> Fmt; } Param = { Subfmt }; if constexpr (bIsFormat) return TStringObjectFormatter<T>::Do(String, Object, Param); else static_assert(bIsFormat, "Parsing is not supported."); }, Index ); } if (!bIsSuccessful) { if constexpr (bIsFormat) { String += LeftBrace; String += Subfmt; String += RightBrace; bIsFullyFormatted = false; } else return false; } else ++FormattedObjectNum; continue; } check_code({ if (Fmt.StartsWith(RightBrace)) check_no_entry(); }); if constexpr (!bIsFormat) { if (TChar<T>::IsSpace(Fmt.Front())) { Fmt.RemovePrefix(1); while (TChar<T>::IsSpace(String.Front())) { String.RemovePrefix(1); } continue; } if (!String.StartsWith(Fmt.Front())) return false; String.RemovePrefix(1); } else String += Fmt.Front(); Fmt.RemovePrefix(1); } return bIsFullyFormatted; }; bool bIsSuccessful = ParseFormat(ParseFormat, Result, Fmt); if constexpr (bIsFormat) return bIsSuccessful; return FormattedObjectNum; } }; NAMESPACE_PRIVATE_END template <CCharType T, CAllocator<T> Allocator> template <typename ... Ts> void TString<T, Allocator>::AppendFormat(TStringView<FElementType> Fmt, const Ts&... Args) { // The Unreal Engine says that the starting buffer size catches 99.97% of printf calls. constexpr size_t ReserveBufferSize = 512; TString<T, TInlineAllocator<ReserveBufferSize>> Result; NAMESPACE_PRIVATE::TStringFormatOrParseHelper<FElementType, true>::Do(Result, Fmt, ForwardAsTuple(Args...)); Append(Result.Begin(), Result.End()); } template <CCharType T, CAllocator<T> Allocator> void TString<T, Allocator>::AppendBool(bool Value) { NAMESPACE_PRIVATE::TStringObjectFormatter<FElementType>::Do(*this, AsConst(Value), Invalid); } template <CCharType T, CAllocator<T> Allocator> template <CIntegral U> requires (!CSameAs<U, bool> && !CConst<U> && !CVolatile<U>) void TString<T, Allocator>::AppendInt(U Value, unsigned Base) { checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); struct { unsigned Base; } Param = { Base }; NAMESPACE_PRIVATE::TStringObjectFormatter<FElementType>::Do(*this, AsConst(Value), Param); } template <CCharType T, CAllocator<T> Allocator> template <CFloatingPoint U> requires (!CConst<U> && !CVolatile<U>) void TString<T, Allocator>::AppendFloat(U Value) { NAMESPACE_PRIVATE::TStringObjectFormatter<FElementType>::Do(*this, AsConst(Value), Invalid); } template <CCharType T, CAllocator<T> Allocator> template <CFloatingPoint U> requires (!CConst<U> && !CVolatile<U>) void TString<T, Allocator>::AppendFloat(U Value, bool bFixed, bool bScientific) { struct { bool bFixed; bool bScientific; } Param = { bFixed, bScientific }; NAMESPACE_PRIVATE::TStringObjectFormatter<FElementType>::Do(*this, AsConst(Value), Param); } template <CCharType T, CAllocator<T> Allocator> template <CFloatingPoint U> requires (!CConst<U> && !CVolatile<U>) void TString<T, Allocator>::AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision) { struct { bool bFixed; bool bScientific; unsigned Precision; } Param = { bFixed, bScientific, Precision }; NAMESPACE_PRIVATE::TStringObjectFormatter<FElementType>::Do(*this, AsConst(Value), Param); } NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END #pragma warning(pop)