3184 lines
		
	
	
		
			86 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3184 lines
		
	
	
		
			86 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #pragma once
 | |
| 
 | |
| #include "CoreTypes.h"
 | |
| #include "TypeTraits/TypeTraits.h"
 | |
| #include "Templates/Utility.h"
 | |
| #include "Ranges/Utility.h"
 | |
| #include "Ranges/View.h"
 | |
| #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"
 | |
| 
 | |
| #include <charconv>
 | |
| 
 | |
| #pragma warning(push)
 | |
| #pragma warning(disable: 4244)
 | |
| 
 | |
| NAMESPACE_REDCRAFT_BEGIN
 | |
| NAMESPACE_MODULE_BEGIN(Redcraft)
 | |
| NAMESPACE_MODULE_BEGIN(Utility)
 | |
| 
 | |
| /** A concept specifies a type is a format description string context. */
 | |
| template <typename T, typename CharType = char>
 | |
| concept CFormatStringContext = CInputRange<T> && CCharType<CharType> && CSameAs<TRangeElement<T>, CharType>
 | |
| 	&& requires(T& Context, TRangeIterator<T> Iter, size_t Index)
 | |
| 	{
 | |
| 		/** Set the iterator of the context. */
 | |
| 		Context.AdvanceTo(MoveTemp(Iter));
 | |
| 
 | |
| 		/** @return The next automatic index. */
 | |
| 		{ Context.GetNextIndex() } -> CSameAs<size_t>;
 | |
| 
 | |
| 		/** @return true if the manual index is valid. */
 | |
| 		{ Context.CheckIndex(Index) } -> CBooleanTestable;
 | |
| 	};
 | |
| 
 | |
| /** A concept specifies a type is a format output context. */
 | |
| template <typename T, typename CharType = char>
 | |
| concept CFormatObjectContext = COutputRange<T, CharType> && CCharType<CharType>
 | |
| 	&& requires(T& Context, TRangeIterator<T> Iter, size_t Index)
 | |
| 	{
 | |
| 		/** Set the iterator of the context. */
 | |
| 		Context.AdvanceTo(MoveTemp(Iter));
 | |
| 
 | |
| 		/** Visit the format argument by index, and the argument is always like const&. */
 | |
| 		{ Context.Visit([](const auto&) { return 0; }, Index) } -> CIntegral;
 | |
| 
 | |
| 		/** Visit the format argument by index, and the argument is always like const&. */
 | |
| 		{ Context.template Visit<int>([](const auto&) { return 0; }, Index) } -> CIntegral;
 | |
| 	};
 | |
| 
 | |
| /**
 | |
|  * A template class that defines the formatting rules for a specific type.
 | |
|  *
 | |
|  * @tparam T        - The type of object being formatted.
 | |
|  * @tparam CharType - The character type of the formatting target.
 | |
|  */
 | |
| template <typename T, CCharType CharType = char> requires (CSameAs<TRemoveCVRef<T>, T>)
 | |
| class TFormatter
 | |
| {
 | |
| public:
 | |
| 
 | |
| 	static_assert(sizeof(T) == -1, "The type is not formattable.");
 | |
| 
 | |
| 	FORCEINLINE constexpr TFormatter()                             = delete;
 | |
| 	FORCEINLINE constexpr TFormatter(const TFormatter&)            = delete;
 | |
| 	FORCEINLINE constexpr TFormatter(TFormatter&&)                 = delete;
 | |
| 	FORCEINLINE constexpr TFormatter& operator=(const TFormatter&) = delete;
 | |
| 	FORCEINLINE constexpr TFormatter& operator=(TFormatter&&)      = delete;
 | |
| 
 | |
| 	/**
 | |
| 	 * Parses the format description string from the context.
 | |
| 	 * Assert that the format description string is valid.
 | |
| 	 *
 | |
| 	 * @return The iterator that points to the first unmatched character.
 | |
| 	 */
 | |
| 	template <CFormatStringContext<CharType> CTX>
 | |
| 	FORCEINLINE constexpr TRangeIterator<CTX> Parse(CTX& Context);
 | |
| 
 | |
| 	/**
 | |
| 	 * Formats the object and writes the result to the context.
 | |
| 	 * Do not assert that the output range is always large enough, and return directly if it is insufficient.
 | |
| 	 * Specify, unlike visiting arguments from the context which is always like const&,
 | |
| 	 * the object argument is a forwarding reference.
 | |
| 	 *
 | |
| 	 * @return The iterator that points to the next position of the output.
 | |
| 	 */
 | |
| 	template <typename U, CFormatObjectContext<CharType> CTX> requires (CSameAs<TRemoveCVRef<T>, U>)
 | |
| 	FORCEINLINE constexpr TRangeIterator<CTX> Format(U&& Object, CTX& Context) const;
 | |
| 
 | |
| };
 | |
| 
 | |
| NAMESPACE_PRIVATE_BEGIN
 | |
| 
 | |
| template <typename I, typename S, typename... Ts>
 | |
| class TFormatStringContext
 | |
| {
 | |
| public:
 | |
| 
 | |
| 	FORCEINLINE constexpr TFormatStringContext(I InFirst, S InLast) : First(MoveTemp(InFirst)), Last(InLast), AutomaticIndex(0) { }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr I Begin()       requires (!CCopyable<I>) { return MoveTemp(First); }
 | |
| 	NODISCARD FORCEINLINE constexpr I Begin() const requires ( CCopyable<I>) { return          First;  }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr S End() const { return Last; }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedSentinelFor<S, I>) { return Last - First; }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return First == Last; }
 | |
| 
 | |
| 	FORCEINLINE constexpr void AdvanceTo(I Iter) { First = MoveTemp(Iter); }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr size_t GetNextIndex()
 | |
| 	{
 | |
| 		bool bIsValid = AutomaticIndex < sizeof...(Ts) && AutomaticIndex != INDEX_NONE;
 | |
| 
 | |
| 		checkf(bIsValid, TEXT("Illegal automatic indexing. Already entered manual indexing mode."));
 | |
| 
 | |
| 		if (!bIsValid) return INDEX_NONE;
 | |
| 
 | |
| 		return AutomaticIndex++;
 | |
| 	}
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr bool CheckIndex(size_t Index)
 | |
| 	{
 | |
| 		bool bIsValid = AutomaticIndex == 0 || AutomaticIndex == INDEX_NONE;
 | |
| 
 | |
| 		checkf(bIsValid, TEXT("Illegal manual indexing. Already entered automatic indexing mode."));
 | |
| 
 | |
| 		if (!bIsValid) return false;
 | |
| 
 | |
| 		AutomaticIndex = INDEX_NONE;
 | |
| 
 | |
| 		return Index < sizeof...(Ts);
 | |
| 	}
 | |
| 
 | |
| private:
 | |
| 
 | |
| 	NO_UNIQUE_ADDRESS I First;
 | |
| 	NO_UNIQUE_ADDRESS S Last;
 | |
| 
 | |
| 	size_t AutomaticIndex = 0;
 | |
| };
 | |
| 
 | |
| static_assert(CFormatStringContext<TFormatStringContext<IInputIterator<char>, ISentinelFor<IInputIterator<char>>>>);
 | |
| 
 | |
| template <typename I, typename S, typename... Ts>
 | |
| class TFormatObjectContext
 | |
| {
 | |
| public:
 | |
| 
 | |
| 	FORCEINLINE constexpr TFormatObjectContext(I InFirst, S InLast, Ts&... Args) : First(MoveTemp(InFirst)), Last(InLast), ArgsTuple(Args...) { }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr I Begin()       requires (!CCopyable<I>) { return MoveTemp(First); }
 | |
| 	NODISCARD FORCEINLINE constexpr I Begin() const requires ( CCopyable<I>) { return          First;  }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr S End() const { return Last; }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedSentinelFor<S, I>) { return Last - First; }
 | |
| 
 | |
| 	NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return First == Last; }
 | |
| 
 | |
| 	FORCEINLINE constexpr void AdvanceTo(I Iter) { First = MoveTemp(Iter); }
 | |
| 
 | |
| 	template <typename F> requires (((sizeof...(Ts) >= 1) && ... && CInvocable<F&&, Ts&>) && CCommonReference<TInvokeResult<F&&, const Ts&>...>)
 | |
| 	FORCEINLINE constexpr decltype(auto) Visit(F&& Func, size_t Index) const { return ArgsTuple.Visit(Forward<F>(Func), Index); }
 | |
| 
 | |
| 	template <typename Ret, typename F> requires ((sizeof...(Ts) >= 1) && ... && CInvocableResult<Ret, F&&, const Ts&>)
 | |
| 	FORCEINLINE constexpr Ret Visit(F&& Func, size_t Index) const { return ArgsTuple.template Visit<Ret>(Forward<F>(Func), Index); }
 | |
| 
 | |
| private:
 | |
| 
 | |
| 	NO_UNIQUE_ADDRESS I First;
 | |
| 	NO_UNIQUE_ADDRESS S Last;
 | |
| 
 | |
| 	TTuple<Ts&...> ArgsTuple;
 | |
| };
 | |
| 
 | |
| static_assert(CFormatObjectContext<TFormatObjectContext<IOutputIterator<char&>, ISentinelFor<IOutputIterator<char&>>, int>>);
 | |
| 
 | |
| NAMESPACE_PRIVATE_END
 | |
| 
 | |
| /** A concept specifies a type is formattable by the 'Algorithms::Format()'. */
 | |
| template <typename T, typename CharType = char>
 | |
| concept CFormattable = CCharType<CharType> && CSemiregular<TFormatter<TRemoveCVRef<T>, CharType>>
 | |
| 	&& requires(TFormatter<TRemoveCVRef<T>, CharType>& Formatter, T& Object,
 | |
| 		NAMESPACE_PRIVATE::TFormatStringContext< IInputIterator<CharType >, ISentinelFor< IInputIterator<CharType >>, T> FormatStringContext,
 | |
| 		NAMESPACE_PRIVATE::TFormatObjectContext<IOutputIterator<CharType&>, ISentinelFor<IOutputIterator<CharType&>>, T> FormatObjectContext)
 | |
