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