#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 #include #include #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 struct TStringObjectFormatter { static bool Do(auto& Result, auto& Object, auto Param) { // ReSharper disable once CppInconsistentNaming using U = TRemoveCVRef; if constexpr (!CConst>) { 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 Fmt = Param.Fmt; // Parse the fill-and-align part and reserve the space for the result. auto ParseFillAndAlign = [&Result, &Fmt] { TStringView FillCharacter = LITERAL(T, " "); T AlignmentOption = CIntegral || CFloatingPoint ? 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 TrimmedFmt = Fmt; TStringView FillAndAlign = TrimmedFmt.First(Index); TrimmedFmt.RemovePrefix(Index); TStringView View = TrimmedFmt.Substr(0, TrimmedFmt.FindFirstNotOf(LITERAL(T, "0123456789"))); TrimmedFmt.RemovePrefix(View.Num()); size_t PossibleWidth = View.template ToInt(); 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::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 String = Object; if (bNeedToEscape) Result += LITERAL(T, '\"'); if (bNeedToCase || bNeedToEscape) { for (T Char : String) { if (bNeedToCase) { if (bStringLowercase) Char = TChar::ToLower(Char); else Char = TChar::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::IsASCII(Char) || !TChar::IsPrint(Char)) { Result += LITERAL(T, "\\x"); const TMakeUnsigned IntValue = static_cast>(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) { if (Fmt.FindFirstOf(LITERAL(T, "Ss")) != INDEX_NONE) { const TStringView StringValue(&Object, 1); return TStringObjectFormatter::Do(Result, StringValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, "BbDdOoXxIi")) != INDEX_NONE) { const TMakeUnsigned IntValue = static_cast>(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::ToLower(Char); else Char = TChar::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::IsASCII(Char) || !TChar::IsPrint(Char)) { Result += LITERAL(T, "\\x"); const TMakeUnsigned IntValue = static_cast>(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) { if (Fmt.IsEmpty()) return TStringObjectFormatter::Do(Result, Object, Invalid); if (Fmt.FindFirstOf(LITERAL(T, 'S')) != INDEX_NONE) { const TStringView StringValue = Object ? LITERAL(T, "True") : LITERAL(T, "False"); return TStringObjectFormatter::Do(Result, StringValue, Param); } if (Fmt.FindFirstOf(LITERAL(T, 's')) != INDEX_NONE) { const TStringView 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 && !CSameAs) { 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::IsDigit(Fmt[1]) && Fmt[1] != LITERAL(T, '0')) { Fmt.RemovePrefix(1); TStringView View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Padding = View.template ToInt(); } if (Fmt.StartsWith(LITERAL(T, '_')) && Fmt.Num() > 1 && TChar::IsDigit(Fmt[1])) { Fmt.RemovePrefix(1); bHasBase = true; TStringView View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Base = View.template ToInt(); } 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) { 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::IsDigit(Fmt[1])) { Fmt.RemovePrefix(1); TStringView View = Fmt.Substr(0, Fmt.FindFirstNotOf(LITERAL(T, "0123456789"))); Fmt.RemovePrefix(View.Num()); Precision = View.template ToInt(); } 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 || TPointerTraits::bIsPointer) { void* Ptr = nullptr; if constexpr (!CNullPointer) 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(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) { auto FillAndAlign = ParseFillAndAlign(); TStringView Begin = LITERAL(T, "("); TStringView Separator = LITERAL(T, ", "); TStringView 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> Buffer; struct { TStringView Fmt; } Empty = { LITERAL(T, "") }; bool bIsSuccessful = TStringObjectFormatter::Do(Buffer, Object.template GetValue<0>(), Empty); bIsSuccessful = [=, &Object, &Buffer](TIndexSequence) -> bool { return (bIsSuccessful && ... && (Buffer += Separator, TStringObjectFormatter::Do(Buffer, Object.template GetValue(), Empty))); } (TMakeIndexSequence()); 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 Begin = LITERAL(T, "["); TStringView Separator = LITERAL(T, ", "); TStringView End = LITERAL(T, "]"); TStringView 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> Buffer; struct { TStringView 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) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo; }; 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 && !CSameAs) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo; }; constexpr bool bHasSign = requires { { Param.PositiveSign } -> CConvertibleTo; }; constexpr bool bHasPrefix = requires { { Param.bPrefix } -> CBooleanTestable; }; constexpr bool bHasBase = requires { { Param.Base } -> CConvertibleTo; }; constexpr bool bHasPadding = requires { { Param.Padding } -> CConvertibleTo; }; static_assert(TChar::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; FUnsignedU Unsigned = static_cast(Object); bool bNegative = false; if constexpr (CSigned) { if (Object < 0) { bNegative = true; Unsigned = static_cast(-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('0' + (Unsigned & 0b00001)); Unsigned >>= 1; } while (Unsigned != 0); break; case 0x04: do { *--Iter = static_cast('0' + (Unsigned & 0b00011)); Unsigned >>= 2; } while (Unsigned != 0); break; case 0x08: do { *--Iter = static_cast('0' + (Unsigned & 0b00111)); Unsigned >>= 3; } while (Unsigned != 0); break; case 0x10: do { *--Iter = TChar::FromDigit(Unsigned & 0b01111, bLowercase); Unsigned >>= 4; } while (Unsigned != 0); break; case 0X20: do { *--Iter = TChar::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('0' + Unsigned % Param.Base); Unsigned = static_cast(Unsigned / Param.Base); } while (Unsigned != 0); break; default: do { *--Iter = TChar::FromDigit(Unsigned % Param.Base, bLowercase); Unsigned = static_cast(Unsigned / Param.Base); } while (Unsigned != 0); break; } } else { switch (Param.Base) { case 0x02: do { *--Iter = static_cast('0' + (Unsigned & 0b00001)); Unsigned >>= 1; } while (Unsigned != 0); break; case 0x04: do { *--Iter = static_cast('0' + (Unsigned & 0b00011)); Unsigned >>= 2; } while (Unsigned != 0); break; case 0x08: do { *--Iter = static_cast('0' + (Unsigned & 0b00111)); Unsigned >>= 3; } while (Unsigned != 0); break; case 0x10: do { *--Iter = TChar::FromDigit(Unsigned & 0b01111); Unsigned >>= 4; } while (Unsigned != 0); break; case 0X20: do { *--Iter = TChar::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('0' + Unsigned % Param.Base); Unsigned = static_cast(Unsigned / Param.Base); } while (Unsigned != 0); break; default: do { *--Iter = TChar::FromDigit(Unsigned % Param.Base); Unsigned = static_cast(Unsigned / Param.Base); } while (Unsigned != 0); break; } } } else do { *--Iter = static_cast('0' + Unsigned % 10); Unsigned = static_cast(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) 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) { constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo; }; constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo; }; constexpr bool bHasSign = requires { { Param.PositiveSign } -> CConvertibleTo; }; constexpr bool bHasPrefix = requires { { Param.bPrefix } -> CBooleanTestable; }; constexpr bool bHasPrecision = requires { { Param.Precision } -> CConvertibleTo; }; 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> 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 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 Fmt, auto ArgsTuple) { size_t FormattedObjectNum = 0; size_t ArgsIndex = 0; auto ParseFormat = [&FormattedObjectNum, &ArgsIndex, ArgsTuple](auto& Self, auto& String, TStringView& 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> FormattedSubfmt; // Recursively format the subformat string. if (SubplaceholderNum > 0) { if constexpr (bIsFormat) bIsSuccessful = Self(Self, FormattedSubfmt, Subfmt); else bIsSuccessful = TStringFormatOrParseHelper::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(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(); } else Index = ArgsIndex++; checkf(Index < ArgsTuple.Num(), TEXT("Argument not found.")); bIsSuccessful = ArgsTuple.Visit( [&String, Subfmt = PlaceholderSubfmt](auto& Object) mutable { struct { TStringView Fmt; } Param = { Subfmt }; if constexpr (bIsFormat) return TStringObjectFormatter::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::IsSpace(Fmt.Front())) { Fmt.RemovePrefix(1); while (TChar::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 Allocator> template void TString::AppendFormat(TStringView 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> Result; NAMESPACE_PRIVATE::TStringFormatOrParseHelper::Do(Result, Fmt, ForwardAsTuple(Args...)); Append(Result.Begin(), Result.End()); } template Allocator> void TString::AppendBool(bool Value) { NAMESPACE_PRIVATE::TStringObjectFormatter::Do(*this, AsConst(Value), Invalid); } template Allocator> template requires (!CSameAs && !CConst && !CVolatile) void TString::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::Do(*this, AsConst(Value), Param); } template Allocator> template requires (!CConst && !CVolatile) void TString::AppendFloat(U Value) { NAMESPACE_PRIVATE::TStringObjectFormatter::Do(*this, AsConst(Value), Invalid); } template Allocator> template requires (!CConst && !CVolatile) void TString::AppendFloat(U Value, bool bFixed, bool bScientific) { struct { bool bFixed; bool bScientific; } Param = { bFixed, bScientific }; NAMESPACE_PRIVATE::TStringObjectFormatter::Do(*this, AsConst(Value), Param); } template Allocator> template requires (!CConst && !CVolatile) void TString::AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision) { struct { bool bFixed; bool bScientific; unsigned Precision; } Param = { bFixed, bScientific, Precision }; NAMESPACE_PRIVATE::TStringObjectFormatter::Do(*this, AsConst(Value), Param); } NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END #pragma warning(pop)