| 	{
 | |
| 		{ Formatter.Parse (        FormatStringContext) } -> CSameAs< IInputIterator<CharType >>;
 | |
| 		{ Formatter.Format(Object, FormatObjectContext) } -> CSameAs<IOutputIterator<CharType&>>;
 | |
| 	};
 | |
| 
 | |
| NAMESPACE_BEGIN(Algorithms)
 | |
| 
 | |
| /**
 | |
|  * Formats the objects and writes the result to the output range.
 | |
|  * Assert that the format description string is valid.
 | |
|  * If the output range is insufficient, return directly without asserting.
 | |
|  *
 | |
|  * @param Output - The output range to write the result.
 | |
|  * @param Fmt    - The format description string.
 | |
|  * @param Args   - The objects to format.
 | |
|  *
 | |
|  * @return The iterator that points to the next position of the output.
 | |
|  */
 | |
| template <CInputRange R1, COutputRange<TRangeElement<R1>> R2, CFormattable<TRangeElement<R1>>... Ts> requires (CBorrowedRange<R2>)
 | |
| FORCEINLINE constexpr TRangeIterator<R2> Format(R2&& Output, R1&& Fmt, Ts&&... Args)
 | |
| {
 | |
| 	if constexpr (CSizedRange<R1&>)
 | |
| 	{
 | |
| 		checkf(Algorithms::Distance(Fmt) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Fmt)."));
 | |
| 	}
 | |
| 
 | |
| 	if constexpr (CSizedRange<R2&>)
 | |
| 	{
 | |
| 		checkf(Algorithms::Distance(Output) >= 0, TEXT("Illegal range. Please check Algorithms::Distance(Output)."));
 | |
| 	}
 | |
| 
 | |
| 	using FCharType   = TRangeElement<R1>;
 | |
| 	using FCharTraits = TChar<FCharType>;
 | |
| 
 | |
| 	using FFormatStringContext = NAMESPACE_PRIVATE::TFormatStringContext<TRangeIterator<R1>, TRangeSentinel<R1>, Ts...>;
 | |
| 	using FFormatObjectContext = NAMESPACE_PRIVATE::TFormatObjectContext<TRangeIterator<R2>, TRangeSentinel<R2>, Ts...>;
 | |
| 
 | |
| 	FFormatStringContext FormatStringContext(Ranges::Begin(Fmt   ), Ranges::End(Fmt   ));
 | |
| 	FFormatObjectContext FormatObjectContext(Ranges::Begin(Output), Ranges::End(Output), Args...);
 | |
| 
 | |
| 	auto FmtIter = Ranges::Begin(FormatStringContext);
 | |
| 	auto FmtSent = Ranges::End  (FormatStringContext);
 | |
| 
 | |
| 	auto OutIter = Ranges::Begin(FormatObjectContext);
 | |
| 	auto OutSent = Ranges::End  (FormatObjectContext);
 | |
| 
 | |
| 	// If the output range is insufficient.
 | |
| 	if (OutIter == OutSent) UNLIKELY return OutIter;
 | |
| 
 | |
| 	// For each character in the format string.
 | |
| 	for (FCharType Char; FmtIter != FmtSent; ++FmtIter)
 | |
| 	{
 | |
| 		Char = *FmtIter;
 | |
| 
 | |
| 		// If the character may be a replacement field.
 | |
| 		if (Char == LITERAL(FCharType, '{'))
 | |
| 		{
 | |
| 			if (++FmtIter == FmtSent) UNLIKELY
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Unmatched '{' in format string."));
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			Char = *FmtIter;
 | |
| 
 | |
| 			// If the character just an escaped '{'.
 | |
| 			if (Char == LITERAL(FCharType, '{'))
 | |
| 			{
 | |
| 				if (OutIter == OutSent) UNLIKELY return OutIter;
 | |
| 
 | |
| 				*OutIter++ = LITERAL(FCharType, '{');
 | |
| 
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			// If available replacement fields.
 | |
| 			if constexpr (sizeof...(Ts) >= 1)
 | |
| 			{
 | |
| 				size_t Index;
 | |
| 
 | |
| 				// If the replacement field has a manual index.
 | |
| 				if (Char != LITERAL(FCharType, ':') && Char != LITERAL(FCharType, '}'))
 | |
| 				{
 | |
| 					Index = 0;
 | |
| 
 | |
| 					bool bIsValid = true;
 | |
| 
 | |
| 					do
 | |
| 					{
 | |
| 						const uint Digit = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 						if (Digit >= 10) bIsValid = false;
 | |
| 
 | |
| 						Index = Index * 10 + Digit;
 | |
| 
 | |
| 						if (++FmtIter == FmtSent) UNLIKELY break;
 | |
| 
 | |
| 						Char = *FmtIter;
 | |
| 					}
 | |
| 					while (Char != LITERAL(FCharType, ':') && Char != LITERAL(FCharType, '}'));
 | |
| 
 | |
| 					// If the index string contains illegal characters or the index is out of range.
 | |
| 					if (!bIsValid || !FormatStringContext.CheckIndex(Index)) UNLIKELY
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the replacement field."));
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// If the replacement field need automatic indexing.
 | |
| 				else
 | |
| 				{
 | |
| 					Index = FormatStringContext.GetNextIndex();
 | |
| 
 | |
| 					if (Index == INDEX_NONE)
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the replacement field."));
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// Jump over the ':' character.
 | |
| 				if (Char == LITERAL(FCharType, ':')) ++FmtIter;
 | |
| 
 | |
| 				if (FmtIter == FmtSent) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					break;
 | |
| 				}
 | |
| 
 | |
| 				Char = *FmtIter;
 | |
| 
 | |
| 				ForwardAsTuple(Forward<Ts>(Args)...).Visit([&]<typename T>(T&& Arg)
 | |
| 				{
 | |
| 					TFormatter<TRemoveCVRef<T>, FCharType> Formatter;
 | |
| 
 | |
| 					if (Char != LITERAL(FCharType, '}'))
 | |
| 					{
 | |
| 						FormatStringContext.AdvanceTo(MoveTemp(FmtIter));
 | |
| 
 | |
| 						// Parse the format description string.
 | |
| 						FmtIter = Formatter.Parse(FormatStringContext);
 | |
| 
 | |
| 						if (FmtIter == FmtSent || *FmtIter != LITERAL(FCharType, '}')) UNLIKELY
 | |
| 						{
 | |
| 							checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 							return;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					FormatObjectContext.AdvanceTo(MoveTemp(OutIter));
 | |
| 
 | |
| 					// Format the object and write the result to the context.
 | |
| 					OutIter = Formatter.Format(Forward<T>(Arg), FormatObjectContext);
 | |
| 				}
 | |
| 				, Index);
 | |
| 			}
 | |
| 
 | |
| 			else
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal index. Please check the replacement field."));
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// If the character just an escaped '}'.
 | |
| 		else if (Char == LITERAL(FCharType, '}'))
 | |
| 		{
 | |
| 			// Confirm the character is an escaped '}'.
 | |
| 			if (++FmtIter != FmtSent && *FmtIter == LITERAL(FCharType, '}'))
 | |
| 			{
 | |
| 				if (OutIter == OutSent) UNLIKELY return OutIter;
 | |
| 
 | |
| 				*OutIter++ = LITERAL(FCharType, '}');
 | |
| 
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			checkf(false, TEXT("Illegal format string. Missing '{' in format string."));
 | |
| 
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		// If the output range is insufficient.
 | |
| 		else if (OutIter == OutSent) UNLIKELY return OutIter;
 | |
| 
 | |
| 		// If the character is not a replacement field.
 | |
| 		else *OutIter++ = Char;
 | |
| 	}
 | |
| 
 | |
| 	return OutIter;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Formats the objects and writes the result to the output range.
 | |
|  * Assert that the format description string is valid.
 | |
|  * If the output range is insufficient, return directly without asserting.
 | |
|  *
 | |
|  * @param OutputFirst - The iterator of the output range to write the result.
 | |
|  * @param OutputLast  - The sentinel of the output range to write the result.
 | |
|  * @param FmtFirst    - The iterator of the format description string.
 | |
|  * @param FmtLast     - The sentinel of the format description string.
 | |
|  * @param Args        - The objects to format.
 | |
|  *
 | |
|  * @return The iterator that points to the next position of the output.
 | |
|  */
 | |
| template <CInputIterator I1, CSentinelFor<I1> S1, COutputIterator<TIteratorElement<I1>> I2, CSentinelFor<I2> S2, CFormattable<TIteratorElement<I1>>... Ts>
 | |
| FORCEINLINE constexpr I2 Format(I2 OutputFirst, S2 OutputLast, I1 FmtFirst, S1 FmtLast, Ts&&... Args)
 | |
| {
 | |
| 	if constexpr (CSizedSentinelFor<S1, I1>)
 | |
| 	{
 | |
| 		checkf(FmtFirst - FmtLast <= 0, TEXT("Illegal range iterator. Please check HaystackFirst <= HaystackLast."));
 | |
| 	}
 | |
| 
 | |
| 	if constexpr (CSizedSentinelFor<S2, I2>)
 | |
| 	{
 | |
| 		checkf(OutputFirst - OutputLast <= 0, TEXT("Illegal range iterator. Please check NeedleFirst <= NeedleLast."));
 | |
| 	}
 | |
| 
 | |
| 	return Algorithms::Format(Ranges::View(MoveTemp(OutputFirst), OutputLast), Ranges::View(MoveTemp(FmtFirst), FmtLast), Forward<Ts>(Args)...);
 | |
| }
 | |
| 
 | |
| NAMESPACE_END(Algorithms)
 | |
| 
 | |
| /**
 | |
|  * A formatter for null-terminated string.
 | |
|  *
 | |
|  * The syntax of format specifications is:
 | |
|  *
 | |
|  *	[Fill And Align] [Width] [Precision] [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 precision part:
 | |
|  *
 | |
|  *	- '.N':   The number is used to specify the maximum field width of the object.
 | |
|  *	          N should be an unsigned non-zero decimal number.
 | |
|  *	- '.{N}': Dynamically determine the maximum 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.
 | |
|  *
 | |
|  * 4. The type indicator part:
 | |
|  *
 | |
|  *	- none: Indicates the as-is formatting.
 | |
|  *	- 'S':  Indicates the as-is formatting.
 | |
|  *	- 's':  Indicates lowercase formatting.
 | |
|  *
 | |
|  * 5. The case indicators part:
 | |
|  *
 | |
|  *	- '!': Indicates capitalize the entire string.
 | |
|  *
 | |
|  * 6. The escape indicators part:
 | |
|  *
 | |
|  *	- '?': Indicates the escape formatting.
 | |
|  *
 | |
|  */
 | |
| template <CCharType T>
 | |
| class TFormatter<T*, T>
 | |
| {
 | |
| private:
 | |
| 
 | |
| 	using FCharType      = T;
 | |
| 	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, '<');
 | |
| 
 | |
| 			MinFieldWidth =  0;
 | |
| 			MaxFieldWidth = -1;
 | |
| 
 | |
| 			bDynamicMin = false;
 | |
| 			bDynamicMax = false;
 | |
| 
 | |
| 			bLowercase = false;
 | |
| 			bUppercase = false;
 | |
| 			bEscape    = 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, '{'))
 | |
| 			{
 | |
| 				bDynamicMin   = true;
 | |
| 				MinFieldWidth = INDEX_NONE;
 | |
| 
 | |
| 				if (Iter == Sent) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 
 | |
| 			if ((bDynamicMin || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char))
 | |
| 			{
 | |
| 				MinFieldWidth = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 				while (true)
 | |
| 				{
 | |
| 					if (Iter == Sent)
 | |
| 					{
 | |
| 						checkf(!bDynamicMin, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 						return Iter;
 | |
| 					}
 | |
| 
 | |
| 					if (!bDynamicMin && *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 					Char = *Iter; ++Iter;
 | |
| 
 | |
| 					const uint Digit = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 					if (Digit >= 10) break;
 | |
| 
 | |
| 					MinFieldWidth = MinFieldWidth * 10 + Digit;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (bDynamicMin)
 | |
| 			{
 | |
| 				if (Char != LITERAL(FCharType, '}')) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				do
 | |
| 				{
 | |
| 					// Try to automatic indexing.
 | |
| 					if (MinFieldWidth == INDEX_NONE)
 | |
| 					{
 | |
| 						MinFieldWidth = Context.GetNextIndex();
 | |
| 
 | |
| 						if (MinFieldWidth == INDEX_NONE) UNLIKELY
 | |
| 						{
 | |
| 							checkf(false, TEXT("Illegal index. Please check the field width."));
 | |
| 						}
 | |
| 						else break;
 | |
| 					}
 | |
| 
 | |
| 					// Try to manual indexing.
 | |
| 					else if (!Context.CheckIndex(MinFieldWidth)) UNLIKELY
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the field width."));
 | |
| 					}
 | |
| 
 | |
| 					else break;
 | |
| 
 | |
| 					bDynamicMin   = false;
 | |
| 					MinFieldWidth = 0;
 | |
| 				}
 | |
| 				while (false);
 | |
| 
 | |
| 				if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the precision part.
 | |
| 		if (Char == LITERAL(FCharType, '.'))
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing precision in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 
 | |
| 			if (Char == LITERAL(FCharType, '{'))
 | |
| 			{
 | |
| 				bDynamicMax   = true;
 | |
| 				MaxFieldWidth = INDEX_NONE;
 | |
| 
 | |
| 				if (Iter == Sent) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 
 | |
| 			if ((bDynamicMax || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char))
 | |
| 			{
 | |
| 				MaxFieldWidth = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 				while (true)
 | |
| 				{
 | |
| 					if (Iter == Sent)
 | |
| 					{
 | |
| 						checkf(!bDynamicMax, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 						return Iter;
 | |
| 					}
 | |
| 
 | |
| 					if (!bDynamicMax && *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 					Char = *Iter; ++Iter;
 | |
| 
 | |
| 					const uint Digit = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 					if (Digit >= 10) break;
 | |
| 
 | |
| 					MaxFieldWidth = MaxFieldWidth * 10 + Digit;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			else if (!bDynamicMax)
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing precision in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			if (bDynamicMax)
 | |
| 			{
 | |
| 				if (Char != LITERAL(FCharType, '}')) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				do
 | |
| 				{
 | |
| 					// Try to automatic indexing.
 | |
| 					if (MaxFieldWidth == INDEX_NONE)
 | |
| 					{
 | |
| 						MaxFieldWidth = Context.GetNextIndex();
 | |
| 
 | |
| 						if (MaxFieldWidth == INDEX_NONE) UNLIKELY
 | |
| 						{
 | |
| 							checkf(false, TEXT("Illegal index. Please check the precision."));
 | |
| 						}
 | |
| 						else break;
 | |
| 					}
 | |
| 
 | |
| 					// Try to manual indexing.
 | |
| 					else if (!Context.CheckIndex(MaxFieldWidth)) UNLIKELY
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the precision."));
 | |
| 					}
 | |
| 
 | |
| 					else break;
 | |
| 
 | |
| 					bDynamicMax   = false;
 | |
| 					MaxFieldWidth = -1;
 | |
| 				}
 | |
| 				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, 's'): bLowercase = true; break;
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'S'):
 | |
| 		case LITERAL(FCharType, 's'): 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;
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the escape indicators part.
 | |
| 		if (Char == LITERAL(FCharType, '?'))
 | |
| 		{
 | |
| 			bEscape = 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(const FCharType* Object, CTX& Context) const
 | |
| 	{
 | |
| 		auto Iter = Ranges::Begin(Context);
 | |
| 		auto Sent = Ranges::End  (Context);
 | |
| 
 | |
| 		size_t MinDynamicField = MinFieldWidth;
 | |
| 		size_t MaxDynamicField = MaxFieldWidth;
 | |
| 
 | |
| 		// Visit the dynamic width argument.
 | |
| 		if (bDynamicMin)
 | |
| 		{
 | |
| 			MinDynamicField = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					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;
 | |
| 				}
 | |
| 			}
 | |
| 			, MinFieldWidth);
 | |
| 		}
 | |
| 
 | |
| 		// Visit the dynamic precision argument.
 | |
| 		if (bDynamicMax)
 | |
| 		{
 | |
| 			MaxDynamicField = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					checkf(Value > 0, TEXT("Illegal format argument. The dynamic precision argument must be a unsigned non-zero number."));
 | |
| 
 | |
| 					return Math::Max(Value, 1);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format argument. The dynamic precision argument must be an integral."));
 | |
| 
 | |
| 					return 0;
 | |
| 				}
 | |
| 			}
 | |
| 			, MaxFieldWidth);
 | |
| 		}
 | |
| 
 | |
| 		size_t LeftPadding  = 0;
 | |
| 		size_t RightPadding = 0;
 | |
| 
 | |
| 		// Estimate the field width.
 | |
| 		if (MinDynamicField != 0)
 | |
| 		{
 | |
| 			// If escape formatting is enabled, add quotes characters.
 | |
| 			size_t FieldWidth = bEscape ? 2 : 0;
 | |
| 
 | |
| 			for (const FCharType* Ptr = Object; *Ptr != LITERAL(FCharType, '\0'); ++Ptr)
 | |
| 			{
 | |
| 				if (bEscape)
 | |
| 				{
 | |
| 					switch (const FCharType Char = *Ptr)
 | |
| 					{
 | |
| 					case LITERAL(FCharType, '\"'):
 | |
| 					case LITERAL(FCharType, '\\'):
 | |
| 					case LITERAL(FCharType, '\a'):
 | |
| 					case LITERAL(FCharType, '\b'):
 | |
| 					case LITERAL(FCharType, '\f'):
 | |
| 					case LITERAL(FCharType, '\n'):
 | |
| 					case LITERAL(FCharType, '\r'):
 | |
| 					case LITERAL(FCharType, '\t'):
 | |
| 					case LITERAL(FCharType, '\v'): FieldWidth += 2; break;
 | |
| 					default:
 | |
| 						{
 | |
| 							// Use '\x00' format for other non-printable characters.
 | |
| 							if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char))
 | |
| 							{
 | |
| 								FieldWidth += 2 + sizeof(FCharType) * 2;
 | |
| 							}
 | |
| 
 | |
| 							else ++FieldWidth;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				else ++FieldWidth;
 | |
| 			}
 | |
| 
 | |
| 			const size_t PaddingWidth = MinDynamicField - Math::Min(FieldWidth, MinDynamicField, MaxDynamicField);
 | |
| 
 | |
| 			switch (AlignOption)
 | |
| 			{
 | |
| 			default:
 | |
| 			case LITERAL(FCharType, '<'): RightPadding = PaddingWidth; break;
 | |
| 			case LITERAL(FCharType, '>'): LeftPadding  = PaddingWidth; break;
 | |
| 			case LITERAL(FCharType, '^'):
 | |
| 				LeftPadding  = Math::DivAndFloor(PaddingWidth, 2);
 | |
| 				RightPadding = PaddingWidth - LeftPadding;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// 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 left quote.
 | |
| 		if (bEscape)
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			*Iter++ = LITERAL(FCharType, '\"');
 | |
| 		}
 | |
| 
 | |
| 		const FCharType* Ptr = Object;
 | |
| 
 | |
| 		bool bComplete = false;
 | |
| 
 | |
| 		// Write the object, include escaped quotes in the counter.
 | |
| 		for (size_t Index = bEscape ? 1 : 0; Index != MaxDynamicField; ++Index)
 | |
| 		{
 | |
| 			if (*Ptr == LITERAL(FCharType, '\0'))
 | |
| 			{
 | |
| 				bComplete = true;
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			FCharType Char = *Ptr++;
 | |
| 
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			// Convert the character case.
 | |
| 			if (bLowercase) Char = FCharTraits::ToLower(Char);
 | |
| 			if (bUppercase) Char = FCharTraits::ToUpper(Char);
 | |
| 
 | |
| 			if (bEscape)
 | |
| 			{
 | |
| 				switch (Char)
 | |
| 				{
 | |
| 				case LITERAL(FCharType, '\"'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\"'); break;
 | |
| 				case LITERAL(FCharType, '\\'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\\'); break;
 | |
| 				case LITERAL(FCharType, '\a'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'a');  break;
 | |
| 				case LITERAL(FCharType, '\b'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'b');  break;
 | |
| 				case LITERAL(FCharType, '\f'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'f');  break;
 | |
| 				case LITERAL(FCharType, '\n'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'n');  break;
 | |
| 				case LITERAL(FCharType, '\r'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'r');  break;
 | |
| 				case LITERAL(FCharType, '\t'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 't');  break;
 | |
| 				case LITERAL(FCharType, '\v'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'v');  break;
 | |
| 				default:
 | |
| 					{
 | |
| 						// Use '\x00' format for other non-printable characters.
 | |
| 						if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char))
 | |
| 						{
 | |
| 							*Iter++ = LITERAL(FCharType, '\\');
 | |
| 							*Iter++ = LITERAL(FCharType, 'x' );
 | |
| 
 | |
| 							using FUnsignedT = TMakeUnsigned<FCharType>;
 | |
| 
 | |
| 							constexpr size_t DigitNum = sizeof(FCharType) * 2;
 | |
| 
 | |
| 							FUnsignedT IntValue = static_cast<FUnsignedT>(Char);
 | |
| 
 | |
| 							TStaticArray<FCharType, DigitNum> Buffer;
 | |
| 
 | |
| 							for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex)
 | |
| 							{
 | |
| 								Buffer[DigitNum - Jndex - 1] = FCharTraits::FromDigit(IntValue & 0xF);
 | |
| 
 | |
| 								IntValue >>= 4;
 | |
| 							}
 | |
| 
 | |
| 							check(IntValue == 0);
 | |
| 
 | |
| 							for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex)
 | |
| 							{
 | |
| 								if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 								*Iter++ = Buffer[Jndex];
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						else *Iter++ = Char;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			else *Iter++ = Char;
 | |
| 		}
 | |
| 
 | |
| 		// Write the right quote, if the field width is enough.
 | |
| 		if (bEscape && bComplete)
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			*Iter++ = LITERAL(FCharType, '\"');
 | |
| 		}
 | |
| 
 | |
| 		// 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 MinFieldWidth =  0;
 | |
| 	size_t MaxFieldWidth = -1;
 | |
| 
 | |
| 	bool bDynamicMin = false;
 | |
| 	bool bDynamicMax = false;
 | |
| 
 | |
| 	bool bLowercase = false;
 | |
| 	bool bUppercase = false;
 | |
| 	bool bEscape    = false;
 | |
| 
 | |
| };
 | |
| 
 | |
| template <CCharType T>           class TFormatter<const T*  , T> : public TFormatter<T*, T> { };
 | |
| template <CCharType T, size_t N> class TFormatter<      T[N], T> : public TFormatter<T*, T> { };
 | |
| template <CCharType T, size_t N> class TFormatter<const T[N], T> : public TFormatter<T*, T> { };
 | |
| 
 | |
| static_assert(CFormattable<      char*>);
 | |
| static_assert(CFormattable<const char*>);
 | |
| 
 | |
| static_assert(CFormattable<      char[256]>);
 | |
| static_assert(CFormattable<const char[256]>);
 | |
| 
 | |
| /**
 | |
|  * A formatter for integral like types.
 | |
|  *
 | |
|  * The syntax of format specifications is:
 | |
|  *
 | |
|  *	[Fill And Align] [Sign] [#] [0] [Width] [Base] [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.
 | |
|  *		- '^': 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.
 | |
|  *		       This is default option.
 | |
|  *
 | |
|  * 2. The sign part:
 | |
|  *
 | |
|  *	This part is allowed only if the type indicator part is not 'C', 'c', 'S' or 's'.
 | |
|  *
 | |
|  *	- '+': Always include a sign character before the number. Use '+' for positive.
 | |
|  *	- '-': Only include a sign character before the number if the number is negative. This is default option.
 | |
|  *	- ' ': Always include a sign character before the number. Use ' ' for positive.
 | |
|  *
 | |
|  * 3. The alternate form indicator part:
 | |
|  *
 | |
|  *	This part is allowed only if the type indicator part is not 'C', 'c', 'S' or 's'.
 | |
|  *
 | |
|  *	- '#': Insert the prefix '0x' for hexadecimal numbers, '0' for octal numbers, '0b' for binary numbers.
 | |
|  *
 | |
|  * 4. The zero padding part:
 | |
|  *
 | |
|  *	This part is allowed only if the type indicator part is not 'C', 'c', 'S' or 's'.
 | |
|  *
 | |
|  *	- '0': By adding the prefix '0' to satisfy the minimum field width of the object.
 | |
|  *	       if the object is normal number.
 | |
|  *
 | |
|  * 5. 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.
 | |
|  *
 | |
|  * 5. The base part:
 | |
|  *
 | |
|  *	This part is allowed only if the type indicator part is 'I' or 'i'.
 | |
|  *
 | |
|  *	- '_N':   The number is override the base of the number.
 | |
|  *	          N should be an unsigned non-zero decimal number.
 | |
|  *	- '_{N}': Dynamically override the base of the number.
 | |
|  *	          N should be a valid index of the format integral argument.
 | |
|  *	          N is optional, and the default value is automatic indexing.
 | |
|  *
 | |
|  * 7. The type indicator part:
 | |
|  *
 | |
|  *	- none: Same as 'D' if the object is integral type.
 | |
|  *	        Same as 'C' if the object is target character type.
 | |
|  *	        Same as 'S' if the object is boolean type.
 | |
|  *	        Others are asserted failure.
 | |
|  *
 | |
|  *	- 'I': Indicates the uppercase integer formatting.
 | |
|  *	- 'i': Indicates the lowercase integer formatting.
 | |
|  *
 | |
|  *	- 'B': Indicates the binary formatting.                Same as '_2I'.
 | |
|  *	- 'b': Indicates the binary formatting.                Same as '_2I'.
 | |
|  *	- 'O': Indicates the octal formatting.                 Same as '_8I'.
 | |
|  *	- 'o': Indicates the octal formatting.                 Same as '_8I'.
 | |
|  *	- 'D': Indicates the decimal formatting.               Same as '_10I'.
 | |
|  *	- 'd': Indicates the decimal formatting.               Same as '_10I'.
 | |
|  *	- 'X': Indicates the uppercase hexadecimal formatting. Same as '_16I'.
 | |
|  *	- 'x': Indicates the lowercase hexadecimal formatting. Same as '_16I'.
 | |
|  *
 | |
|  *	If the object is not boolean type and is a valid character value.
 | |
|  *
 | |
|  *	- 'C': Indicates the the as-is character formatting.
 | |
|  *	- 'c': Indicates the lowercase character formatting.
 | |
|  *
 | |
|  *	If the object is boolean type.
 | |
|  *
 | |
|  *	- 'C': Indicates the uppercase character formatting, as 'T' or 'F'.
 | |
|  *	- 'c': Indicates the lowercase character formatting, as 't' or 'f'.
 | |
|  *
 | |
|  *	- 'S': Indicates the normal string formatting,    as 'True' or 'False'.
 | |
|  *	- 's': Indicates the lowercase string formatting, as 'true' or "false'.
 | |
|  *
 | |
|  * 8. The case indicators part:
 | |
|  *
 | |
|  *	- '!': Indicates capitalize the entire string.
 | |
|  *
 | |
|  * 9. The escape indicators part:
 | |
|  *
 | |
|  *	This part is allowed only if the type indicator part is 'C', 'c', 'S' or 's'.
 | |
|  *
 | |
|  *	- '?': Indicates the escape formatting.
 | |
|  *
 | |
|  */
 | |
| template <CIntegral T, CCharType CharType> requires (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      = CSameAs<T, FCharType> || CSameAs<T, bool> ? LITERAL(FCharType, '<') : LITERAL(FCharType, '>');
 | |
| 
 | |
| 			SignOption = LITERAL(FCharType, '-');
 | |
| 
 | |
| 			bAlternateForm = false;
 | |
| 			bZeroPadding   = false;
 | |
| 
 | |
| 			FieldWidth   = 0;
 | |
| 			IntegralBase = 10;
 | |
| 
 | |
| 			bDynamicWidth = false;
 | |
| 			bDynamicBase  = false;
 | |
| 
 | |
| 			bCharacter = CSameAs<T, FCharType>;
 | |
| 			bString    = CSameAs<T, bool>;
 | |
| 
 | |
| 			bLowercase = false;
 | |
| 			bUppercase = false;
 | |
| 			bEscape    = false;
 | |
| 		}
 | |
| 
 | |
| 		// If the format description string is empty.
 | |
| 		if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 		FCharType Char = *Iter; ++Iter;
 | |
| 
 | |
| 		// Flag indicates that the part is specified.
 | |
| 		bool bHasFillAndAlign = false;
 | |
| 		bool bHasSignOption   = false;
 | |
| 		bool bHasIntegralBase = false;
 | |
| 
 | |
| 		// 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;
 | |
| 			}
 | |
| 
 | |
| 			bHasFillAndAlign = true;
 | |
| 
 | |
| 			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;
 | |
| 
 | |
| 			bHasFillAndAlign = true;
 | |
| 
 | |
| 			AlignOption = Char;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 		while (false);
 | |
| 
 | |
| 		// The flag indicates that the type indicator part defaults to 'D'.
 | |
| 		// Use to check the sign part, alternate form indicator part or zero padding part is allowed
 | |
| 		// when there is no type indicator part.
 | |
| 		constexpr bool bIntegral = !CSameAs<T, FCharType> && !CSameAs<T, bool>;
 | |
| 
 | |
| 		// Try to parse the sign part.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, '+'):
 | |
| 		case LITERAL(FCharType, '-'):
 | |
| 		case LITERAL(FCharType, ' '):
 | |
| 
 | |
| 			bHasSignOption = true;
 | |
| 
 | |
| 			SignOption = Char;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}'))
 | |
| 			{
 | |
| 				checkf(bIntegral, TEXT("Illegal format string. The sign option is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the alternate form indicator part.
 | |
| 		if (Char == LITERAL(FCharType, '#'))
 | |
| 		{
 | |
| 			bAlternateForm = true;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}'))
 | |
| 			{
 | |
| 				checkf(bIntegral, TEXT("Illegal format string. The alternate form is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the zero padding part.
 | |
| 		if (Char == LITERAL(FCharType, '0'))
 | |
| 		{
 | |
| 			bZeroPadding = true;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}'))
 | |
| 			{
 | |
| 				checkf(bIntegral, TEXT("Illegal format string. The zero padding is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 
 | |
| 		// 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 base part.
 | |
| 		if (Char == LITERAL(FCharType, '_'))
 | |
| 		{
 | |
| 			bHasIntegralBase = true;
 | |
| 
 | |
| 			if (Iter == Sent) UNLIKELY
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing base in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 
 | |
| 			if (Char == LITERAL(FCharType, '{'))
 | |
| 			{
 | |
| 				bDynamicBase = true;
 | |
| 				IntegralBase = INDEX_NONE;
 | |
| 
 | |
| 				if (Iter == Sent) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 
 | |
| 			if ((bDynamicBase || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char))
 | |
| 			{
 | |
| 				IntegralBase = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 				while (true)
 | |
| 				{
 | |
| 					if (Iter == Sent)
 | |
| 					{
 | |
| 						checkf(!bDynamicBase, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 						checkf(false, TEXT("Illegal format string. Missing 'I' or 'i' in format string."));
 | |
| 
 | |
| 						return Iter;
 | |
| 					}
 | |
| 
 | |
| 					if (!bDynamicBase && *Iter == LITERAL(FCharType, '}'))
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal format string. Missing 'I' or 'i' in format string."));
 | |
| 
 | |
| 						return Iter;
 | |
| 					}
 | |
| 
 | |
| 					Char = *Iter; ++Iter;
 | |
| 
 | |
| 					const uint Digit = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 					if (Digit >= 10) break;
 | |
| 
 | |
| 					IntegralBase = IntegralBase * 10 + Digit;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			else if (!bDynamicBase)
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing base in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			if (bDynamicBase)
 | |
| 			{
 | |
| 				if (Char != LITERAL(FCharType, '}')) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				do
 | |
| 				{
 | |
| 					// Try to automatic indexing.
 | |
| 					if (IntegralBase == INDEX_NONE)
 | |
| 					{
 | |
| 						IntegralBase = Context.GetNextIndex();
 | |
| 
 | |
| 						if (IntegralBase == INDEX_NONE) UNLIKELY
 | |
| 						{
 | |
| 							checkf(false, TEXT("Illegal index. Please check the base."));
 | |
| 						}
 | |
| 						else break;
 | |
| 					}
 | |
| 
 | |
| 					// Try to manual indexing.
 | |
| 					else if (!Context.CheckIndex(IntegralBase)) UNLIKELY
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the base."));
 | |
| 					}
 | |
| 
 | |
| 					else break;
 | |
| 
 | |
| 					bDynamicBase = false;
 | |
| 					IntegralBase = 0;
 | |
| 				}
 | |
| 				while (false);
 | |
| 
 | |
| 				if (Iter == Sent || *Iter == LITERAL(FCharType, '}'))
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing 'I' or 'i' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the type indicators part.
 | |
| 
 | |
| 		const bool bHasAlternateForm = bAlternateForm == true;
 | |
| 		const bool bHasZeroPadding   = bZeroPadding   == true;
 | |
| 
 | |
| 		// If indicates this is lowercase.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'i'):
 | |
| 		case LITERAL(FCharType, 'b'):
 | |
| 		case LITERAL(FCharType, 'o'):
 | |
| 		case LITERAL(FCharType, 'd'):
 | |
| 		case LITERAL(FCharType, 'x'):
 | |
| 		case LITERAL(FCharType, 'c'):
 | |
| 		case LITERAL(FCharType, 's'): bLowercase = true; break;
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		// If indicates this is variable base integer.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'I'): case LITERAL(FCharType, 'i'):
 | |
| 
 | |
| 			checkf( bHasIntegralBase, TEXT("Illegal format string. The base is required for 'I' or 'i' type."));
 | |
| 
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			checkf(!bHasIntegralBase, TEXT("Illegal format string. The base is only allowed for 'I' or 'i' type."));
 | |
| 		}
 | |
| 
 | |
| 		// If indicates this is integral.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'I'): case LITERAL(FCharType, 'i'):                                          break;
 | |
| 		case LITERAL(FCharType, 'B'): case LITERAL(FCharType, 'b'): IntegralBase = 2;  bDynamicBase = false; break;
 | |
| 		case LITERAL(FCharType, 'O'): case LITERAL(FCharType, 'o'): IntegralBase = 8;  bDynamicBase = false; break;
 | |
| 		case LITERAL(FCharType, 'D'): case LITERAL(FCharType, 'd'): IntegralBase = 10; bDynamicBase = false; break;
 | |
| 		case LITERAL(FCharType, 'X'): case LITERAL(FCharType, 'x'): IntegralBase = 16; bDynamicBase = false; break;
 | |
| 		default: if constexpr (bIntegral) break;
 | |
| 		case LITERAL(FCharType, 'C'): case LITERAL(FCharType, 'c'):
 | |
| 		case LITERAL(FCharType, 'S'): case LITERAL(FCharType, 's'):
 | |
| 			checkf(!bHasSignOption,    TEXT("Illegal format string. The sign option is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 			checkf(!bHasAlternateForm, TEXT("Illegal format string. The alternate form is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 			checkf(!bHasZeroPadding,   TEXT("Illegal format string. The zero padding is not allowed for 'C', 'c', 'S' or 's' type."));
 | |
| 		}
 | |
| 
 | |
| 		// If indicates this is character or string.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'I'): case LITERAL(FCharType, 'i'):
 | |
| 		case LITERAL(FCharType, 'B'): case LITERAL(FCharType, 'b'):
 | |
| 		case LITERAL(FCharType, 'O'): case LITERAL(FCharType, 'o'):
 | |
| 		case LITERAL(FCharType, 'D'): case LITERAL(FCharType, 'd'):
 | |
| 		case LITERAL(FCharType, 'X'): case LITERAL(FCharType, 'x'): bCharacter = false; bString = false; break;
 | |
| 		case LITERAL(FCharType, 'C'): case LITERAL(FCharType, 'c'): bCharacter = true;  bString = false; break;
 | |
| 		case LITERAL(FCharType, 'S'): case LITERAL(FCharType, 's'): bCharacter = false; bString = true;  break;
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		if (!bHasFillAndAlign) AlignOption = bCharacter || bString ? LITERAL(FCharType, '<') : LITERAL(FCharType, '>');
 | |
| 
 | |
| 		checkf((!bString || CSameAs<T, bool>), TEXT("Illegal format string. The 'S' or 's' type is only allowed for boolean type."));
 | |
| 
 | |
| 		// If exists the type indicators part.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'I'): case LITERAL(FCharType, 'i'):
 | |
| 		case LITERAL(FCharType, 'B'): case LITERAL(FCharType, 'b'):
 | |
| 		case LITERAL(FCharType, 'O'): case LITERAL(FCharType, 'o'):
 | |
| 		case LITERAL(FCharType, 'D'): case LITERAL(FCharType, 'd'):
 | |
| 		case LITERAL(FCharType, 'X'): case LITERAL(FCharType, 'x'):
 | |
| 		case LITERAL(FCharType, 'C'): case LITERAL(FCharType, 'c'):
 | |
| 		case LITERAL(FCharType, 'S'): case LITERAL(FCharType, 's'): 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;
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the escape indicators part.
 | |
| 		if (Char == LITERAL(FCharType, '?') && (bCharacter || bString))
 | |
| 		{
 | |
| 			bEscape = 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;
 | |
| 		size_t TargetBase  = IntegralBase;
 | |
| 
 | |
| 		// Visit the dynamic width argument.
 | |
| 		if (bDynamicWidth)
 | |
| 		{
 | |
| 			TargetField = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					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);
 | |
| 		}
 | |
| 
 | |
| 		// Visit the dynamic base argument.
 | |
| 		if (bDynamicBase)
 | |
| 		{
 | |
| 			TargetBase = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					checkf(Math::IsWithinInclusive(Value, 2, 36), TEXT("Illegal format argument. The dynamic base argument must be in the range [2, 36]."));
 | |
| 
 | |
| 					return Math::Max(Value, 1);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format argument. The dynamic base argument must be an integral."));
 | |
| 
 | |
| 					return 0;
 | |
| 				}
 | |
| 			}
 | |
| 			, IntegralBase);
 | |
| 		}
 | |
| 
 | |
| 		bool bNegative = false;
 | |
| 
 | |
| 		bool bNormal = false;
 | |
| 
 | |
| 		size_t TargetWidth;
 | |
| 
 | |
| 		const FCharType* Target = nullptr;
 | |
| 
 | |
| 		constexpr size_t BufferSize = sizeof(T) * 8;
 | |
| 
 | |
| 		TStaticArray<FCharType, BufferSize> Buffer;
 | |
| 
 | |
| 		do
 | |
| 		{
 | |
| 			// Handle the literal boolean type.
 | |
| 			if constexpr (CSameAs<T, bool>) if (bCharacter || bString)
 | |
| 			{
 | |
| 				TargetWidth = bCharacter ? 1 : Object ? 4 : 5;
 | |
| 
 | |
| 				Target = Object ? LITERAL(FCharType, "True") : LITERAL(FCharType, "False");
 | |
| 
 | |
| 				// Convert the character case.
 | |
| 				if (bLowercase) Target = Object ? LITERAL(FCharType, "true") : LITERAL(FCharType, "false");
 | |
| 				if (bUppercase) Target = Object ? LITERAL(FCharType, "TRUE") : LITERAL(FCharType, "FALSE");
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			// Handle the literal character type.
 | |
| 			if constexpr (!CSameAs<T, bool>) if (bCharacter)
 | |
| 			{
 | |
| 				TargetWidth = 1;
 | |
| 
 | |
| 				FCharType Char = static_cast<FCharType>(Object);
 | |
| 
 | |
| 				checkf(Char == Object, TEXT("Illegal format argument. The integral value is not a valid character."));
 | |
| 
 | |
| 				// Convert the character case.
 | |
| 				if (bLowercase) Char = FCharTraits::ToLower(Char);
 | |
| 				if (bUppercase) Char = FCharTraits::ToUpper(Char);
 | |
| 
 | |
| 				Buffer[0] = Char;
 | |
| 
 | |
| 				Target = Buffer.GetData();
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			bNormal = true;
 | |
| 
 | |
| 			// Handle the illegal base.
 | |
| 			if (!Math::IsWithinInclusive(TargetBase, 2, 36))
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format argument. The base must be in the range [2, 36]."));
 | |
| 
 | |
| 				TargetBase = 10;
 | |
| 			}
 | |
| 
 | |
| 			// Handle the integral boolean type.
 | |
| 			if constexpr (CSameAs<T, bool>)
 | |
| 			{
 | |
| 				TargetWidth = 1;
 | |
| 
 | |
| 				Buffer[0] = Object ? LITERAL(FCharType, '1') : LITERAL(FCharType, '0');
 | |
| 
 | |
| 				Target = Buffer.GetData();
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			// Handle the integral type.
 | |
| 			else
 | |
| 			{
 | |
| 				using FUnsignedT = TMakeUnsigned<T>;
 | |
| 
 | |
| 				FUnsignedT Unsigned = static_cast<FUnsignedT>(Object);
 | |
| 
 | |
| 				if constexpr (CSigned<T>)
 | |
| 				{
 | |
| 					if (Object < 0)
 | |
| 					{
 | |
| 						bNegative = true;
 | |
| 
 | |
| 						Unsigned = static_cast<FUnsignedT>(-Object);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				FCharType* DigitIter = Buffer.GetData() + BufferSize;
 | |
| 				FCharType* DigitSent = Buffer.GetData() + BufferSize;
 | |
| 
 | |
| 				switch (TargetBase)
 | |
| 				{
 | |
| 				case 0x02: do { *--DigitIter = static_cast<FCharType>('0' + (Unsigned & 0b00001));                            Unsigned >>= 1; } while (Unsigned != 0); break;
 | |
| 				case 0x04: do { *--DigitIter = static_cast<FCharType>('0' + (Unsigned & 0b00011));                            Unsigned >>= 2; } while (Unsigned != 0); break;
 | |
| 				case 0x08: do { *--DigitIter = static_cast<FCharType>('0' + (Unsigned & 0b00111));                            Unsigned >>= 3; } while (Unsigned != 0); break;
 | |
| 				case 0x10: do { *--DigitIter =        FCharTraits::FromDigit(Unsigned & 0b01111, bLowercase && !bUppercase);  Unsigned >>= 4; } while (Unsigned != 0); break;
 | |
| 				case 0X20: do { *--DigitIter =        FCharTraits::FromDigit(Unsigned & 0b11111, bLowercase && !bUppercase);  Unsigned >>= 5; } while (Unsigned != 0); break;
 | |
| 
 | |
| 				case 3:
 | |
| 				case 5:
 | |
| 				case 6:
 | |
| 				case 7:
 | |
| 				case 9:
 | |
| 				case 10: do { *--DigitIter = static_cast<FCharType>('0' + Unsigned % TargetBase);                            Unsigned = static_cast<FUnsignedT>(Unsigned / TargetBase); } while (Unsigned != 0); break;
 | |
| 				default: do { *--DigitIter =       FCharTraits::FromDigit(Unsigned % TargetBase, bLowercase && !bUppercase); Unsigned = static_cast<FUnsignedT>(Unsigned / TargetBase); } while (Unsigned != 0); break;
 | |
| 				}
 | |
| 
 | |
| 				TargetWidth = DigitSent - DigitIter;
 | |
| 
 | |
| 				Target = DigitIter;
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		while (false);
 | |
| 
 | |
| 		size_t ZeroPadding  = 0;
 | |
| 		size_t LeftPadding  = 0;
 | |
| 		size_t RightPadding = 0;
 | |
| 
 | |
| 		// Estimate the field width.
 | |
| 		if (TargetField != 0)
 | |
| 		{
 | |
| 			size_t LiteralWidth = TargetWidth;
 | |
| 
 | |
| 			// Handle the escape option.
 | |
| 			if (bEscape) LiteralWidth += 2;
 | |
| 
 | |
| 			// Handle the sign option.
 | |
| 			switch (SignOption)
 | |
| 			{
 | |
| 			case LITERAL(FCharType, '+'):
 | |
| 			case LITERAL(FCharType, ' '): LiteralWidth += 1; break;
 | |
| 			default: if (bNegative)       LiteralWidth += 1;
 | |
| 			}
 | |
| 
 | |
| 			// Handle the alternate form.
 | |
| 			if (bAlternateForm) switch (TargetBase)
 | |
| 			{
 | |
| 			case 0x02: LiteralWidth += 2; break;
 | |
| 			case 0x08: LiteralWidth += 1; break;
 | |
| 			case 0x10: LiteralWidth += 2; break;
 | |
| 			default: { }
 | |
| 			}
 | |
| 
 | |
| 			const size_t PaddingWidth = TargetField - Math::Min(LiteralWidth, TargetField);
 | |
| 
 | |
| 			if (!bZeroPadding || !bNormal)
 | |
| 			{
 | |
| 				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();
 | |
| 				}
 | |
| 			}
 | |
| 			else ZeroPadding = PaddingWidth;
 | |
| 		}
 | |
| 
 | |
| 		// 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 left quote.
 | |
| 		if (bEscape)
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			*Iter++ = bCharacter ? LITERAL(FCharType, '\'') : LITERAL(FCharType, '\"');
 | |
| 		}
 | |
| 
 | |
| 		// Write the object.
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			// Handle the sign option.
 | |
| 			switch (SignOption)
 | |
| 			{
 | |
| 			case LITERAL(FCharType, '+'): *Iter++ = bNegative ? LITERAL(FCharType, '-') : LITERAL(FCharType, '+'); break;
 | |
| 			case LITERAL(FCharType, ' '): *Iter++ = bNegative ? LITERAL(FCharType, '-') : LITERAL(FCharType, ' '); break;
 | |
| 			default: if (bNegative)       *Iter++ =             LITERAL(FCharType, '-');
 | |
| 			}
 | |
| 
 | |
| 			// Handle the alternate form.
 | |
| 			if (bAlternateForm)
 | |
| 			{
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				switch (TargetBase)
 | |
| 				{
 | |
| 				case 0x02:
 | |
| 				case 0x08:
 | |
| 				case 0x10: *Iter++ = LITERAL(FCharType, '0'); break;
 | |
| 				default: { }
 | |
| 				}
 | |
| 
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				switch (TargetBase)
 | |
| 				{
 | |
| 				case 0x02: *Iter++ = bUppercase ? LITERAL(FCharType, 'B') : LITERAL(FCharType, 'b'); break;
 | |
| 				case 0x10: *Iter++ = bUppercase ? LITERAL(FCharType, 'X') : LITERAL(FCharType, 'x'); break;
 | |
| 				default: { }
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Handle the zero padding.
 | |
| 			for (size_t Index = 0; Index != ZeroPadding; ++Index)
 | |
| 			{
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				*Iter++ = LITERAL(FCharType, '0');
 | |
| 			}
 | |
| 
 | |
| 			// Write the target object.
 | |
| 			for (size_t Index = 0; Index != TargetWidth; ++Index)
 | |
| 			{
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				*Iter++ = Target[Index];
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Write the right quote, if the field width is enough.
 | |
| 		if (bEscape)
 | |
| 		{
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			*Iter++ = bCharacter ? LITERAL(FCharType, '\'') : LITERAL(FCharType, '\"');
 | |
| 		}
 | |
| 
 | |
| 		// 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    = CSameAs<T, FCharType> || CSameAs<T, bool> ? LITERAL(FCharType, '<') : LITERAL(FCharType, '>');
 | |
| 
 | |
| 	FCharType SignOption = LITERAL(FCharType, '-');
 | |
| 
 | |
| 	bool bAlternateForm = false;
 | |
| 	bool bZeroPadding   = false;
 | |
| 
 | |
| 	size_t FieldWidth   = 0;
 | |
| 	size_t IntegralBase = 10;
 | |
| 
 | |
| 	bool bDynamicWidth = false;
 | |
| 	bool bDynamicBase  = false;
 | |
| 
 | |
| 	bool bCharacter = CSameAs<T, FCharType>;
 | |
| 	bool bString    = CSameAs<T, bool>;
 | |
| 
 | |
| 	bool bLowercase = false;
 | |
| 	bool bUppercase = false;
 | |
| 	bool bEscape    = false;
 | |
| 
 | |
| };
 | |
| 
 | |
| static_assert(CFormattable<int >);
 | |
| static_assert(CFormattable<char>);
 | |
| static_assert(CFormattable<bool>);
 | |
| 
 | |
| /**
 | |
|  * A formatter for the floating-point types.
 | |
|  *
 | |
|  * The syntax of format specifications is:
 | |
|  *
 | |
|  *	[Fill And Align] [Sign] [#] [0] [Width] [Precision] [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.
 | |
|  *		- '^': 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.
 | |
|  *		       This is default option.
 | |
|  *
 | |
|  * 2. The sign part:
 | |
|  *
 | |
|  *	- '+': Always include a sign character before the number. Use '+' for positive.
 | |
|  *	- '-': Only include a sign character before the number if the number is negative. This is default option.
 | |
|  *	- ' ': Always include a sign character before the number. Use ' ' for positive.
 | |
|  *
 | |
|  * 3. The alternate form indicator part:
 | |
|  *
 | |
|  *	- '#': Insert the decimal point character unconditionally,
 | |
|  *	       and do not remove trailing zeros if the type indicator part is 'G' or 'g'.
 | |
|  *
 | |
|  * 4. The zero padding part:
 | |
|  *
 | |
|  *	- '0': By adding the prefix '0' to satisfy the minimum field width of the object.
 | |
|  *	       if the object is normal number.
 | |
|  *
 | |
|  * 5. 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.
 | |
|  *
 | |
|  * 6. The precision part:
 | |
|  *
 | |
|  *	- '.N':   The number is used to specify the precision of the floating-point number.
 | |
|  *	          N should be an unsigned non-zero decimal number.
 | |
|  *	- '.{N}': Dynamically determine the precision of the floating-point number.
 | |
|  *	          N should be a valid index of the format integral argument.
 | |
|  *	          N is optional, and the default value is automatic indexing.
 | |
|  *
 | |
|  * 7. The type indicator part:
 | |
|  *
 | |
|  *	- none: Indicates the normal formatting.
 | |
|  *	- 'G':  Indicates the general formatting.
 | |
|  *	- 'g':  Indicates the general formatting.
 | |
|  *	- 'F':  Indicates the fixed-point formatting.
 | |
|  *	- 'f':  Indicates the fixed-point formatting.
 | |
|  *	- 'E':  Indicates the scientific formatting.
 | |
|  *	- 'e':  Indicates the scientific formatting.
 | |
|  *	- 'A':  Indicates the uppercase hexadecimal formatting.
 | |
|  *	- 'a':  Indicates the lowercase hexadecimal formatting.
 | |
|  *
 | |
|  * 8. The case indicators part:
 | |
|  *
 | |
|  *	- '!': Indicates capitalize the entire string.
 | |
|  *
 | |
|  */
 | |
| template <CFloatingPoint T, CCharType CharType> requires (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  = { LITERAL(FCharType, ' ') };
 | |
| 			AlignOption    =   LITERAL(FCharType, '>');
 | |
| 
 | |
| 			SignOption = LITERAL(FCharType, '-');
 | |
| 
 | |
| 			bAlternateForm = false;
 | |
| 			bZeroPadding   = false;
 | |
| 
 | |
| 			bHasPrecision = false;
 | |
| 
 | |
| 			FieldWidth = 0;
 | |
| 			Precision  = 0;
 | |
| 
 | |
| 			bDynamicWidth     = false;
 | |
| 			bDynamicPrecision = false;
 | |
| 
 | |
| 			bGeneral     = false;
 | |
| 			bFixedPoint  = false;
 | |
| 			bScientific  = false;
 | |
| 			bHexadecimal = false;
 | |
| 
 | |
| 			bLowercase = false;
 | |
| 			bUppercase = false;
 | |
| 		}
 | |
| 
 | |
| 		// If the format description string is empty.
 | |
| 		if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 		FCharType Char = *Iter; ++Iter;
 | |
| 
 | |
| 		// Flag indicates that the part is specified.
 | |
| 		bool bHasFillAndAlign = false;
 | |
| 
 | |
| 		// 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;
 | |
| 			}
 | |
| 
 | |
| 			bHasFillAndAlign = true;
 | |
| 
 | |
| 			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;
 | |
| 
 | |
| 			bHasFillAndAlign = true;
 | |
| 
 | |
| 			AlignOption = Char;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 		while (false);
 | |
| 
 | |
| 		// Try to parse the sign part.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, '+'):
 | |
| 		case LITERAL(FCharType, '-'):
 | |
| 		case LITERAL(FCharType, ' '):
 | |
| 
 | |
| 			SignOption = Char;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the alternate form indicator part.
 | |
| 		if (Char == LITERAL(FCharType, '#'))
 | |
| 		{
 | |
| 			bAlternateForm = true;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the zero padding part.
 | |
| 		if (Char == LITERAL(FCharType, '0'))
 | |
| 		{
 | |
| 			bZeroPadding = true;
 | |
| 
 | |
| 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 		}
 | |
| 
 | |
| 		// 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 precision part.
 | |
| 		if (Char == LITERAL(FCharType, '.'))
 | |
| 		{
 | |
| 			bHasPrecision = true;
 | |
| 
 | |
| 			if (Iter == Sent) UNLIKELY
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing precision in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			Char = *Iter; ++Iter;
 | |
| 
 | |
| 			if (Char == LITERAL(FCharType, '{'))
 | |
| 			{
 | |
| 				bDynamicPrecision = true;
 | |
| 				Precision         = INDEX_NONE;
 | |
| 
 | |
| 				if (Iter == Sent) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 
 | |
| 			if ((bDynamicPrecision || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char))
 | |
| 			{
 | |
| 				Precision = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 				while (true)
 | |
| 				{
 | |
| 					if (Iter == Sent)
 | |
| 					{
 | |
| 						checkf(!bDynamicPrecision, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 						return Iter;
 | |
| 					}
 | |
| 
 | |
| 					if (!bDynamicPrecision && *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 					Char = *Iter; ++Iter;
 | |
| 
 | |
| 					const uint Digit = FCharTraits::ToDigit(Char);
 | |
| 
 | |
| 					if (Digit >= 10) break;
 | |
| 
 | |
| 					Precision = Precision * 10 + Digit;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			else if (!bDynamicPrecision)
 | |
| 			{
 | |
| 				checkf(false, TEXT("Illegal format string. Missing precision in format string."));
 | |
| 
 | |
| 				return Iter;
 | |
| 			}
 | |
| 
 | |
| 			if (bDynamicPrecision)
 | |
| 			{
 | |
| 				if (Char != LITERAL(FCharType, '}')) UNLIKELY
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string."));
 | |
| 
 | |
| 					return Iter;
 | |
| 				}
 | |
| 
 | |
| 				do
 | |
| 				{
 | |
| 					// Try to automatic indexing.
 | |
| 					if (Precision == INDEX_NONE)
 | |
| 					{
 | |
| 						Precision = Context.GetNextIndex();
 | |
| 
 | |
| 						if (Precision == INDEX_NONE) UNLIKELY
 | |
| 						{
 | |
| 							checkf(false, TEXT("Illegal index. Please check the precision."));
 | |
| 						}
 | |
| 						else break;
 | |
| 					}
 | |
| 
 | |
| 					// Try to manual indexing.
 | |
| 					else if (!Context.CheckIndex(Precision)) UNLIKELY
 | |
| 					{
 | |
| 						checkf(false, TEXT("Illegal index. Please check the precision."));
 | |
| 					}
 | |
| 
 | |
| 					else break;
 | |
| 
 | |
| 					bDynamicPrecision = false;
 | |
| 					Precision         = 0;
 | |
| 				}
 | |
| 				while (false);
 | |
| 
 | |
| 				if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter;
 | |
| 
 | |
| 				Char = *Iter; ++Iter;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the type indicators part.
 | |
| 
 | |
| 		// If indicates this is lowercase.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'g'):
 | |
| 		case LITERAL(FCharType, 'f'):
 | |
| 		case LITERAL(FCharType, 'e'):
 | |
| 		case LITERAL(FCharType, 'a'): bLowercase = true; break;
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		// If indicates this is not normal formatting.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'G'): case LITERAL(FCharType, 'g'): bGeneral     = true; break;
 | |
| 		case LITERAL(FCharType, 'F'): case LITERAL(FCharType, 'f'): bFixedPoint  = true; break;
 | |
| 		case LITERAL(FCharType, 'E'): case LITERAL(FCharType, 'e'): bScientific  = true; break;
 | |
| 		case LITERAL(FCharType, 'A'): case LITERAL(FCharType, 'a'): bHexadecimal = true; break;
 | |
| 		default: { }
 | |
| 		}
 | |
| 
 | |
| 		// If exists the type indicators part.
 | |
| 		switch (Char)
 | |
| 		{
 | |
| 		case LITERAL(FCharType, 'G'): case LITERAL(FCharType, 'g'):
 | |
| 		case LITERAL(FCharType, 'F'): case LITERAL(FCharType, 'f'):
 | |
| 		case LITERAL(FCharType, 'E'): case LITERAL(FCharType, 'e'):
 | |
| 		case LITERAL(FCharType, 'A'): case LITERAL(FCharType, 'a'): 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;
 | |
| 		size_t TargetPrecision = Precision;
 | |
| 
 | |
| 		// Visit the dynamic width argument.
 | |
| 		if (bDynamicWidth)
 | |
| 		{
 | |
| 			TargetField = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					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);
 | |
| 		}
 | |
| 
 | |
| 		// Visit the dynamic precision argument.
 | |
| 		if (bDynamicPrecision)
 | |
| 		{
 | |
| 			TargetPrecision = Context.Visit([]<typename U>(U&& Value) -> size_t
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					checkf(Value >= 0, TEXT("Illegal format argument. The dynamic precision argument must be a unsigned number."));
 | |
| 
 | |
| 					return Math::Max(Value, 1);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					checkf(false, TEXT("Illegal format argument. The dynamic precision argument must be an integral."));
 | |
| 
 | |
| 					return 0;
 | |
| 				}
 | |
| 			}
 | |
| 			, Precision);
 | |
| 		}
 | |
| 
 | |
| 		const bool bNegative = Math::IsNegative(Object);
 | |
| 
 | |
| 		bool bNormal = false;
 | |
| 
 | |
| 		size_t TargetWidth;
 | |
| 
 | |
| 		const char* Target = nullptr;
 | |
| 
 | |
| 		constexpr size_t StartingBufferSize = 64;
 | |
| 
 | |
| 		TArray<char, TInlineAllocator<StartingBufferSize>> Buffer(StartingBufferSize);
 | |
| 
 | |
| 		// Handle the infinite value.
 | |
| 		if (Math::IsInfinity(Object))
 | |
| 		{
 | |
| 			TargetWidth = 8;
 | |
| 
 | |
| 			Target = TEXT("Infinity");
 | |
| 
 | |
| 			// Convert the character case.
 | |
| 			if (bLowercase) Target = TEXT("infinity");
 | |
| 			if (bUppercase) Target = TEXT("INFINITY");
 | |
| 		}
 | |
| 
 | |
| 		// Handle the NaN value.
 | |
| 		else if (Math::IsNaN(Object))
 | |
| 		{
 | |
| 			TargetWidth = 3;
 | |
| 
 | |
| 			Target = TEXT("NaN");
 | |
| 
 | |
| 			// Convert the character case.
 | |
| 			if (bLowercase) Target = TEXT("nan");
 | |
| 			if (bUppercase) Target = TEXT("NAN");
 | |
| 		}
 | |
| 
 | |
| 		// Handle the normal value.
 | |
| 		else
 | |
| 		{
 | |
| 			bNormal = true;
 | |
| 
 | |
| 			NAMESPACE_STD::to_chars_result ConvertResult;
 | |
| 
 | |
| 			while (true)
 | |
| 			{
 | |
| 				if (bHasPrecision)
 | |
| 				{
 | |
| 					check(static_cast<int>(TargetPrecision) >= 0);
 | |
| 
 | |
| 					if      (bGeneral    ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::general   , static_cast<int>(TargetPrecision));
 | |
| 					else if (bFixedPoint ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::fixed     , static_cast<int>(TargetPrecision));
 | |
| 					else if (bScientific ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::scientific, static_cast<int>(TargetPrecision));
 | |
| 					else if (bHexadecimal) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::hex       , static_cast<int>(TargetPrecision));
 | |
| 					else                   ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::general   , static_cast<int>(TargetPrecision));
 | |
| 				}
 | |
| 
 | |
| 				else
 | |
| 				{
 | |
| 					if      (bGeneral    ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::general   );
 | |
| 					else if (bFixedPoint ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::fixed     );
 | |
| 					else if (bScientific ) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::scientific);
 | |
| 					else if (bHexadecimal) ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object, NAMESPACE_STD::chars_format::hex       );
 | |
| 					else                   ConvertResult = NAMESPACE_STD::to_chars(Buffer.GetData(), Buffer.GetData() + Buffer.Num(), Object);
 | |
| 				}
 | |
| 
 | |
| 				if (ConvertResult.ec != NAMESPACE_STD::errc::value_too_large) break;
 | |
| 
 | |
| 				Buffer.SetNum(Buffer.Num() * 2);
 | |
| 			}
 | |
| 
 | |
| 			Buffer.SetNum(ConvertResult.ptr - Buffer.GetData());
 | |
| 
 | |
| 			// Remove the negative sign.
 | |
| 			if (Buffer.Front() == TEXT('-')) Buffer.StableErase(Buffer.Begin());
 | |
| 
 | |
| 			// Handle the alternate form.
 | |
| 			if (bAlternateForm)
 | |
| 			{
 | |
| 				const char ExponentChar = bHexadecimal ? TEXT('p') : TEXT('e');
 | |
| 
 | |
| 				auto BufferIter = Buffer.Begin();
 | |
| 
 | |
| 				// Insert the decimal point character.
 | |
| 				while (true)
 | |
| 				{
 | |
| 					if (BufferIter == Buffer.End())
 | |
| 					{
 | |
| 						Buffer.PushBack(TEXT('.'));
 | |
| 
 | |
| 						BufferIter = Algorithms::Prev(Buffer.End());
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 
 | |
| 					if (*BufferIter == ExponentChar)
 | |
| 					{
 | |
| 						BufferIter = Buffer.Insert(BufferIter, TEXT('.'));
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 
 | |
| 					if (*BufferIter == TEXT('.')) break;
 | |
| 
 | |
| 					++BufferIter;
 | |
| 				}
 | |
| 
 | |
| 				// Restore trailing zeros.
 | |
| 				if (bGeneral)
 | |
| 				{
 | |
| 					if (!bHasPrecision) TargetPrecision = 6;
 | |
| 
 | |
| 					size_t DigitNum = BufferIter - Buffer.Begin();
 | |
| 
 | |
| 					++BufferIter;
 | |
| 
 | |
| 					while (true)
 | |
| 					{
 | |
| 						if (DigitNum >= TargetPrecision) break;
 | |
| 
 | |
| 						if (BufferIter == Buffer.End())
 | |
| 						{
 | |
| 							Buffer.SetNum(Buffer.Num() + TargetPrecision - DigitNum, TEXT('0'));
 | |
| 
 | |
| 							break;
 | |
| 						}
 | |
| 
 | |
| 						if (*BufferIter == ExponentChar)
 | |
| 						{
 | |
| 							Buffer.Insert(BufferIter, TargetPrecision - DigitNum, TEXT('0'));
 | |
| 
 | |
| 							break;
 | |
| 						}
 | |
| 
 | |
| 						++BufferIter;
 | |
| 						++DigitNum;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Convert the character case.
 | |
| 			if (!bLowercase || bUppercase) for (char& Char : Buffer)
 | |
| 			{
 | |
| 				// Convert the exponent character.
 | |
| 				if      ( bHexadecimal && Char == TEXT('p')) Char = bUppercase ? TEXT('P') : TEXT('p');
 | |
| 				else if (!bHexadecimal && Char == TEXT('e')) Char = bUppercase ? TEXT('E') : TEXT('e');
 | |
| 
 | |
| 				// Convert the digit character.
 | |
| 				else if (!bLowercase) Char = FChar::ToUpper(Char);
 | |
| 			}
 | |
| 
 | |
| 			TargetWidth = Buffer.Num();
 | |
| 
 | |
| 			Target = Buffer.GetData();
 | |
| 		}
 | |
| 
 | |
| 		size_t ZeroPadding  = 0;
 | |
| 		size_t LeftPadding  = 0;
 | |
| 		size_t RightPadding = 0;
 | |
| 
 | |
| 		// Estimate the field width.
 | |
| 		if (TargetField != 0)
 | |
| 		{
 | |
| 			size_t LiteralWidth = TargetWidth;
 | |
| 
 | |
| 			// Handle the sign option.
 | |
| 			switch (SignOption)
 | |
| 			{
 | |
| 			case LITERAL(FCharType, '+'):
 | |
| 			case LITERAL(FCharType, ' '): LiteralWidth += 1; break;
 | |
| 			default: if (bNegative)       LiteralWidth += 1;
 | |
| 			}
 | |
| 
 | |
| 			const size_t PaddingWidth = TargetField - Math::Min(LiteralWidth, TargetField);
 | |
| 
 | |
| 			if (!bZeroPadding || !bNormal)
 | |
| 			{
 | |
| 				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();
 | |
| 				}
 | |
| 			}
 | |
| 			else ZeroPadding = PaddingWidth;
 | |
| 		}
 | |
| 
 | |
| 		// 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.
 | |
| 		{
 | |
| 			static_assert(FChar::IsASCII() && FCharTraits::IsASCII());
 | |
| 
 | |
| 			if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 			// Handle the sign option.
 | |
| 			switch (SignOption)
 | |
| 			{
 | |
| 			case LITERAL(FCharType, '+'): *Iter++ = bNegative ? LITERAL(FCharType, '-') : LITERAL(FCharType, '+'); break;
 | |
| 			case LITERAL(FCharType, ' '): *Iter++ = bNegative ? LITERAL(FCharType, '-') : LITERAL(FCharType, ' '); break;
 | |
| 			default: if (bNegative)       *Iter++ =             LITERAL(FCharType, '-');
 | |
| 			}
 | |
| 
 | |
| 			// Handle the zero padding.
 | |
| 			for (size_t Index = 0; Index != ZeroPadding; ++Index)
 | |
| 			{
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				*Iter++ = LITERAL(FCharType, '0');
 | |
| 			}
 | |
| 
 | |
| 			// Write the target object.
 | |
| 			for (size_t Index = 0; Index != TargetWidth; ++Index)
 | |
| 			{
 | |
| 				if (Iter == Sent) UNLIKELY return Iter;
 | |
| 
 | |
| 				*Iter++ = static_cast<FCharType>(Target[Index]);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// 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, '>');
 | |
| 
 | |
| 	FCharType SignOption = LITERAL(FCharType, '-');
 | |
| 
 | |
| 	bool bAlternateForm = false;
 | |
| 	bool bZeroPadding   = false;
 | |
| 
 | |
| 	bool bHasPrecision = false;
 | |
| 
 | |
| 	size_t FieldWidth = 0;
 | |
| 	size_t Precision  = 0;
 | |
| 
 | |
| 	bool bDynamicWidth     = false;
 | |
| 	bool bDynamicPrecision = false;
 | |
| 
 | |
| 	bool bGeneral     = false;
 | |
| 	bool bFixedPoint  = false;
 | |
| 	bool bScientific  = false;
 | |
| 	bool bHexadecimal = false;
 | |
| 
 | |
| 	bool bLowercase = false;
 | |
| 	bool bUppercase = false;
 | |
| 
 | |
| };
 | |
| 
 | |
| 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
 | |
| 			{
 | |
| 				using FDecayU = TRemoveCVRef<U>;
 | |
| 
 | |
| 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>)
 | |
| 				{
 | |
| 					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
 | |
| 
 | |
| #pragma warning(pop)
 |