From bc93455d0e9afb723f9a36f1e8b3a3010fad381f Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Tue, 12 Nov 2024 13:32:43 +0800 Subject: [PATCH] refactor(string): refactor character and digit conversion to be non-invalidable --- .../Source/Private/Testing/StringTesting.cpp | 12 +++++----- Redcraft.Utility/Source/Public/String/Char.h | 20 ++++++++++------ .../Source/Public/String/Conversion.h.inl | 24 +++++++++---------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index fa45957..cc44e49 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -77,13 +77,13 @@ void TestChar() always_check(TChar::ToLower(LITERAL(T, 'i')) == LITERAL(T, 'i')); always_check(TChar::ToUpper(LITERAL(T, 'l')) == LITERAL(T, 'L')); - always_check(0x0 == TChar::ToDigit(LITERAL(T, '0'), 16)); - always_check(0xF == TChar::ToDigit(LITERAL(T, 'f'), 16)); - always_check(0xF == TChar::ToDigit(LITERAL(T, 'F'), 16)); + always_check(0x0 == TChar::ToDigit(LITERAL(T, '0'))); + always_check(0xF == TChar::ToDigit(LITERAL(T, 'f'))); + always_check(0xF == TChar::ToDigit(LITERAL(T, 'F'))); - always_check(LITERAL(T, '0') == TChar::FromDigit(0x0, 16)); - always_check(LITERAL(T, 'f') != TChar::FromDigit(0xF, 16)); - always_check(LITERAL(T, 'F') == TChar::FromDigit(0xF, 16)); + always_check(LITERAL(T, '0') == TChar::FromDigit(0x0)); + always_check(LITERAL(T, 'f') != TChar::FromDigit(0xF)); + always_check(LITERAL(T, 'F') == TChar::FromDigit(0xF)); }; Test(InPlaceType); diff --git a/Redcraft.Utility/Source/Public/String/Char.h b/Redcraft.Utility/Source/Public/String/Char.h index f4451be..aa4d8b2 100644 --- a/Redcraft.Utility/Source/Public/String/Char.h +++ b/Redcraft.Utility/Source/Public/String/Char.h @@ -1,7 +1,6 @@ #pragma once #include "CoreTypes.h" -#include "Templates/Optional.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/AssertionMacros.h" @@ -809,7 +808,7 @@ struct TChar return InChar; } - NODISCARD FORCEINLINE static constexpr TOptional ToDigit(CharType InChar, unsigned Base = 10) + NODISCARD FORCEINLINE static constexpr unsigned ToDigit(CharType InChar) { static_assert(TChar::IsASCII()); @@ -835,16 +834,23 @@ struct TChar static_assert(sizeof(DigitFromChar) == 256); - if constexpr (sizeof(CharType) > 1) if (InChar >> 8) return Invalid; - - if (DigitFromChar[InChar] >= Base) return Invalid; + if constexpr (sizeof(CharType) > 1) if (InChar >> 8) return DigitFromChar[0]; return DigitFromChar[InChar]; } - NODISCARD FORCEINLINE static constexpr TOptional FromDigit(unsigned InDigit, unsigned Base = 10) + NODISCARD FORCEINLINE static constexpr CharType FromDigit(unsigned InDigit) { - if (InDigit > Base) return Invalid; + checkf(InDigit < 36, TEXT("Digit must be in the range [0, 35].")); + + return LITERAL(CharType, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")[InDigit]; + } + + NODISCARD FORCEINLINE static constexpr CharType FromDigit(unsigned InDigit, bool bLowercase) + { + checkf(InDigit < 36, TEXT("Digit must be in the range [0, 35].")); + + if (bLowercase) return LITERAL(CharType, "0123456789abcdefghijklmnopqrstuvwxyz")[InDigit]; return LITERAL(CharType, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")[InDigit]; } diff --git a/Redcraft.Utility/Source/Public/String/Conversion.h.inl b/Redcraft.Utility/Source/Public/String/Conversion.h.inl index 2856ede..3653b7c 100644 --- a/Redcraft.Utility/Source/Public/String/Conversion.h.inl +++ b/Redcraft.Utility/Source/Public/String/Conversion.h.inl @@ -212,11 +212,11 @@ struct TStringHelper while (!View.IsEmpty()) { - auto Digit = TChar::ToDigit(View.Front(), Base); + auto Digit = TChar::ToDigit(View.Front()); - if (!Digit) break; + if (Digit >= Base) break; - Result = Result * static_cast(Base) + static_cast(*Digit); + Result = Result * static_cast(Base) + static_cast(Digit); View.RemovePrefix(1); } @@ -632,13 +632,13 @@ constexpr U TStringView::ToInt(unsigned Base) const for (ElementType Char : View) { - auto Digit = TChar::ToDigit(Char, Base); + auto Digit = TChar::ToDigit(Char); - if (!Digit) break; + if (Digit >= Base) break; LastValue = Value; - Value = static_cast(LastValue * Base + *Digit); + Value = static_cast(LastValue * Base + Digit); if (Value < LastValue) { @@ -794,7 +794,7 @@ void TString::AppendInt(U Value, unsigned Base) { checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base.")); - constexpr const ElementType* DigitToChar = LITERAL(ElementType, "0123456789ABCDEF"); + static_assert(TChar::IsASCII()); using UnsignedU = TMakeUnsigned; @@ -823,8 +823,8 @@ void TString::AppendInt(U Value, unsigned 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 = DigitToChar[Unsigned & 0b01111]; Unsigned >>= 4; } while (Unsigned != 0); break; - case 0X20: do { *--Iter = DigitToChar[Unsigned & 0b11111]; Unsigned >>= 5; } 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: @@ -832,7 +832,7 @@ void TString::AppendInt(U Value, unsigned Base) case 7: case 9: case 10: do { *--Iter = static_cast('0' + Unsigned % Base); Unsigned = static_cast(Unsigned / Base); } while (Unsigned != 0); break; - default: do { *--Iter = DigitToChar[Unsigned % Base]; Unsigned = static_cast(Unsigned / Base); } while (Unsigned != 0); break; + default: do { *--Iter = TChar::FromDigit(Unsigned % Base); Unsigned = static_cast(Unsigned / Base); } while (Unsigned != 0); break; } if constexpr (CSigned) if (bNegative) *--Iter = LITERAL(T, '-'); @@ -900,8 +900,8 @@ struct TStringFloatSerializer for (char& Char : Buffer) { - const auto Digit = FChar::ToDigit(Char, Base); - if (Digit) Char = *FChar::FromDigit(*Digit, Base); + const auto Digit = FChar::ToDigit(Char); + if (Digit < Base) Char = FChar::FromDigit(Digit); } Result.Append(Buffer.Begin(), Buffer.End());