feat(strings): add formatter for pointer or member pointer types
This commit is contained in:
parent
88aba14620
commit
c16da1af53
@ -8,6 +8,7 @@
|
||||
#include "Algorithms/Basic.h"
|
||||
#include "Containers/StaticArray.h"
|
||||
#include "Containers/Array.h"
|
||||
#include "Numerics/Bit.h"
|
||||
#include "Numerics/Math.h"
|
||||
#include "Strings/Char.h"
|
||||
#include "Miscellaneous/AssertionMacros.h"
|
||||
@ -1867,7 +1868,7 @@ public:
|
||||
*Iter++ = bCharacter ? LITERAL(FCharType, '\'') : LITERAL(FCharType, '\"');
|
||||
}
|
||||
|
||||
// Write the object, include escaped quotes in the counter.
|
||||
// Write the object.
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
@ -1972,7 +1973,7 @@ static_assert(CFormattable<char>);
|
||||
static_assert(CFormattable<bool>);
|
||||
|
||||
/**
|
||||
* A formatter for the floating-point type.
|
||||
* A formatter for the floating-point types.
|
||||
*
|
||||
* The syntax of format specifications is:
|
||||
*
|
||||
@ -2697,7 +2698,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Write the object, include escaped quotes in the counter.
|
||||
// Write the object.
|
||||
{
|
||||
static_assert(FChar::IsASCII() && FCharTraits::IsASCII());
|
||||
|
||||
@ -2773,6 +2774,397 @@ private:
|
||||
|
||||
static_assert(CFormattable<float>);
|
||||
|
||||
/**
|
||||
* A formatter for pointer or member pointer types.
|
||||
*
|
||||
* The syntax of format specifications is:
|
||||
*
|
||||
* [Fill And Align] [Width] [Type] [!]
|
||||
*
|
||||
* 1. The fill and align part:
|
||||
*
|
||||
* [Fill Character] <Align Option>
|
||||
*
|
||||
* i. Fill Character: The character is used to fill width of the object. It is optional and cannot be '{' or '}'.
|
||||
* It should be representable as a single unicode otherwise it is undefined behavior.
|
||||
*
|
||||
* ii. Align Option: The character is used to indicate the direction of alignment.
|
||||
*
|
||||
* - '<': Align the formatted argument to the left of the available space
|
||||
* by inserting n fill characters after the formatted argument.
|
||||
* This is default option.
|
||||
* - '^': Align the formatted argument to the center of the available space
|
||||
* by inserting n fill characters around the formatted argument.
|
||||
* If cannot absolute centering, offset to the left.
|
||||
* - '>': Align the formatted argument ro the right of the available space
|
||||
* by inserting n fill characters before the formatted argument.
|
||||
*
|
||||
* 2. The width part:
|
||||
*
|
||||
* - 'N': The number is used to specify the minimum field width of the object.
|
||||
* N should be an unsigned non-zero decimal number.
|
||||
* - '{N}': Dynamically determine the minimum field width of the object.
|
||||
* N should be a valid index of the format integral argument.
|
||||
* N is optional, and the default value is automatic indexing.
|
||||
*
|
||||
* 3. The type indicator part:
|
||||
*
|
||||
* - none: Indicates the normal formatting.
|
||||
* - 'P': Indicates the normal formatting.
|
||||
* - 'p': Indicates lowercase formatting.
|
||||
*
|
||||
* 4. The case indicators part:
|
||||
*
|
||||
* - '!': Indicates capitalize the entire string.
|
||||
*
|
||||
*/
|
||||
template <typename T, CCharType CharType> requires ((CNullPointer<T> || CPointer<T> || CMemberPointer<T>) && CSameAs<TRemoveCVRef<T>, T>)
|
||||
class TFormatter<T, CharType>
|
||||
{
|
||||
private:
|
||||
|
||||
using FCharType = CharType;
|
||||
using FCharTraits = TChar<FCharType>;
|
||||
using FFillCharacter = TStaticArray<FCharType, FCharTraits::MaxCodeUnitLength>;
|
||||
|
||||
public:
|
||||
|
||||
template <CFormatStringContext<FCharType> CTX>
|
||||
constexpr TRangeIterator<CTX> Parse(CTX& Context)
|
||||
{
|
||||
auto Iter = Ranges::Begin(Context);
|
||||
auto Sent = Ranges::End (Context);
|
||||
|
||||
// Set the default values.
|
||||
{
|
||||
FillUnitLength = 1;
|
||||
FillCharacter[0] = LITERAL(FCharType, ' ');
|
||||
AlignOption = LITERAL(FCharType, '>');
|
||||
|
||||
FieldWidth = 0;
|
||||
|
||||
bDynamicWidth = false;
|
||||
|
||||
bLowercase = false;
|
||||
bUppercase = false;
|
||||
}
|
||||
|
||||
// If the format description string is empty.
|
||||
if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
FCharType Char = *Iter; ++Iter;
|
||||
|
||||
// Try to parse the fill and align part.
|
||||
// This code assumes that the format string does not contain multi-unit characters, except for fill character.
|
||||
|
||||
// If the fill character is multi-unit.
|
||||
if (!FCharTraits::IsValid(Char))
|
||||
{
|
||||
FillUnitLength = 1;
|
||||
FillCharacter[0] = Char;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
|
||||
// If the fill character ends.
|
||||
if (FillUnitLength == FCharTraits::MaxCodeUnitLength || FCharTraits::IsValid(Char)) break;
|
||||
|
||||
FillCharacter[FillUnitLength++] = Char;
|
||||
}
|
||||
|
||||
if (Char != LITERAL(FCharType, '<') && Char != LITERAL(FCharType, '^') && Char != LITERAL(FCharType, '>')) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal format string. The fill character is not representable as a single unicode."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
AlignOption = Char;
|
||||
|
||||
if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
|
||||
// If the fill character is single-unit.
|
||||
else do
|
||||
{
|
||||
if (Iter == Sent) break;
|
||||
|
||||
// If the fill character is specified.
|
||||
if (*Iter == LITERAL(FCharType, '<') || *Iter == LITERAL(FCharType, '^') || *Iter == LITERAL(FCharType, '>'))
|
||||
{
|
||||
FillUnitLength = 1;
|
||||
FillCharacter[0] = Char;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
|
||||
// If the fill character is not specified and the align option is not specified.
|
||||
else if (Char != LITERAL(FCharType, '<') && Char != LITERAL(FCharType, '^') && Char != LITERAL(FCharType, '>')) break;
|
||||
|
||||
AlignOption = Char;
|
||||
|
||||
if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
while (false);
|
||||
|
||||
// Try to parse the width part.
|
||||
{
|
||||
if (Char == LITERAL(FCharType, '{'))
|
||||
{
|
||||
bDynamicWidth = true;
|
||||
FieldWidth = INDEX_NONE;
|
||||
|
||||
if (Iter == Sent) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
|
||||
if ((bDynamicWidth || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char))
|
||||
{
|
||||
FieldWidth = FCharTraits::ToDigit(Char);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Iter == Sent)
|
||||
{
|
||||
checkf(!bDynamicWidth, TEXT("Illegal format string. Missing '}' in format string."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
if (!bDynamicWidth && *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
|
||||
const uint Digit = FCharTraits::ToDigit(Char);
|
||||
|
||||
if (Digit >= 10) break;
|
||||
|
||||
FieldWidth = FieldWidth * 10 + Digit;
|
||||
}
|
||||
}
|
||||
|
||||
if (bDynamicWidth)
|
||||
{
|
||||
if (Char != LITERAL(FCharType, '}')) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// Try to automatic indexing.
|
||||
if (FieldWidth == INDEX_NONE)
|
||||
{
|
||||
FieldWidth = Context.GetNextIndex();
|
||||
|
||||
if (FieldWidth == INDEX_NONE) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal index. Please check the field width."));
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
// Try to manual indexing.
|
||||
else if (!Context.CheckIndex(FieldWidth)) UNLIKELY
|
||||
{
|
||||
checkf(false, TEXT("Illegal index. Please check the field width."));
|
||||
}
|
||||
|
||||
else break;
|
||||
|
||||
bDynamicWidth = false;
|
||||
FieldWidth = 0;
|
||||
}
|
||||
while (false);
|
||||
|
||||
if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to parse the type indicators part.
|
||||
|
||||
switch (Char)
|
||||
{
|
||||
case LITERAL(FCharType, 'p'): bLowercase = true; break;
|
||||
default: { }
|
||||
}
|
||||
|
||||
switch (Char)
|
||||
{
|
||||
case LITERAL(FCharType, 'P'):
|
||||
case LITERAL(FCharType, 'p'): if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; break;
|
||||
default: { }
|
||||
}
|
||||
|
||||
// Try to parse the case indicators part.
|
||||
if (Char == LITERAL(FCharType, '!'))
|
||||
{
|
||||
bUppercase = true;
|
||||
|
||||
if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
|
||||
|
||||
Char = *Iter; ++Iter;
|
||||
}
|
||||
|
||||
checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
template <CFormatObjectContext<FCharType> CTX>
|
||||
constexpr TRangeIterator<CTX> Format(T Object, CTX& Context) const
|
||||
{
|
||||
auto Iter = Ranges::Begin(Context);
|
||||
auto Sent = Ranges::End (Context);
|
||||
|
||||
size_t TargetField = FieldWidth;
|
||||
|
||||
// Visit the dynamic width argument.
|
||||
if (bDynamicWidth)
|
||||
{
|
||||
TargetField = Context.Visit([]<typename U>(U&& Value) -> size_t
|
||||
{
|
||||
if constexpr (CIntegral<TRemoveReference<U>>)
|
||||
{
|
||||
checkf(Value > 0, TEXT("Illegal format argument. The dynamic width argument must be a unsigned non-zero number."));
|
||||
|
||||
return Math::Max(Value, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
checkf(false, TEXT("Illegal format argument. The dynamic width argument must be an integral."));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
, FieldWidth);
|
||||
}
|
||||
|
||||
size_t LeftPadding = 0;
|
||||
size_t RightPadding = 0;
|
||||
|
||||
// Estimate the field width.
|
||||
if (TargetField != 0)
|
||||
{
|
||||
const size_t LiteralWidth = 2 * sizeof(T) + 2;
|
||||
|
||||
const size_t PaddingWidth = TargetField - Math::Min(LiteralWidth, TargetField);
|
||||
|
||||
switch (AlignOption)
|
||||
{
|
||||
case LITERAL(FCharType, '<'): RightPadding = PaddingWidth; break;
|
||||
case LITERAL(FCharType, '>'): LeftPadding = PaddingWidth; break;
|
||||
case LITERAL(FCharType, '^'):
|
||||
LeftPadding = Math::DivAndFloor(PaddingWidth, 2);
|
||||
RightPadding = PaddingWidth - LeftPadding;
|
||||
break;
|
||||
default: check_no_entry();
|
||||
}
|
||||
}
|
||||
|
||||
// Write the left padding.
|
||||
for (size_t Index = 0; Index != LeftPadding; ++Index)
|
||||
{
|
||||
for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex)
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FillCharacter[Jndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Write the object, include escaped quotes in the counter.
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = LITERAL(FCharType, '0');
|
||||
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = bUppercase ? LITERAL(FCharType, 'X') : LITERAL(FCharType, 'x');
|
||||
|
||||
const uint8* Ptr = reinterpret_cast<const uint8*>(&Object);
|
||||
|
||||
if constexpr (Math::EEndian::Native != Math::EEndian::Little)
|
||||
{
|
||||
for (size_t Index = 0; Index != sizeof(T); ++Index)
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FCharTraits::FromDigit(Ptr[Index] >> 4, bLowercase);
|
||||
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FCharTraits::FromDigit(Ptr[Index] & 0x0F, bLowercase);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
for (size_t Index = 0; Index != sizeof(T); ++Index)
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FCharTraits::FromDigit(Ptr[sizeof(T) - Index - 1] >> 4, bLowercase);
|
||||
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FCharTraits::FromDigit(Ptr[sizeof(T) - Index - 1] & 0x0F, bLowercase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the right padding.
|
||||
for (size_t Index = 0; Index != RightPadding; ++Index)
|
||||
{
|
||||
for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex)
|
||||
{
|
||||
if (Iter == Sent) UNLIKELY return Iter;
|
||||
|
||||
*Iter++ = FillCharacter[Jndex];
|
||||
}
|
||||
}
|
||||
|
||||
return Iter;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
size_t FillUnitLength = 1;
|
||||
FFillCharacter FillCharacter = { LITERAL(FCharType, ' ') };
|
||||
FCharType AlignOption = LITERAL(FCharType, '>');
|
||||
|
||||
size_t FieldWidth = 0;
|
||||
|
||||
bool bDynamicWidth = false;
|
||||
|
||||
bool bLowercase = false;
|
||||
bool bUppercase = false;
|
||||
|
||||
};
|
||||
|
||||
NAMESPACE_MODULE_END(Utility)
|
||||
NAMESPACE_MODULE_END(Redcraft)
|
||||
NAMESPACE_REDCRAFT_END
|
||||
|
Loading…
x
Reference in New Issue
Block a user