From a3cebf03ecc64a0ec8afed6081b80180c9ce2a68 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Wed, 25 Dec 2024 18:15:46 +0800 Subject: [PATCH] refactor(strings): refactor and simplify string parsing functions --- .../Source/Private/Testing/Strings.cpp | 131 +- .../Source/Public/Strings/Conversion.h.inl | 1212 +---------------- .../Source/Public/Strings/Convert.h | 487 +++++++ .../Source/Public/Strings/String.h | 184 +-- .../Source/Public/Strings/StringView.h | 147 +- 5 files changed, 676 insertions(+), 1485 deletions(-) create mode 100644 Redcraft.Utility/Source/Public/Strings/Convert.h diff --git a/Redcraft.Utility/Source/Private/Testing/Strings.cpp b/Redcraft.Utility/Source/Private/Testing/Strings.cpp index 285779e..d735cce 100644 --- a/Redcraft.Utility/Source/Private/Testing/Strings.cpp +++ b/Redcraft.Utility/Source/Private/Testing/Strings.cpp @@ -2,9 +2,10 @@ #include "Strings/Char.h" #include "Memory/Memory.h" -#include "Strings/String.h" #include "Numerics/Numerics.h" +#include "Strings/String.h" #include "Strings/StringView.h" +#include "Strings/Convert.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN @@ -196,10 +197,11 @@ void TestStringView() { always_check( LITERAL_VIEW(T, "012345678900").IsASCII()); always_check(!LITERAL_VIEW(T, "\u4E38\u8FA3").IsASCII()); - always_check( LITERAL_VIEW(T, "012345678900").IsInteger()); - always_check(!LITERAL_VIEW(T, "\u4E38\u8FA3").IsInteger()); - always_check(!LITERAL_VIEW(T, "0123456789AB").IsInteger()); - always_check( LITERAL_VIEW(T, "0123456789AB").IsInteger(16)); + + always_check( LITERAL_VIEW(T, "012345678900").template IsInteger(10)); + always_check(!LITERAL_VIEW(T, "\u4E38\u8FA3").template IsInteger(10)); + always_check(!LITERAL_VIEW(T, "0123456789AB").template IsInteger(10)); + always_check( LITERAL_VIEW(T, "0123456789AB").template IsInteger(16)); } }; @@ -211,7 +213,7 @@ void TestStringView() Test(InPlaceType); } -void TestTemplateString() +void TestString() { auto Test = [](TInPlaceType) { @@ -448,38 +450,17 @@ void TestTemplateString() Test(InPlaceType); } -void TestStringConversion() +void TestConvert() { auto Test = [](TInPlaceType) { - - always_check(TString::Format(LITERAL(T, "#{}#"), true ) == LITERAL(T, "#True#" )); - always_check(TString::Format(LITERAL(T, "#{}#"), false) == LITERAL(T, "#False#")); - - always_check(TString::Format(LITERAL(T, "#{}#"), +0) == LITERAL(T, "#0#")); - always_check(TString::Format(LITERAL(T, "#{}#"), 0) == LITERAL(T, "#0#")); - always_check(TString::Format(LITERAL(T, "#{}#"), -0) == LITERAL(T, "#0#")); - - always_check(TString::Format(LITERAL(T, "#{}#"), 42) == LITERAL(T, "#42#")); - - always_check(TString::Format(LITERAL(T, "#{}#"), +0.0) == LITERAL(T, "#0.000000#")); - always_check(TString::Format(LITERAL(T, "#{}#"), 0.0) == LITERAL(T, "#0.000000#")); - always_check(TString::Format(LITERAL(T, "#{}#"), -0.0) == LITERAL(T, "#-0.000000#")); - - always_check(TString::Format(LITERAL(T, "#{}#"), 3.14) == LITERAL(T, "#3.140000#")); - - always_check(TString::Format(LITERAL(T, "#{}#"), +TNumericLimits::Infinity()) == LITERAL(T, "#Infinity#")); - always_check(TString::Format(LITERAL(T, "#{}#"), -TNumericLimits::Infinity()) == LITERAL(T, "#-Infinity#")); - always_check(TString::Format(LITERAL(T, "#{}#"), +TNumericLimits::QuietNaN()) == LITERAL(T, "#NaN#")); - always_check(TString::Format(LITERAL(T, "#{}#"), -TNumericLimits::QuietNaN()) == LITERAL(T, "#-NaN#")); - auto CheckParseArithmetic = [](TStringView View, U Result) { U Object; - if constexpr (CSameAs) always_check(View.Parse(LITERAL(T, "{0:}"), Object) == 1); - else if constexpr (CIntegral) always_check(View.Parse(LITERAL(T, "{0:+#I}"), Object) == 1); - else if constexpr (CFloatingPoint) always_check(View.Parse(LITERAL(T, "{0:+#G}"), Object) == 1); + if constexpr (CSameAs) always_check(View.Parse(Object)); + else if constexpr (CIntegral) always_check(View.Parse(Object)); + else if constexpr (CFloatingPoint) always_check(View.Parse(Object)); if constexpr (CFloatingPoint) { @@ -554,6 +535,60 @@ void TestStringConversion() CheckParseFloat(InPlaceType); CheckParseFloat(InPlaceType); + { + always_check( LITERAL_VIEW(T, "true" ).ToBool()); + always_check(!LITERAL_VIEW(T, "false").ToBool()); + always_check( LITERAL_VIEW(T, "True" ).ToBool()); + always_check(!LITERAL_VIEW(T, "False").ToBool()); + } + + { + always_check(LITERAL_VIEW(T, "42" ).ToInt() == 42 ); + always_check(LITERAL_VIEW(T, "FF" ).ToInt(16) == 255); + always_check(LITERAL_VIEW(T, "-42" ).ToInt() == -42); + always_check(LITERAL_VIEW(T, "0" ).ToInt() == 0 ); + } + + { + always_check(LITERAL_VIEW(T, "3.14" ).ToFloat() == 3.14f); + always_check(LITERAL_VIEW(T, "3.14e+00").ToFloat() == 3.14f); + always_check(LITERAL_VIEW(T, "-3.14" ).ToFloat() == -3.14f); + always_check(LITERAL_VIEW(T, "0.0" ).ToFloat() == 0.0f); + } + }; + + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); +} + +void TestStringConversion() +{ + auto Test = [](TInPlaceType) + { + always_check(TString::Format(LITERAL(T, "#{}#"), true ) == LITERAL(T, "#True#" )); + always_check(TString::Format(LITERAL(T, "#{}#"), false) == LITERAL(T, "#False#")); + + always_check(TString::Format(LITERAL(T, "#{}#"), +0) == LITERAL(T, "#0#")); + always_check(TString::Format(LITERAL(T, "#{}#"), 0) == LITERAL(T, "#0#")); + always_check(TString::Format(LITERAL(T, "#{}#"), -0) == LITERAL(T, "#0#")); + + always_check(TString::Format(LITERAL(T, "#{}#"), 42) == LITERAL(T, "#42#")); + + always_check(TString::Format(LITERAL(T, "#{}#"), +0.0) == LITERAL(T, "#0.000000#")); + always_check(TString::Format(LITERAL(T, "#{}#"), 0.0) == LITERAL(T, "#0.000000#")); + always_check(TString::Format(LITERAL(T, "#{}#"), -0.0) == LITERAL(T, "#-0.000000#")); + + always_check(TString::Format(LITERAL(T, "#{}#"), 3.14) == LITERAL(T, "#3.140000#")); + + always_check(TString::Format(LITERAL(T, "#{}#"), +TNumericLimits::Infinity()) == LITERAL(T, "#Infinity#")); + always_check(TString::Format(LITERAL(T, "#{}#"), -TNumericLimits::Infinity()) == LITERAL(T, "#-Infinity#")); + always_check(TString::Format(LITERAL(T, "#{}#"), +TNumericLimits::QuietNaN()) == LITERAL(T, "#NaN#")); + always_check(TString::Format(LITERAL(T, "#{}#"), -TNumericLimits::QuietNaN()) == LITERAL(T, "#-NaN#")); + { always_check(TString::FromBool(true ) == LITERAL(T, "True" )); always_check(TString::FromBool(false) == LITERAL(T, "False")); @@ -579,37 +614,6 @@ void TestStringConversion() always_check(TString::FromFloat(3.14f, false, false, 2) == LITERAL(T, "1.92p+1" )); always_check(TString::FromFloat(1.0f / 3.0f, true, false, 5) == LITERAL(T, "0.33333" )); } - - { - always_check( LITERAL_VIEW(T, "True" ).ToBool()); - always_check(!LITERAL_VIEW(T, "False" ).ToBool()); - always_check( LITERAL_VIEW(T, "1" ).ToBool()); - always_check(!LITERAL_VIEW(T, "0" ).ToBool()); - always_check(!LITERAL_VIEW(T, "random").ToBool()); - } - - { - always_check(LITERAL_VIEW(T, "42" ).ToInt() == 42 ); - always_check(LITERAL_VIEW(T, "FF" ).ToInt(16) == 255); - always_check(LITERAL_VIEW(T, "-42" ).ToInt() == -42); - always_check(LITERAL_VIEW(T, "0" ).ToInt() == 0 ); - always_check(LITERAL_VIEW(T, "Invalid").ToInt() == 0 ); - - always_check(LITERAL_VIEW(T, "999999999999999999999999999999").ToInt() == 0); - always_check(LITERAL_VIEW(T, "-999999999999999999999999999999").ToInt() == 0); - } - - { - always_check(LITERAL_VIEW(T, "3.14" ).ToFloat() == 3.14f); - always_check(LITERAL_VIEW(T, "3.14e+00").ToFloat() == 3.14f); - always_check(LITERAL_VIEW(T, "-3.14" ).ToFloat() == -3.14f); - always_check(LITERAL_VIEW(T, "0.0" ).ToFloat() == 0.0f); - - always_check(Math::IsNaN(LITERAL_VIEW(T, "1e+308").ToFloat())); - always_check(Math::IsNaN(LITERAL_VIEW(T, "-1e+308").ToFloat())); - always_check(Math::IsNaN(LITERAL_VIEW(T, "1e-308").ToFloat())); - always_check(Math::IsNaN(LITERAL_VIEW(T, "-1e-308").ToFloat())); - } }; Test(InPlaceType); @@ -626,7 +630,8 @@ void TestString() { NAMESPACE_PRIVATE::TestChar(); NAMESPACE_PRIVATE::TestStringView(); - NAMESPACE_PRIVATE::TestTemplateString(); + NAMESPACE_PRIVATE::TestString(); + NAMESPACE_PRIVATE::TestConvert(); NAMESPACE_PRIVATE::TestStringConversion(); } diff --git a/Redcraft.Utility/Source/Public/Strings/Conversion.h.inl b/Redcraft.Utility/Source/Public/Strings/Conversion.h.inl index ec99f1d..e07eaba 100644 --- a/Redcraft.Utility/Source/Public/Strings/Conversion.h.inl +++ b/Redcraft.Utility/Source/Public/Strings/Conversion.h.inl @@ -302,7 +302,11 @@ struct TStringObjectFormatter TrimmedFmt.RemovePrefix(Index); - size_t PossibleWidth = TrimmedFmt.template ToIntAndTrim(); + TStringView View = TrimmedFmt.Substr(0, TrimmedFmt.FindFirstNotOf(LITERAL(T, "0123456789"))); + + TrimmedFmt.RemovePrefix(View.Num()); + + size_t PossibleWidth = View.template ToInt(); bool bIsValid = true; @@ -652,7 +656,11 @@ struct TStringObjectFormatter { Fmt.RemovePrefix(1); - Padding = Fmt.template ToIntAndTrim(); + 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])) @@ -661,7 +669,11 @@ struct TStringObjectFormatter bHasBase = true; - Base = Fmt.template ToIntAndTrim(); + 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); } @@ -736,7 +748,11 @@ struct TStringObjectFormatter { Fmt.RemovePrefix(1); - Precision = Fmt.template ToIntAndTrim(); + 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); } @@ -1345,1137 +1361,6 @@ struct TStringObjectFormatter } }; -template -struct TStringObjectParser -{ - static bool Do(auto& View, auto& Object, auto Param) - { - // ReSharper disable once CppInconsistentNaming - using U = TRemoveCVRef; - - if constexpr (CConst>) - { - checkf(false, TEXT("Cannot assign to a variable that is const.")); - - return false; - } - - if (View.IsEmpty()) 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; - - TStringView Subview; - - // Parse the fill-and-align part and reserve the space for the result. - auto ParseFillAndAlign = [&Subview, &View, &Fmt] - { - TStringView FillCharacter = LITERAL(T, " "); - - T AlignmentOption = LITERAL(T, '^'); - - size_t AlignmentWidth = DynamicExtent; - - // 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); - - size_t PossibleWidth = TrimmedFmt.template ToIntAndTrim(); - - 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; - } - } - } - - if (AlignmentWidth > View.Num()) AlignmentWidth = View.Num(); - - TStringView TrimmedView = View; - - if (AlignmentOption != LITERAL(T, '<')) while (AlignmentWidth && TrimmedView.StartsWith(FillCharacter)) - { - TrimmedView.RemovePrefix(FillCharacter.Num()); - - --AlignmentWidth; - } - - Subview = TrimmedView.First(AlignmentWidth); - - return MakeTuple(FillCharacter, AlignmentOption, AlignmentWidth, TrimmedView); - }; - - // Apply the fill-and-align part to the result. - auto ApplyFillAndAlign = [&Subview, &View](auto FillAndAlign) - { - auto [FillCharacter, AlignmentOption, AlignmentWidth, TrimmedView] = FillAndAlign; - - const size_t ParsedNum = AlignmentWidth - Subview.Num(); - - TrimmedView.RemovePrefix(ParsedNum); - - AlignmentWidth -= ParsedNum; - - if (AlignmentOption != LITERAL(T, '>')) while (AlignmentWidth && TrimmedView.StartsWith(FillCharacter)) - { - TrimmedView.RemovePrefix(FillCharacter.Num()); - - --AlignmentWidth; - } - - View = TrimmedView; - }; - - if constexpr (CTString) - { - auto FillAndAlign = ParseFillAndAlign(); - - bool bNeedToCase = false; - bool bStringLowercase = false; - bool bNeedToEscape = false; - bool bEscapeLowercase = false; - - bool bStringSensitive = false; - bool bEscapeSensitive = 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, '='))) { bStringSensitive = true; 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, '='))) { bEscapeSensitive = true; Fmt.RemovePrefix(1); } - } - - if (!Fmt.IsEmpty()) - { - checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); - return false; - } - - TStringView String; - - if (bNeedToEscape) - { - if (Subview.StartsWith(LITERAL(T, '\"'))) Subview.RemovePrefix(1); - else return false; - - size_t EndIndex = Subview.FindFirstOf(LITERAL(T, '\"')); - - if (EndIndex != INDEX_NONE) - { - String = Subview.First(EndIndex); - Subview.RemovePrefix(EndIndex + 1); - } - else return false; - - TString> Buffer; - - Buffer.Reserve(String.Num()); - - while (!String.IsEmpty()) - { - if (String.StartsWith(LITERAL(T, '\\'))) - { - String.RemovePrefix(1); - - if (String.IsEmpty()) return false; - - switch (String.Front()) - { - case LITERAL(T, '\"'): Buffer += LITERAL(T, '\"'); break; - case LITERAL(T, '\\'): Buffer += LITERAL(T, '\\'); break; - case LITERAL(T, 'a'): Buffer += LITERAL(T, '\a'); break; - case LITERAL(T, 'b'): Buffer += LITERAL(T, '\b'); break; - case LITERAL(T, 'f'): Buffer += LITERAL(T, '\f'); break; - case LITERAL(T, 'n'): Buffer += LITERAL(T, '\n'); break; - case LITERAL(T, 'r'): Buffer += LITERAL(T, '\r'); break; - case LITERAL(T, 't'): Buffer += LITERAL(T, '\t'); break; - case LITERAL(T, 'v'): Buffer += LITERAL(T, '\v'); break; - case LITERAL(T, 'x'): - { - String.RemovePrefix(1); - - if (String.IsEmpty()) return false; - - TStringView Digit = String; - - if (String.Num() > sizeof(T) * 2) Digit = Digit.First(sizeof(T) * 2); - - const size_t OldNum = Digit.Num(); - - struct { int DigitStyle; unsigned Base; } DigitParam = { !bEscapeSensitive ? 0 : bEscapeLowercase ? -1 : 1, 16 }; - - TMakeUnsigned IntValue; - - if (!TStringObjectParser::Do(Digit, IntValue, DigitParam)) return false; - - Buffer += static_cast(IntValue); - - String.RemovePrefix(OldNum - Digit.Num()); - - break; - } - default: return false; - } - } - else - { - Buffer += String.Front(); - String.RemovePrefix(1); - } - - if (bStringSensitive && bNeedToCase) - { - if ( bStringLowercase && TChar::IsUpper(Buffer.Back())) return false; - if (!bStringLowercase && TChar::IsLower(Buffer.Back())) return false; - } - } - - Object = TStringView(Buffer); - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - size_t EndIndex = Subview.Find( - [bStringSensitive, bNeedToCase, bStringLowercase](T Char) - { - bool bIsValid = true; - - bIsValid &= !TChar::IsSpace(Char); - - if (bStringSensitive && bNeedToCase) - { - bIsValid &= bStringLowercase || !TChar::IsUpper(Char); - bIsValid &= !bStringLowercase || !TChar::IsLower(Char); - } - - return !bIsValid; - } - ); - - if (EndIndex != INDEX_NONE) - { - String = Subview.First(EndIndex); - Subview.RemovePrefix(EndIndex); - } - else String = Exchange(Subview, TStringView()); - - Object = String; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - // Parse the character value by format string. - else if constexpr (CCharType) - { - if (Fmt.FindFirstOf(LITERAL(T, "Ss")) != INDEX_NONE) - { - TStringView TrimmedView = View; - - TString StringValue; - - if (!TStringObjectParser::Do(TrimmedView, StringValue, Param)) return false; - - if (StringValue.Num() != 1) return false; - - Object = StringValue.Front(); - - View = TrimmedView; - - return true; - } - - if (Fmt.FindFirstOf(LITERAL(T, "BbDdOoXxIi")) != INDEX_NONE) - { - TMakeUnsigned IntValue; - - if (!TStringObjectParser::Do(View, IntValue, Param)) return false; - - Object = static_cast(IntValue); - - return true; - } - - auto FillAndAlign = ParseFillAndAlign(); - - bool bNeedToCase = false; - bool bStringLowercase = false; - bool bNeedToEscape = false; - bool bEscapeLowercase = false; - - bool bStringSensitive = false; - bool bEscapeSensitive = 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, '='))) { bStringSensitive = true; 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, '='))) { bEscapeSensitive = true; Fmt.RemovePrefix(1); } - } - - if (!Fmt.IsEmpty()) - { - checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); - return false; - } - - if (bNeedToEscape) - { - TStringView String; - - if (Subview.StartsWith(LITERAL(T, '\''))) Subview.RemovePrefix(1); - else return false; - - size_t EndIndex = Subview.FindFirstOf(LITERAL(T, '\'')); - - if (EndIndex != INDEX_NONE) - { - String = Subview.First(EndIndex); - Subview.RemovePrefix(EndIndex + 1); - } - else return false; - - T Buffer; - - if (Subview.IsEmpty()) return false; - - if (Subview.StartsWith(LITERAL(T, '\\'))) - { - Subview.RemovePrefix(1); - - if (Subview.IsEmpty()) return false; - - switch (Subview.Front()) - { - case LITERAL(T, '\"'): Buffer = LITERAL(T, '\"'); break; - case LITERAL(T, '\\'): Buffer = LITERAL(T, '\\'); break; - case LITERAL(T, 'a'): Buffer = LITERAL(T, '\a'); break; - case LITERAL(T, 'b'): Buffer = LITERAL(T, '\b'); break; - case LITERAL(T, 'f'): Buffer = LITERAL(T, '\f'); break; - case LITERAL(T, 'n'): Buffer = LITERAL(T, '\n'); break; - case LITERAL(T, 'r'): Buffer = LITERAL(T, '\r'); break; - case LITERAL(T, 't'): Buffer = LITERAL(T, '\t'); break; - case LITERAL(T, 'v'): Buffer = LITERAL(T, '\v'); break; - case LITERAL(T, 'x'): - { - Subview.RemovePrefix(1); - - if (Subview.IsEmpty()) return false; - - TStringView Digit = Subview; - - if (Subview.Num() > sizeof(T) * 2) Digit = Digit.First(sizeof(T) * 2); - - const size_t OldNum = Digit.Num(); - - struct { int DigitStyle; unsigned Base; } DigitParam = { !bEscapeSensitive ? 0 : bEscapeLowercase ? -1 : 1, 16 }; - - TMakeUnsigned IntValue; - - if (!TStringObjectParser::Do(Digit, IntValue, DigitParam)) return false; - - Buffer = static_cast(IntValue); - - Subview.RemovePrefix(OldNum - Digit.Num()); - - break; - } - default: return false; - } - } - else - { - Buffer = Subview.Front(); - Subview.RemovePrefix(1); - } - - if (bStringSensitive && bNeedToCase) - { - if ( bStringLowercase && TChar::IsUpper(Buffer)) return false; - if (!bStringLowercase && TChar::IsLower(Buffer)) return false; - } - - Object = Buffer; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - if (Subview.IsEmpty()) return false; - - T Char = Subview.Front(); - - if (bStringSensitive && bNeedToCase) - { - if ( bStringLowercase && TChar::IsUpper(Char)) return false; - if (!bStringLowercase && TChar::IsLower(Char)) return false; - } - - Object = Char; - - Subview.RemovePrefix(1); - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - // Parse the boolean value by format string. - else if constexpr (CSameAs) - { - if (Fmt.IsEmpty()) - { - auto FillAndAlign = ParseFillAndAlign(); - - if (!TStringObjectParser::Do(Subview, Object, Invalid)) return false; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - if (Fmt.FindFirstOf(LITERAL(T, 'S')) != INDEX_NONE) - { - TStringView TrimmedView = View; - - TString StringValue; - - if (!TStringObjectParser::Do(Subview, StringValue, Param)) return false; - - if (StringValue == LITERAL(T, "True")) { Object = true; View = TrimmedView; return true; } - if (StringValue == LITERAL(T, "False")) { Object = false; View = TrimmedView; return true; } - - return false; - } - - if (Fmt.FindFirstOf(LITERAL(T, 's')) != INDEX_NONE) - { - TStringView TrimmedView = View; - - TString StringValue; - - if (!TStringObjectParser::Do(Subview, StringValue, Param)) return false; - - if (StringValue == LITERAL(T, "true")) { Object = true; View = TrimmedView; return true; } - if (StringValue == LITERAL(T, "false")) { Object = false; View = TrimmedView; return true; } - - return false; - } - - if (Fmt.FindFirstOf(LITERAL(T, 'C')) != INDEX_NONE) - { - TStringView TrimmedView = View; - - T CharacterValue; - - if (!TStringObjectParser::Do(Subview, CharacterValue, Param)) return false; - - if (CharacterValue == LITERAL(T, 'T')) { Object = true; View = TrimmedView; return true; } - if (CharacterValue == LITERAL(T, 'F')) { Object = false; View = TrimmedView; return true; } - - return false; - } - - if (Fmt.FindFirstOf(LITERAL(T, 'c')) != INDEX_NONE) - { - TStringView TrimmedView = View; - - T CharacterValue; - - if (!TStringObjectParser::Do(Subview, CharacterValue, Param)) return false; - - if (CharacterValue == LITERAL(T, 't')) { Object = true; View = TrimmedView; return true; } - if (CharacterValue == LITERAL(T, 'f')) { Object = false; View = TrimmedView; return true; } - - return false; - } - - if (Fmt.FindFirstOf(LITERAL(T, "BbDdOoXxIi")) != INDEX_NONE) - { - int IntValue = Object ? 1 : 0; - - if (!TStringObjectParser::Do(Subview, IntValue, Param)) return false; - - Object = IntValue != 0; - - return true; - } - - checkf(false, TEXT("Illegal format string. Redundant unknown characters.")); - return false; - } - - // Parse the integer value by format string. - else if constexpr (CIntegral && !CSameAs) - { - auto FillAndAlign = ParseFillAndAlign(); - - if (Fmt.IsEmpty()) - { - if (!TStringObjectParser::Do(Subview, Object, Invalid)) return false; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - T PositiveIndicator = LITERAL(T, '-'); - - bool bPrefix = false; - - unsigned Padding = 0; - - bool bHasBase = false; - - unsigned Base = 0; - - bool bDigitLowercase = false; - bool bOtherLowercase = true; - - bool bSensitive = 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, '0')) && Fmt.Num() > 1 && TChar::IsDigit(Fmt[1]) && Fmt[1] != LITERAL(T, '0')) - { - Fmt.RemovePrefix(1); - - Padding = Fmt.template ToIntAndTrim(); - } - - if (Fmt.StartsWith(LITERAL(T, '_')) && Fmt.Num() > 1 && TChar::IsDigit(Fmt[1])) - { - Fmt.RemovePrefix(1); - - bHasBase = true; - - Base = Fmt.template ToIntAndTrim(); - } - - 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, '='))) { bSensitive = true; 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 = - { - !bSensitive ? 0 : bDigitLowercase ? -1 : 1, - !bSensitive ? 0 : bOtherLowercase ? -1 : 1, - PositiveIndicator, - bPrefix, - Padding, - Base == 0 ? bPrefix ? 0 : 10 : Base, - }; - - if (!TStringObjectParser::Do(Subview, Object, IntParam)) return false; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - // Parse the floating-point value by format string. - else if constexpr (CFloatingPoint) - { - auto FillAndAlign = ParseFillAndAlign(); - - if (Fmt.IsEmpty()) - { - if (!TStringObjectParser::Do(Subview, Object, Invalid)) return false; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - T PositiveIndicator = LITERAL(T, '-'); - - bool bPrefix = false; - - int Precision = -1; - - bool bDigitLowercase = false; - bool bOtherLowercase = true; - - bool bFixed = true; - bool bScientific = false; - - bool bSensitive = 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); - - Precision = Fmt.template ToIntAndTrim(); - } - - 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, '='))) { bSensitive = true; 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, - !bSensitive ? 0 : bDigitLowercase ? -1 : 1, - !bSensitive ? 0 : bOtherLowercase ? -1 : 1, - PositiveIndicator, - bPrefix, - }; - - if (!TStringObjectParser::Do(Subview, Object, FloatParam)) return false; - - ApplyFillAndAlign(FillAndAlign); - - return true; - } - - else static_assert(sizeof(U) == -1, "Unsupported object type."); - } - else - { - // Parse the boolean value by structured parameters. - if constexpr (CSameAs) - { - constexpr bool bHasDigitStyle = requires { { Param.DigitStyle } -> CConvertibleTo; }; - constexpr bool bHasOtherStyle = requires { { Param.OtherStyle } -> CConvertibleTo; }; - - if (View.Num() < 4) return false; - - if constexpr (bHasDigitStyle || bHasOtherStyle) - { - TOptional Result; - - int DigitStyle = 0; if constexpr (bHasDigitStyle) DigitStyle = Param.DigitStyle; - int OtherStyle = 0; if constexpr (bHasOtherStyle) OtherStyle = Param.OtherStyle; - - if (DigitStyle <= 0 && OtherStyle <= 0) - { - if (View.StartsWith(LITERAL(T, "true"))) Result = true; - if (View.StartsWith(LITERAL(T, "false"))) Result = false; - } - - if (DigitStyle >= 0 && OtherStyle <= 0) - { - if (View.StartsWith(LITERAL(T, "True"))) Result = true; - if (View.StartsWith(LITERAL(T, "False"))) Result = false; - } - - if (DigitStyle <= 0 && OtherStyle >= 0) - { - if (View.StartsWith(LITERAL(T, "tRUE"))) Result = true; - if (View.StartsWith(LITERAL(T, "fALSE"))) Result = false; - } - - if (DigitStyle >= 0 && OtherStyle >= 0) - { - if (View.StartsWith(LITERAL(T, "TRUE"))) Result = true; - if (View.StartsWith(LITERAL(T, "FALSE"))) Result = false; - } - - if (Result.IsValid()) - { - View.RemovePrefix(*Result == true ? 4 : 5); - Object = *Result; - return true; - } - - return false; - } - - if (View.StartsWith(LITERAL(T, "true")) - || View.StartsWith(LITERAL(T, "True")) - || View.StartsWith(LITERAL(T, "tRUE")) - || View.StartsWith(LITERAL(T, "TRUE"))) - { - View.RemovePrefix(4); - Object = true; - return true; - } - - if (View.StartsWith(LITERAL(T, "false")) - || View.StartsWith(LITERAL(T, "False")) - || View.StartsWith(LITERAL(T, "fALSE")) - || View.StartsWith(LITERAL(T, "FALSE"))) - { - View.RemovePrefix(5); - Object = false; - return true; - } - - return false; - } - - // Parse 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; }; - - static_assert(TChar::IsASCII()); - - // Create a temporary view to avoid modifying the original view. - TStringView TrimmedView = View; - - bool bNegative = false; - - // Handle optional negative sign. - if constexpr (CSigned) - { - if (TrimmedView.StartsWith(LITERAL(T, '-'))) - { - bNegative = true; - TrimmedView.RemovePrefix(1); - } - } - - // Handle optional positive sign. - if constexpr (bHasSign) if (!bNegative && Param.PositiveSign != LITERAL(T, '-')) if (TrimmedView.StartsWith(Param.PositiveSign)) TrimmedView.RemovePrefix(1); - - unsigned Base; if constexpr (bHasBase) Base = Param.Base; else Base = 10; - - // Handle optional prefix. - if constexpr (bHasPrefix) if (Param.bPrefix) - { - int OtherStyle = 0; if constexpr (bHasOtherStyle) OtherStyle = Param.OtherStyle; - - // Auto detect base. - if (Base == 0) - { - if (TrimmedView.Num() >= 2 && TrimmedView.Front() == LITERAL(T, '0')) - { - if ((OtherStyle <= 0 && TrimmedView[1] == LITERAL(T, 'x')) - || (OtherStyle >= 0 && TrimmedView[1] == LITERAL(T, 'X'))) - { - Base = 16; - TrimmedView.RemovePrefix(2); - } - else if ((OtherStyle <= 0 && TrimmedView[1] == LITERAL(T, 'b')) - || (OtherStyle >= 0 && TrimmedView[1] == LITERAL(T, 'B'))) - { - Base = 2; - TrimmedView.RemovePrefix(2); - } - else if (TChar::IsDigit(TrimmedView.Front(), 8)) Base = 8; - } - - if (Base == 0) Base = 10; - } - - // Handle prefixes with known base. - else if (Base == 2 || Base == 8 || Base == 10 || Base == 16) - { - bool bNeedRemove = false; - - bNeedRemove |= OtherStyle <= 0 && Base == 2 && TrimmedView.StartsWith(LITERAL(T, "0b")); - bNeedRemove |= OtherStyle >= 0 && Base == 2 && TrimmedView.StartsWith(LITERAL(T, "0B")); - - bNeedRemove |= OtherStyle <= 0 && Base == 16 && TrimmedView.StartsWith(LITERAL(T, "0x")); - bNeedRemove |= OtherStyle >= 0 && Base == 16 && TrimmedView.StartsWith(LITERAL(T, "0X")); - - if (bNeedRemove) TrimmedView.RemovePrefix(2); - } - - // Illegal base for prefix. - else checkf(false, TEXT("Prefix is only supported for binary, octal, decimal and hexadecimal value.")); - } - - checkf(Base >= 2 && Base <= 36, TEXT("Illegal base.")); - - auto ToDigit = [=](T Char) -> unsigned - { - if constexpr (bHasDigitStyle) if (Param.DigitStyle != 0) - { - return TChar::ToDigit(Char, Param.DigitStyle < 0); - } - - return TChar::ToDigit(Char); - }; - - using FUnsignedU = TMakeUnsigned; - - // The limit value that can be stored in an unsigned integer. - constexpr FUnsignedU UnsignedMaximum = static_cast(-1); - - // The limit value that can be stored in a signed integer. - constexpr U SignedMaximum = static_cast(UnsignedMaximum >> 1); - constexpr U SignedMinimum = -static_cast(SignedMaximum) - 1; - - FUnsignedU LastValue = 0; - FUnsignedU Unsigned = 0; - - if (TrimmedView.IsEmpty()) return false; - - unsigned Digit; - - Digit = ToDigit(TrimmedView.Front()); - - // The first character must be a digit. - if (Digit >= Base) return false; - - TrimmedView.RemovePrefix(1); - - Unsigned = static_cast(Digit); - - while (!TrimmedView.IsEmpty()) - { - Digit = ToDigit(TrimmedView.Front()); - - if (Digit >= Base) break; - - TrimmedView.RemovePrefix(1); - - LastValue = Unsigned; - - Unsigned = static_cast(LastValue * Base + Digit); - - if (Unsigned < LastValue) return false; - } - - View = TrimmedView; - - if constexpr (CSigned) - { - // Handle overflow. - if (!bNegative && Unsigned >= static_cast(SignedMaximum)) return false; - if ( bNegative && Unsigned >= static_cast(SignedMinimum)) return false; - - // Handle negative sign. - if (bNegative) Unsigned = static_cast(-Unsigned); - } - - Object = static_cast(Unsigned); - 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; - - // Create a temporary view to avoid modifying the original view. - TStringView TrimmedView = View; - - bool bNegative = false; - - if (TrimmedView.StartsWith(LITERAL(T, '-'))) - { - bNegative = true; - TrimmedView.RemovePrefix(1); - } - - // Handle optional positive sign. - else if constexpr (bHasSign) if (Param.PositiveSign != LITERAL(T, '-')) if (TrimmedView.StartsWith(Param.PositiveSign)) TrimmedView.RemovePrefix(1); - - int DigitStyle = 0; if constexpr (bHasDigitStyle) DigitStyle = Param.DigitStyle; - int OtherStyle = 0; if constexpr (bHasOtherStyle) OtherStyle = Param.OtherStyle; - - // Handle the infinity and NaN values. - do - { - const U Infinity = bNegative ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); - const U NaN = bNegative ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); - - if constexpr (bHasDigitStyle || bHasOtherStyle) - { - TOptional Result; - - if (DigitStyle <= 0 && OtherStyle <= 0) - { - if (TrimmedView.StartsWith(LITERAL(T, "infinity"))) Result = Infinity; - if (TrimmedView.StartsWith(LITERAL(T, "nan"))) Result = NaN; - } - - if (DigitStyle >= 0 && OtherStyle <= 0) - { - if (TrimmedView.StartsWith(LITERAL(T, "Infinity"))) Result = Infinity; - if (TrimmedView.StartsWith(LITERAL(T, "NaN"))) Result = NaN; - } - - if (DigitStyle <= 0 && OtherStyle >= 0) - { - if (TrimmedView.StartsWith(LITERAL(T, "iNFINITY"))) Result = Infinity; - if (TrimmedView.StartsWith(LITERAL(T, "nAn"))) Result = NaN; - } - - if (DigitStyle >= 0 && OtherStyle >= 0) - { - if (TrimmedView.StartsWith(LITERAL(T, "INFINITY"))) Result = Infinity; - if (TrimmedView.StartsWith(LITERAL(T, "NAN"))) Result = NaN; - } - - if (Result.IsValid()) - { - TrimmedView.RemovePrefix(NAMESPACE_STD::isnan(*Result) ? 3 : 8); - Object = *Result; - return true; - } - - break; - } - - if (TrimmedView.StartsWith(LITERAL(T, "infinity")) - || TrimmedView.StartsWith(LITERAL(T, "Infinity")) - || TrimmedView.StartsWith(LITERAL(T, "iNFINITY")) - || TrimmedView.StartsWith(LITERAL(T, "INFINITY"))) - { - TrimmedView.RemovePrefix(8); - Object = Infinity; - return true; - } - - if (TrimmedView.StartsWith(LITERAL(T, "nan")) - || TrimmedView.StartsWith(LITERAL(T, "NaN")) - || TrimmedView.StartsWith(LITERAL(T, "nAn")) - || TrimmedView.StartsWith(LITERAL(T, "NAN"))) - { - TrimmedView.RemovePrefix(3); - Object = NaN; - return true; - } - } - while (false); - - bool bHex = Format == NAMESPACE_STD::chars_format::hex; - - // Handle the prefix. - if constexpr (bHasPrefix) if (Param.bPrefix) - { - bool bNeedRemove = false; - - bNeedRemove |= OtherStyle <= 0 && TrimmedView.StartsWith(LITERAL(T, "0x")); - bNeedRemove |= OtherStyle >= 0 && TrimmedView.StartsWith(LITERAL(T, "0X")); - - if (bNeedRemove) - { - bHex = true; - TrimmedView.RemovePrefix(2); - Format = NAMESPACE_STD::chars_format::hex; - } - } - - auto Iter = TrimmedView.Begin(); - - do - { - auto IsDigit = [=](T Char, unsigned Base) -> unsigned - { - if constexpr (bHasDigitStyle) if (Param.DigitStyle != 0) - { - return TChar::ToDigit(Char, Param.DigitStyle < 0) < Base; - } - - return TChar::ToDigit(Char) < Base; - }; - - // Handle the number before the decimal point. - while (Iter != View.End() && IsDigit(*Iter, bHex ? 16 : 10)) ++Iter; - - // Handle the decimal point. - if (Iter != View.End() && *Iter == LITERAL(T, '.')) ++Iter; - - // Handle the number after the decimal point. - if constexpr (bHasPrecision) - { - unsigned Precision = static_cast(Param.Precision); - - for (size_t Index = 0; Index != Precision; ++Index) - { - if (Iter != View.End() && IsDigit(*Iter, bHex ? 16 : 10)) ++Iter; else break; - } - } - else while (Iter != View.End() && IsDigit(*Iter, bHex ? 16 : 10)) ++Iter; - - const bool bScientific = static_cast(Format & NAMESPACE_STD::chars_format::scientific); - - // Handle the scientific notation. - if (Iter != View.End()) - { - bool bNeedRemove = false; - - bNeedRemove |= OtherStyle <= 0 && bHex && *Iter == LITERAL(T, 'p'); - bNeedRemove |= OtherStyle >= 0 && bHex && *Iter == LITERAL(T, 'P'); - - bNeedRemove |= OtherStyle <= 0 && bScientific && *Iter == LITERAL(T, 'e'); - bNeedRemove |= OtherStyle >= 0 && bScientific && *Iter == LITERAL(T, 'E'); - - if (bNeedRemove) ++Iter; - } - - // Handle the sign of the exponent. - if (Iter != View.End() && *Iter == LITERAL(T, '+')) ++Iter; - if (Iter != View.End() && *Iter == LITERAL(T, '-')) ++Iter; - - // Handle the number of the exponent. - while (Iter != View.End() && IsDigit(*Iter, 10)) ++Iter; - } - while (false); - - U Result; - - NAMESPACE_STD::from_chars_result ConvertResult; - - TString> Buffer; - - if (bNegative) Buffer += '-'; - - Buffer.Append(TrimmedView.Begin(), Iter); - - ConvertResult = NAMESPACE_STD::from_chars(ToAddress(Buffer.Begin()), ToAddress(Buffer.End()), Result, Format); - - if (ConvertResult.ec == NAMESPACE_STD::errc::result_out_of_range) return false; - if (ConvertResult.ec == NAMESPACE_STD::errc::invalid_argument) return false; - - size_t Num = ConvertResult.ptr - Buffer.GetData(); - - check(Num != 0); - - if (bNegative) Num -= 1; - - View = TrimmedView.RemovePrefix(Num); - - Object = Result; - return true; - } - - else static_assert(sizeof(U) == -1, "Unsupported object type."); - } - - checkf(false, TEXT("Unsupported type for parsing.")); - - return false; - } -}; - template struct TStringFormatOrParseHelper { @@ -2615,7 +1500,7 @@ struct TStringFormatOrParseHelper if (IndexLength != 0) { - if (!PlaceholderIndex.IsInteger(10, false)) + if (!PlaceholderIndex.template IsInteger(10)) { checkf(false, TEXT("Invalid placeholder index.")); @@ -2631,7 +1516,7 @@ struct TStringFormatOrParseHelper continue; } - else Index = PlaceholderIndex.template ToInt(); + else Index = PlaceholderIndex.template ToInt(); } else Index = ArgsIndex++; @@ -2644,7 +1529,7 @@ struct TStringFormatOrParseHelper if constexpr (bIsFormat) return TStringObjectFormatter::Do(String, Object, Param); - else return TStringObjectParser::Do(String, Object, Param); + else static_assert(bIsFormat, "Parsing is not supported."); }, Index ); @@ -2719,13 +1604,6 @@ void TString::AppendFormat(TStringView Fmt, const Ts Append(Result.Begin(), Result.End()); } -template -template -size_t TStringView::ParseAndTrim(TStringView Fmt, Ts&... Args) -{ - return NAMESPACE_PRIVATE::TStringFormatOrParseHelper::Do(*this, Fmt, ForwardAsTuple(Args...)); -} - template Allocator> void TString::AppendBool(bool Value) { @@ -2765,50 +1643,6 @@ void TString::AppendFloat(U Value, bool bFixed, bool bScientific, NAMESPACE_PRIVATE::TStringObjectFormatter::Do(*this, AsConst(Value), Param); } -template -constexpr bool TStringView::ToBoolAndTrim() -{ - bool Value = false; - - if (!NAMESPACE_PRIVATE::TStringObjectParser::Do(*this, Value, Invalid)) - { - if (int IntValue; NAMESPACE_PRIVATE::TStringObjectParser::Do(*this, IntValue, Invalid)) - { - Value = IntValue != 0; - } - } - - return Value; -} - -template -template requires (!CSameAs && !CConst && !CVolatile) -constexpr U TStringView::ToIntAndTrim(unsigned Base) -{ - checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); - - U Value = 0; - - struct { unsigned Base; } Param = { Base }; - - NAMESPACE_PRIVATE::TStringObjectParser::Do(*this, Value, Param); - - return Value; -} - -template -template requires (!CConst && !CVolatile) -constexpr U TStringView::ToFloatAndTrim(bool bFixed, bool bScientific) -{ - U Value = NAMESPACE_STD::numeric_limits::quiet_NaN(); - - struct { bool bFixed; bool bScientific; } Param = { bFixed, bScientific }; - - NAMESPACE_PRIVATE::TStringObjectParser::Do(*this, Value, Param); - - return Value; -} - NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Strings/Convert.h b/Redcraft.Utility/Source/Public/Strings/Convert.h new file mode 100644 index 0000000..8ee8a62 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Strings/Convert.h @@ -0,0 +1,487 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" +#include "Templates/Utility.h" +#include "Ranges/Utility.h" +#include "Numerics/Limits.h" +#include "Algorithms/Basic.h" +#include "Memory/Allocators.h" +#include "Memory/Address.h" +#include "Containers/Array.h" +#include "Strings/Char.h" +#include "Miscellaneous/AssertionMacros.h" + +#include + +#pragma warning(push) +#pragma warning(disable : 4146) + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +template +concept CStringRange = CInputRange && CCharType>; + +template +concept CStringIterator = CInputIterator && CCharType>; + +NAMESPACE_BEGIN(Algorithms) + +/** + * Parses a boolean value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * + * - "True" become true. + * - "False" become false. + * + * @param Range - The range of characters to parse. + * @param Value - The boolean value to parse. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template +constexpr bool Parse(R&& Range, bool& Value) +{ + using FCharTraits = TChar>; + + if constexpr (CSizedRange) + { + checkf(Algorithms::Distance(Range) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Range).")); + } + + auto Iter = Ranges::Begin(Range); + auto Sent = Ranges::End (Range); + + bool Result; + + // Ignore leading spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter == Sent) return false; + + // Parse the true value. + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 't') || *Iter == LITERAL(TRangeElement, 'T'))) + { + ++Iter; + + Result = true; + + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'r') || *Iter == LITERAL(TRangeElement, 'R'))) ++Iter; else return false; + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'u') || *Iter == LITERAL(TRangeElement, 'U'))) ++Iter; else return false; + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'e') || *Iter == LITERAL(TRangeElement, 'E'))) ++Iter; else return false; + } + + // Parse the false value. + else if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'f') || *Iter == LITERAL(TRangeElement, 'F'))) + { + ++Iter; + + Result = false; + + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'a') || *Iter == LITERAL(TRangeElement, 'A'))) ++Iter; else return false; + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'l') || *Iter == LITERAL(TRangeElement, 'L'))) ++Iter; else return false; + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 's') || *Iter == LITERAL(TRangeElement, 'S'))) ++Iter; else return false; + if (Iter != Sent && (*Iter == LITERAL(TRangeElement, 'e') || *Iter == LITERAL(TRangeElement, 'E'))) ++Iter; else return false; + } + + else return false; + + // Ignore trailing spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + Value = Result; + + return true; +} + +/** + * Parses a boolean value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * + * - "True" become true. + * - "False" become false. + * + * @param First - The iterator of the range. + * @param Last - The sentinel of the range. + * @param Value - The boolean value to parse. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template S> +FORCEINLINE constexpr bool Parse(I First, S Last, bool& Value) +{ + if constexpr (CSizedSentinelFor) + { + checkf(First - Last <= 0, TEXT("Illegal range iterator. Please check First <= Last.")); + } + + return Algorithms::Parse(Ranges::View(MoveTemp(First), Last), Value); +} + +/** + * Parses an integral value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * If the ingeter value is unsigned, the negative sign causes the parsing to fail. + * Allow parsing base prefixes: "0x" for hexadecimal, "0b" for binary, and "0" for octal. + * + * @param Range - The range of characters to parse. + * @param Value - The integral value to parse. + * @param Base - The base of the number, between [2, 36], or 0 for auto-detect. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template requires (!CConst && !CVolatile && !CSameAs) +constexpr bool Parse(R&& Range, T& Value, uint Base = 0) +{ + using FCharTraits = TChar>; + + checkf(Base == 0 || (Base >= 2 && Base <= 36), TEXT("Illegal base. Please check the Base.")); + + if constexpr (CSizedRange) + { + checkf(Algorithms::Distance(Range) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Range).")); + } + + auto Iter = Ranges::Begin(Range); + auto Sent = Ranges::End (Range); + + // Ignore leading spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter == Sent) return false; + + bool bNegative = false; + + // Parse the negative sign. + if constexpr (CSigned) + { + if (*Iter == LITERAL(TRangeElement, '-')) + { + bNegative = true; + ++Iter; + } + } + + // Parse the positive sign. + if (!bNegative && *Iter == LITERAL(TRangeElement, '+')) ++Iter; + + // Auto-detect the base. + if (Base == 0) + { + if (Iter == Sent) return false; + + if (*Iter == LITERAL(TRangeElement, '0')) + { + ++Iter; + + // Return zero if the string has only one zero. + if (Iter == Sent || FCharTraits::IsSpace(*Iter)) + { + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + Value = 0; + + return true; + } + + if (*Iter == LITERAL(TRangeElement, 'x') || *Iter == LITERAL(TRangeElement, 'X')) + { + Base = 16; + ++Iter; + } + + else if (*Iter == LITERAL(TRangeElement, 'b') || *Iter == LITERAL(TRangeElement, 'B')) + { + Base = 2; + ++Iter; + } + + else if (FCharTraits::IsDigit(*Iter, 8)) Base = 8; + + else return false; + } + + else Base = 10; + } + + // Parse the base prefix. + else if (Base == 2 || Base == 16) + { + if (Iter == Sent) return false; + + if (*Iter == LITERAL(TRangeElement, '0')) + { + ++Iter; + + // Return zero if the string has only one zero. + if (Iter == Sent || FCharTraits::IsSpace(*Iter)) + { + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + Value = 0; + + return true; + } + + if (Base == 16 && (*Iter == LITERAL(TRangeElement, 'x') || *Iter == LITERAL(TRangeElement, 'X'))) ++Iter; + if (Base == 2 && (*Iter == LITERAL(TRangeElement, 'b') || *Iter == LITERAL(TRangeElement, 'B'))) ++Iter; + } + } + + if (Iter == Sent) return false; + + check(Base >= 2 && Base <= 36); + + if (!FCharTraits::IsDigit(*Iter, Base)) return false; + + using FUnsignedT = TMakeUnsigned; + + FUnsignedT LastValue = 0; + FUnsignedT Unsigned = 0; + + do + { + uint Digit = FCharTraits::ToDigit(*Iter); + + // Break if the char is not a digit. + if (Digit >= Base) break; + + ++Iter; + + LastValue = Unsigned; + + Unsigned = LastValue * Base + Digit; + + // Fail if the value is overflowed. + if (Unsigned < LastValue) return false; + } + while (Iter != Sent); + + // Ignore trailing spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + if constexpr (CSigned) + { + // Fail if the value is overflowed. + if (!bNegative && Unsigned >= static_cast(TNumericLimits::Max())) return false; + if ( bNegative && Unsigned >= static_cast(TNumericLimits::Min())) return false; + + // Reverse if the value is negative. + if (bNegative) Unsigned = -Unsigned; + } + + Value = Unsigned; + + return true; +} + +/** + * Parses an integral value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * If the ingeter value is unsigned, the negative sign causes the parsing to fail. + * Allow parsing base prefixes: "0x" for hexadecimal, "0b" for binary, and "0" for octal. + * + * @param First - The iterator of the range. + * @param Last - The sentinel of the range. + * @param Value - The integral value to parse. + * @param Base - The base of the number, between [2, 36], or 0 for auto-detect. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template S, CIntegral T> requires (!CConst && !CVolatile && !CSameAs) +FORCEINLINE constexpr bool Parse(I First, S Last, T& Value, uint Base = 0) +{ + if constexpr (CSizedSentinelFor) + { + checkf(First - Last <= 0, TEXT("Illegal range iterator. Please check First <= Last.")); + } + + return Algorithms::Parse(Ranges::View(MoveTemp(First), Last), Value, Base); +} + +/** + * Parses a floating-point value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * Automatically detect formats if multiple formats are allowed. + * Allow parsing base prefixes: "0x" for hexadecimal. + * + * @param Range - The range of characters to parse. + * @param Value - The floating-point value to parse. + * @param bFixed - Allow parsing fixed-point values. + * @param bScientific - Allow parsing scientific notation values. + * @param bHex - Allow parsing hex floating-point values. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template requires (!CConst && !CVolatile) +constexpr bool Parse(R&& Range, T& Value, bool bFixed = true, bool bScientific = true, bool bHex = true) +{ + if (!bFixed && !bScientific && !bHex) return false; + + using FCharTraits = TChar>; + + if constexpr (CSizedRange) + { + checkf(Algorithms::Distance(Range) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Range).")); + } + + auto Iter = Ranges::Begin(Range); + auto Sent = Ranges::End (Range); + + // Ignore leading spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter == Sent) return false; + + bool bNegative = false; + + // Parse the negative sign. + if (*Iter == LITERAL(TRangeElement, '-')) + { + bNegative = true; + ++Iter; + } + + // Parse the positive sign. + else if (*Iter == LITERAL(TRangeElement, '+')) ++Iter; + + if (Iter == Sent) return false; + + // Fail if the string has multiple signs. + if (*Iter == LITERAL(TRangeElement, '-')) return false; + if (*Iter == LITERAL(TRangeElement, '+')) return false; + + NAMESPACE_STD::chars_format Format = NAMESPACE_STD::chars_format::general; + + if ( bFixed && !bScientific) Format = NAMESPACE_STD::chars_format::fixed; + else if (!bFixed && bScientific) Format = NAMESPACE_STD::chars_format::scientific; + else if (!bFixed && !bScientific) Format = NAMESPACE_STD::chars_format::hex; + + // Auto-detect the hex format. + if (bHex) + { + if (*Iter == LITERAL(TRangeElement, '0')) + { + ++Iter; + + // Return zero if the string has only one zero. + if (Iter == Sent || FCharTraits::IsSpace(*Iter)) + { + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + Value = static_cast(bNegative ? -0.0 : 0.0); + + return true; + } + + if (*Iter == LITERAL(TRangeElement, 'x') || *Iter == LITERAL(TRangeElement, 'X')) + { + Format = NAMESPACE_STD::chars_format::hex; + ++Iter; + } + } + } + + if (Iter == Sent) return false; + + T Result; + + // Copy to a buffer if the range is not contiguous. + if constexpr (!CContiguousRange || !CSameAs, char>) + { + TArray> Buffer; + + for (; Iter != Sent; ++Iter) + { + auto Char = *Iter; + + // Ignore trailing spaces. + if (FCharTraits::IsSpace(Char)) break; + + // Assert that floating-point values must be represented by ASCII. + if (FCharTraits::IsASCII(Char)) Buffer.PushBack(static_cast(Char)); + + else return false; + } + + const char* First = Buffer.GetData(); + const char* Last = Buffer.GetData() + Buffer.Num(); + + NAMESPACE_STD::from_chars_result ConvertResult = NAMESPACE_STD::from_chars(First, Last, Result, Format); + + if (ConvertResult.ec == NAMESPACE_STD::errc::result_out_of_range) return false; + if (ConvertResult.ec == NAMESPACE_STD::errc::invalid_argument) return false; + + // Assert that the buffer is fully parsed. + if (ConvertResult.ptr != Last) return false; + } + + else + { + const char* First = ToAddress(Iter); + const char* Last = ToAddress(Iter) + Algorithms::Distance(Iter, Sent); + + NAMESPACE_STD::from_chars_result ConvertResult = NAMESPACE_STD::from_chars(First, Last, Result, Format); + + if (ConvertResult.ec == NAMESPACE_STD::errc::result_out_of_range) return false; + if (ConvertResult.ec == NAMESPACE_STD::errc::invalid_argument) return false; + + // Move the iterator to the end of the parsed value. + Algorithms::Advance(Iter, ConvertResult.ptr - First); + } + + // Ignore trailing spaces. + while (Iter != Sent && FCharTraits::IsSpace(*Iter)) ++Iter; + + if (Iter != Sent) return false; + + Value = bNegative ? -Result : Result; + + return true; +} + +/** + * Parses a floating-point value from the given string range. + * Ignore leading and trailing spaces and case-insensitive. + * Automatically detect formats if multiple formats are allowed. + * Allow parsing base prefixes: "0x" for hexadecimal. + * + * @param First - The iterator of the range. + * @param Last - The sentinel of the range. + * @param Value - The floating-point value to parse. + * @param bFixed - Allow parsing fixed-point values. + * @param bScientific - Allow parsing scientific notation values. + * @param bHex - Allow parsing hex floating-point values. + * + * @return true if the value is successfully parsed, false otherwise. + */ +template S, CFloatingPoint T> requires (!CConst && !CVolatile) +FORCEINLINE constexpr bool Parse(I First, S Last, T& Value, bool bFixed = true, bool bScientific = true, bool bHex = true) +{ + if constexpr (CSizedSentinelFor) + { + checkf(First - Last <= 0, TEXT("Illegal range iterator. Please check First <= Last.")); + } + + return Algorithms::Parse(Ranges::View(MoveTemp(First), Last), Value, bFixed, bScientific, bHex); +} + +NAMESPACE_END(Algorithms) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END + +#pragma warning(pop) diff --git a/Redcraft.Utility/Source/Public/Strings/String.h b/Redcraft.Utility/Source/Public/Strings/String.h index 1dcd90b..10b54b3 100644 --- a/Redcraft.Utility/Source/Public/Strings/String.h +++ b/Redcraft.Utility/Source/Public/Strings/String.h @@ -1144,22 +1144,64 @@ public: return TStringView(*this).IsASCII(); } - /** @return true if the string can be fully represented as a boolean value, false otherwise. */ + /** @return true if the string can be converted to a boolean value, false otherwise. */ NODISCARD FORCEINLINE bool IsBoolean() const { return TStringView(*this).IsBoolean(); } - /** @return true if the string can be fully represented as an integer value, false otherwise. */ - NODISCARD FORCEINLINE bool IsInteger(unsigned Base = 10, bool bSigned = true) const + /** @return true if the string can be converted to an integer value, false otherwise. */ + template requires (!CSameAs && CSameAs, U>) + NODISCARD FORCEINLINE bool IsInteger(uint Base = 0) const { - return TStringView(*this).IsInteger(Base, bSigned); + return TStringView(*this).template IsInteger(Base); } - /** @return true if the string can be fully represented as a floating-point value, false otherwise. */ - NODISCARD FORCEINLINE bool IsFloatingPoint(bool bFixed = true, bool bScientific = true, bool bSigned = true) const + /** @return true if the string can be converted to a floating-point value, false otherwise. */ + template requires (!CSameAs && CSameAs, U>) + NODISCARD FORCEINLINE bool IsFloatingPoint(bool bFixed = true, bool bScientific = true, bool bHex = true) const { - return TStringView(*this).IsFloatingPoint(bFixed, bScientific, bSigned); + return TStringView(*this).template IsFloatingPoint(bFixed, bScientific, bHex); + } + + /** Converts the string into a boolean value. */ + NODISCARD FORCEINLINE constexpr bool ToBool() const + { + return TStringView(*this).ToBool(); + } + + /** Converts the string into an integer value. */ + template requires (!CSameAs && !CConst && !CVolatile) + NODISCARD FORCEINLINE constexpr U ToInt(uint Base = 0) const + { + return TStringView(*this).template ToInt(Base); + } + + /** Converts the string into a floating-point value. */ + template requires (!CConst && !CVolatile) + NODISCARD FORCEINLINE constexpr U ToFloat(bool bFixed = true, bool bScientific = true, bool bHex = true) const + { + return TStringView(*this).template ToFloat(bFixed, bScientific, bHex); + } + + /** Parse the string into a boolean value. */ + NODISCARD FORCEINLINE constexpr bool Parse(bool& Value) + { + return TStringView(*this).Parse(Value); + } + + /** Parse the string into an integer value. */ + template requires (!CSameAs && !CConst && !CVolatile) + NODISCARD FORCEINLINE constexpr bool Parse(U& Value, uint Base = 0) + { + return TStringView(*this).Parse(Value, Base); + } + + /** Parse the string into a floating-point value. */ + template requires (!CConst && !CVolatile) + NODISCARD FORCEINLINE constexpr bool Parse(U& Value, bool bFixed = true, bool bScientific = true, bool bHex = true) + { + return TStringView(*this).Parse(Value, bFixed, bScientific, bHex); } public: @@ -1275,105 +1317,6 @@ public: template requires (!CConst && !CVolatile) void AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision); - /** - * Converts a string into a boolean value. - * - * - "True" and non-zero integers become true. - * - "False" and unparsable values become false. - * - * @return The boolean value. - */ - NODISCARD FORCEINLINE bool ToBool() const - { - return TStringView(*this).ToBool(); - } - - /** - * Converts a string into an integer value. - * - * - "0x" or "0X" prefixes are not recognized if base is 16. - * - Only the minus sign is recognized (not the plus sign), and only for signed integer types of value. - * - Leading whitespace is not ignored. - * - * Ensure that the entire string can be parsed if IsNumeric(Base, false, true, false) is true. - * - * @param Base - The base of the number, between [2, 36]. - * - * @return The integer value. - */ - template requires (!CSameAs && !CConst && !CVolatile) - NODISCARD FORCEINLINE U ToInt(unsigned Base = 10) const - { - checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); - - return TStringView(*this).template ToInt(Base); - } - - /** - * Converts a string into a floating-point value. - * - * - "0x" or "0X" prefixes are not recognized if base is 16. - * - The plus sign is not recognized outside the exponent (only the minus sign is permitted at the beginning). - * - Leading whitespace is not ignored. - * - * Ensure that the entire string can be parsed if bFixed and IsNumeric(10, false) is true. - * Parsers hex floating-point values if bFixed and bScientific are false. - * - * @param bFixed - The fixed-point format. - * @param bScientific - The scientific notation. - * - * @return The floating-point value. - */ - template requires (!CConst && !CVolatile) - NODISCARD FORCEINLINE U ToFloat(bool bFixed = true, bool bScientific = false) const - { - return TStringView(*this).template ToFloat(bFixed, bScientific); - } - - /** Converts a string into a boolean value and remove the parsed substring. */ - NODISCARD FORCEINLINE bool ToBoolAndTrim() - { - TStringView View = *this; - - bool Result = View.ToBoolAndTrim(); - - size_t TrimNum = this->Num() - View.Num(); - - if (TrimNum > 0) Erase(0, TrimNum); - - return Result; - } - - /** Converts a string into an integer value and remove the parsed substring. */ - template requires (!CSameAs && !CConst && !CVolatile) - NODISCARD FORCEINLINE U ToIntAndTrim(unsigned Base = 10) - { - TStringView View = *this; - - U Result = View.template ToIntAndTrim(Base); - - size_t TrimNum = this->Num() - View.Num(); - - if (TrimNum > 0) Erase(0, TrimNum); - - return Result; - } - - /** Converts a string into a floating-point value and remove the parsed substring. */ - template requires (!CConst && !CVolatile) - NODISCARD FORCEINLINE U ToFloatAndTrim(bool bFixed = true, bool bScientific = true) - { - TStringView View = *this; - - U Result = View.template ToFloatAndTrim(bFixed, bScientific); - - size_t TrimNum = this->Num() - View.Num(); - - if (TrimNum > 0) Erase(0, TrimNum); - - return Result; - } - public: /** @@ -1398,35 +1341,6 @@ public: template void AppendFormat(TStringView Fmt, const Ts&... Args); - /** - * Parse a string using a format string to objects. - * - * @param Fmt - The format string. - * @param Args - The objects to parse. - * - * @return The number of objects successfully parsed. - */ - template - FORCEINLINE size_t Parse(TStringView Fmt, Ts&... Args) const - { - return TStringView(*this).Parse(Fmt, Args...); - } - - /** Parse a string using a format string to objects and remove the parsed substring. */ - template - FORCEINLINE size_t ParseAndTrim(TStringView Fmt, Ts&... Args) - { - TStringView View = *this; - - size_t Result = View.ParseAndTrim(Fmt, Args...); - - size_t TrimNum = this->Num() - View.Num(); - - if (TrimNum > 0) Erase(0, TrimNum); - - return Result; - } - public: /** Overloads the GetTypeHash algorithm for TString. */ diff --git a/Redcraft.Utility/Source/Public/Strings/StringView.h b/Redcraft.Utility/Source/Public/Strings/StringView.h index 993db1b..7abc131 100644 --- a/Redcraft.Utility/Source/Public/Strings/StringView.h +++ b/Redcraft.Utility/Source/Public/Strings/StringView.h @@ -12,6 +12,7 @@ #include "Iterators/BasicIterator.h" #include "Iterators/Sentinel.h" #include "Strings/Char.h" +#include "Strings/Convert.h" #include "Miscellaneous/AssertionMacros.h" #include @@ -529,133 +530,83 @@ public: return true; } - /** @return true if the string can be fully represented as a boolean value, false otherwise. */ + /** @return true if the string can be converted to a boolean value, false otherwise. */ NODISCARD FORCEINLINE constexpr bool IsBoolean() const { - TStringView View = *this; + bool Temp; - Ignore = View.ToBoolAndTrim(); - - return View.IsEmpty(); + return Algorithms::Parse(*this, Temp); } - /** @return true if the string can be fully represented as an integer value, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsInteger(unsigned Base = 10, bool bSigned = true) const + /** @return true if the string can be converted to an integer value, false otherwise. */ + template requires (!CSameAs && CSameAs, U>) + NODISCARD FORCEINLINE constexpr bool IsInteger(uint Base = 0) const { - TStringView View = *this; + U Temp; - if (View.StartsWith(LITERAL(FElementType, '-'))) - { - if (bSigned) View.RemovePrefix(1); - else return false; - } - - Ignore = View.ToIntAndTrim(Base); - - return View.IsEmpty(); + return Algorithms::Parse(*this, Temp, Base); } - /** @return true if the string can be fully represented as a floating-point value, false otherwise. */ - NODISCARD FORCEINLINE constexpr bool IsFloatingPoint(bool bFixed = true, bool bScientific = true, bool bSigned = true) const + /** @return true if the string can be converted to a floating-point value, false otherwise. */ + template requires (!CSameAs && CSameAs, U>) + NODISCARD FORCEINLINE constexpr bool IsFloatingPoint(bool bFixed = true, bool bScientific = true, bool bHex = true) const { - TStringView View = *this; + U Temp; - if (View.StartsWith(LITERAL(FElementType, '-'))) - { - if (bSigned) View.RemovePrefix(1); - else return false; - } - - Ignore = View.ToFloatAndTrim(bFixed, bScientific); - - return View.IsEmpty(); + return Algorithms::Parse(*this, Temp, bFixed, bScientific, bHex); } -public: - - /** - * Converts a string into a boolean value. - * - * - "True" and non-zero integers become true. - * - "False" and unparsable values become false. - * - * @return The boolean value. - */ - NODISCARD constexpr bool ToBool() const + /** Converts the string into a boolean value. */ + NODISCARD FORCEINLINE constexpr bool ToBool() const { - return TStringView(*this).ToBoolAndTrim(); + bool Result; + + verifyf(Algorithms::Parse(*this, Result), TEXT("Illegal conversion. Please check the IsBoolean().")); + + return Result; } - /** - * Converts a string into an integer value. - * - * - "0x" or "0X" prefixes are not recognized if base is 16. - * - Only the minus sign is recognized (not the plus sign), and only for signed integer types of value. - * - Leading whitespace is not ignored. - * - * Ensure that the entire string can be parsed if IsNumeric(Base, false, true, false) is true. - * - * @param Base - The base of the number, between [2, 36]. - * - * @return The integer value. - */ + /** Converts the string into an integer value. */ template requires (!CSameAs && !CConst && !CVolatile) - NODISCARD constexpr U ToInt(unsigned Base = 10) const + NODISCARD FORCEINLINE constexpr U ToInt(uint Base = 0) const { - return TStringView(*this).ToIntAndTrim(Base); + U Result; + + verifyf(Algorithms::Parse(*this, Result, Base), TEXT("Illegal conversion. Please check the IsInteger().")); + + return Result; } - /** - * Converts a string into a floating-point value. - * - * - "0x" or "0X" prefixes are not recognized if base is 16. - * - The plus sign is not recognized outside the exponent (only the minus sign is permitted at the beginning). - * - Leading whitespace is not ignored. - * - * Ensure that the entire string can be parsed if bFixed and IsNumeric(10, false) is true. - * Parsers hex floating-point values if bFixed and bScientific are false. - * - * @param bFixed - The fixed-point format. - * @param bScientific - The scientific notation. - * - * @return The floating-point value. - */ + /** Converts the string into a floating-point value. */ template requires (!CConst && !CVolatile) - NODISCARD constexpr U ToFloat(bool bFixed = true, bool bScientific = true) const + NODISCARD FORCEINLINE constexpr U ToFloat(bool bFixed = true, bool bScientific = true, bool bHex = true) const { - return TStringView(*this).ToFloatAndTrim(bFixed, bScientific); + U Result; + + verifyf(Algorithms::Parse(*this, Result, bFixed, bScientific, bHex), TEXT("Illegal conversion. Please check the IsFloatingPoint().")); + + return Result; } - /** Converts a string into a boolean value and remove the parsed substring. */ - NODISCARD constexpr bool ToBoolAndTrim(); + /** Parse the string into a boolean value. */ + NODISCARD FORCEINLINE constexpr bool Parse(bool& Value) + { + return Algorithms::Parse(*this, Value); + } - /** Converts a string into an integer value and remove the parsed substring. */ + /** Parse the string into an integer value. */ template requires (!CSameAs && !CConst && !CVolatile) - NODISCARD constexpr U ToIntAndTrim(unsigned Base = 10); - - /** Converts a string into a floating-point value and remove the parsed substring. */ - template requires (!CConst && !CVolatile) - NODISCARD constexpr U ToFloatAndTrim(bool bFixed = true, bool bScientific = true); - -public: - - /** - * Parse a string using a format string to objects. - * - * @param Fmt - The format string. - * @param Args - The objects to parse. - * - * @return The number of objects successfully parsed. - */ - template - size_t Parse(TStringView Fmt, Ts&... Args) const + NODISCARD FORCEINLINE constexpr bool Parse(U& Value, uint Base = 0) { - return TStringView(*this).ParseAndTrim(Fmt, Args...); + return Algorithms::Parse(*this, Value, Base); } - /** Parse a string using a format string to objects and remove the parsed substring. */ - template - size_t ParseAndTrim(TStringView Fmt, Ts&... Args); + /** Parse the string into a floating-point value. */ + template requires (!CConst && !CVolatile) + NODISCARD FORCEINLINE constexpr bool Parse(U& Value, bool bFixed = true, bool bScientific = true, bool bHex = true) + { + return Algorithms::Parse(*this, Value, bFixed, bScientific, bHex); + } public: