diff --git a/Sources/Plasma/CoreLib/plFormat.cpp b/Sources/Plasma/CoreLib/plFormat.cpp index 547fb657..2b2ae1e1 100644 --- a/Sources/Plasma/CoreLib/plFormat.cpp +++ b/Sources/Plasma/CoreLib/plFormat.cpp @@ -43,6 +43,10 @@ Mead, WA 99021 #include "plFormat.h" #include "HeadSpin.h" +#include +#include + +#define BADCHAR_REPLACEMENT (0xFFFDul) namespace plFormat_Private { @@ -114,8 +118,12 @@ namespace plFormat_Private case 'X': spec.fDigitClass = kDigitHexUpper; break; + case '+': + spec.fDigitClass = kDigitDecAlwaysSigned; + break; case 'd': - spec.fDigitClass = kDigitDec; + if (spec.fDigitClass != kDigitDecAlwaysSigned) + spec.fDigitClass = kDigitDec; break; case 'o': spec.fDigitClass = kDigitOct; @@ -179,9 +187,290 @@ namespace plFormat_Private } } -PL_FORMAT_IMPL(int) +template +void _IFormatNumeric_Impl(char *output_end, _IType value, int radix, bool upperCase = false) +{ + while (value) + { + int digit = (value % radix); + value /= radix; + --output_end; + + if (digit < 10) + *output_end = '0' + digit; + else if (upperCase) + *output_end = 'A' + digit - 10; + else + *output_end = 'a' + digit - 10; + } +} + +template +static plStringBuffer _formatNumeric(const plFormat_Private::FormatSpec &format, + _IType value, int radix, bool upperCase = false) +{ + char pad = format.fPadChar ? format.fPadChar : ' '; + + size_t max = 0; + _IType temp = value; + while (temp) { + ++max; + temp /= radix; + } + + plStringBuffer buffer; + if (format.fPrecisionLeft > max) { + char *output = buffer.CreateWritableBuffer(format.fPrecisionLeft); + memset(output, pad, format.fPrecisionLeft); + if (format.fAlignment == plFormat_Private::kAlignLeft) { + _IFormatNumeric_Impl<_IType, bits>(output + max, value, radix, upperCase); + } else { + _IFormatNumeric_Impl<_IType, bits>(output + format.fPrecisionLeft, + value, radix, upperCase); + } + output[format.fPrecisionLeft] = 0; + } else { + char *output = buffer.CreateWritableBuffer(max); + _IFormatNumeric_Impl<_IType, bits>(output + max, value, radix, upperCase); + output[max] = 0; + } + + return buffer; +} + +// Currently, only decimal formatting supports rendering negative numbers +template +static plStringBuffer _formatDecimal(const plFormat_Private::FormatSpec &format, _IType value) +{ + char pad = format.fPadChar ? format.fPadChar : ' '; + _IType abs = (value < 0) ? -value : value; + + size_t max = 0; + _IType temp = abs; + while (temp) { + ++max; + temp /= 10; + } + + if (value < 0 || format.fDigitClass == plFormat_Private::kDigitDecAlwaysSigned) + ++max; + + plStringBuffer buffer; + char *output; + if (format.fPrecisionLeft > max) { + output = buffer.CreateWritableBuffer(format.fPrecisionLeft); + memset(output, pad, format.fPrecisionLeft); + if (format.fAlignment == plFormat_Private::kAlignLeft) + _IFormatNumeric_Impl<_IType, bits>(output + max, abs, 10); + else + _IFormatNumeric_Impl<_IType, bits>(output + format.fPrecisionLeft, abs, 10); + output[format.fPrecisionLeft] = 0; + } else { + output = buffer.CreateWritableBuffer(max); + _IFormatNumeric_Impl<_IType, bits>(output + max, abs, 10); + output[max] = 0; + } + + int signPos = format.fPrecisionLeft - static_cast(max); + if (signPos < 0) + signPos = 0; + + if (value < 0) + output[signPos] = '-'; + else if (format.fDigitClass == plFormat_Private::kDigitDecAlwaysSigned) + output[signPos] = '+'; + + return buffer; +} + +static plStringBuffer _formatChar(const plFormat_Private::FormatSpec &format, int ch) +{ + hsAssert(format.fPrecisionLeft == 0 && format.fPadChar == 0, + "Char formatting does not currently support padding"); + + // Don't need to nul-terminate this, since plStringBuffer's constructor fixes it + char utf8[4]; + size_t max; + + // Yanked from plString + if (ch > 0x10FFFF) { + hsAssert(0, "Unicode character out of range"); + + // Character out of range; Use U+FFFD instead for release builds + max = 3; + utf8[0] = 0xE0 | ((BADCHAR_REPLACEMENT >> 12) & 0x0F); + utf8[1] = 0x80 | ((BADCHAR_REPLACEMENT >> 6) & 0x3F); + utf8[2] = 0x80 | ((BADCHAR_REPLACEMENT ) & 0x3F); + } else if (ch > 0xFFFF) { + max = 4; + utf8[0] = 0xF0 | ((ch >> 18) & 0x07); + utf8[1] = 0x80 | ((ch >> 12) & 0x3F); + utf8[2] = 0x80 | ((ch >> 6) & 0x3F); + utf8[3] = 0x80 | ((ch ) & 0x3F); + } else if (ch > 0x7FF) { + max = 3; + utf8[0] = 0xE0 | ((ch >> 12) & 0x0F); + utf8[1] = 0x80 | ((ch >> 6) & 0x3F); + utf8[2] = 0x80 | ((ch ) & 0x3F); + } else if (ch > 0x7F) { + max = 2; + utf8[0] = 0xC0 | ((ch >> 6) & 0x1F); + utf8[1] = 0x80 | ((ch ) & 0x3F); + } else { + max = 1; + utf8[0] = (char)ch; + } + + return plStringBuffer(utf8, max); +} + +#define _PL_FORMAT_IMPL_INT_TYPE(_stype, _utype, _bits) \ + PL_FORMAT_IMPL(_stype) \ + { \ + /* Note: The use of unsigned here is not a typo -- we only format decimal + values with a sign, so we can convert everything else to unsigned. */ \ + switch (format.fDigitClass) { \ + case plFormat_Private::kDigitBin: \ + return _formatNumeric<_utype, _bits>(format, value, 2); \ + case plFormat_Private::kDigitOct: \ + return _formatNumeric<_utype, _bits>(format, value, 8); \ + case plFormat_Private::kDigitHex: \ + return _formatNumeric<_utype, _bits>(format, value, 16, false); \ + case plFormat_Private::kDigitHexUpper: \ + return _formatNumeric<_utype, _bits>(format, value, 16, true); \ + case plFormat_Private::kDigitDec: \ + case plFormat_Private::kDigitDecAlwaysSigned: \ + case plFormat_Private::kDigitDefault: \ + return _formatDecimal<_stype, _bits>(format, value); \ + case plFormat_Private::kDigitChar: \ + return _formatChar(format, value); \ + } \ + \ + hsAssert(0, "Unexpected digit class"); \ + return plStringBuffer(); \ + } \ + \ + PL_FORMAT_IMPL(_utype) \ + { \ + switch (format.fDigitClass) { \ + case plFormat_Private::kDigitBin: \ + return _formatNumeric<_utype, _bits>(format, value, 2); \ + case plFormat_Private::kDigitOct: \ + return _formatNumeric<_utype, _bits>(format, value, 8); \ + case plFormat_Private::kDigitHex: \ + return _formatNumeric<_utype, _bits>(format, value, 16, false); \ + case plFormat_Private::kDigitHexUpper: \ + return _formatNumeric<_utype, _bits>(format, value, 16, true); \ + case plFormat_Private::kDigitDec: \ + case plFormat_Private::kDigitDecAlwaysSigned: \ + case plFormat_Private::kDigitDefault: \ + return _formatDecimal<_utype, _bits>(format, value); \ + case plFormat_Private::kDigitChar: \ + return _formatChar(format, value); \ + } \ + \ + hsAssert(0, "Unexpected digit class"); \ + return plStringBuffer(); \ + } + +_PL_FORMAT_IMPL_INT_TYPE(signed char, unsigned char, 8) +_PL_FORMAT_IMPL_INT_TYPE(short, unsigned short, 16) +_PL_FORMAT_IMPL_INT_TYPE(int, unsigned, sizeof(int) * 8) +_PL_FORMAT_IMPL_INT_TYPE(long, unsigned long, sizeof(long) * 8) +_PL_FORMAT_IMPL_INT_TYPE(int64_t, uint64_t, 64) + +PL_FORMAT_IMPL(char) +{ + /* Note: The use of unsigned here is not a typo -- we only format decimal + values with a sign, so we can convert everything else to unsigned. */ + switch (format.fDigitClass) { + case plFormat_Private::kDigitBin: + return _formatNumeric(format, value, 2); + case plFormat_Private::kDigitOct: + return _formatNumeric(format, value, 8); + case plFormat_Private::kDigitHex: + return _formatNumeric(format, value, 16, false); + case plFormat_Private::kDigitHexUpper: + return _formatNumeric(format, value, 16, true); + case plFormat_Private::kDigitDec: + case plFormat_Private::kDigitDecAlwaysSigned: + return _formatDecimal(format, value); + case plFormat_Private::kDigitChar: + case plFormat_Private::kDigitDefault: + return _formatChar(format, value); + } + + hsAssert(0, "Unexpected digit class"); + return plStringBuffer(); +} + +PL_FORMAT_IMPL(wchar_t) +{ + switch (format.fDigitClass) { + case plFormat_Private::kDigitBin: + return _formatNumeric(format, value, 2); + case plFormat_Private::kDigitOct: + return _formatNumeric(format, value, 8); + case plFormat_Private::kDigitHex: + return _formatNumeric(format, value, 16, false); + case plFormat_Private::kDigitHexUpper: + return _formatNumeric(format, value, 16, true); + case plFormat_Private::kDigitDec: + case plFormat_Private::kDigitDecAlwaysSigned: + return _formatDecimal(format, value); + case plFormat_Private::kDigitChar: + case plFormat_Private::kDigitDefault: + return _formatChar(format, value); + } + + hsAssert(0, "Unexpected digit class"); + return plStringBuffer(); +} + +static plStringBuffer _formatString(const plFormat_Private::FormatSpec &format, + const plStringBuffer &value) +{ + char pad = format.fPadChar ? format.fPadChar : ' '; + + if (format.fPrecisionLeft > value.GetSize()) { + plStringBuffer buf; + char *output = buf.CreateWritableBuffer(format.fPrecisionLeft); + memset(output, pad, format.fPrecisionLeft); + if (format.fAlignment == plFormat_Private::kAlignRight) { + memcpy(output + (format.fPrecisionLeft - value.GetSize()), + value.GetData(), value.GetSize()); + } else { + memcpy(output, value.GetData(), value.GetSize()); + } + + output[format.fPrecisionLeft] = 0; + return buf; + } + + return value; +} + +PL_FORMAT_IMPL(const char *) +{ + return _formatString(format, plString(value).ToUtf8()); +} + +PL_FORMAT_IMPL(const wchar_t *) +{ + return _formatString(format, plString::FromWchar(value).ToUtf8()); +} + +PL_FORMAT_IMPL(const plString &) +{ + return _formatString(format, value.ToUtf8()); +} + +PL_FORMAT_IMPL(const std::string &) +{ + return _formatString(format, plStringBuffer(value.c_str(), value.size())); +} + +PL_FORMAT_IMPL(const std::wstring &) { - char buffer[32]; - int size = snprintf(buffer, 32, "%d", value); - return plStringBuffer(buffer, size); + return _formatString(format, plString::FromWchar(value.c_str(), value.size()).ToUtf8()); } diff --git a/Sources/Plasma/CoreLib/plFormat.h b/Sources/Plasma/CoreLib/plFormat.h index be6768ad..dc70eced 100644 --- a/Sources/Plasma/CoreLib/plFormat.h +++ b/Sources/Plasma/CoreLib/plFormat.h @@ -59,6 +59,7 @@ Mead, WA 99021 * < - Align left * > - Align right * NNN - Pad to NNN characters (minimum - can be more) + * + - Show a '+' char for positive signed values (decimal only) * _C - Use C as the pad character (only '\001'..'\177' supported for now) * x - Hex (lower-case) * X - Hex (upper-case) @@ -83,8 +84,8 @@ namespace plFormat_Private enum DigitClass : unsigned char { - kDigitDefault, kDigitDec, kDigitHex, kDigitHexUpper, - kDigitOct, kDigitBin, kDigitChar + kDigitDefault, kDigitDec, kDigitDecAlwaysSigned, + kDigitHex, kDigitHexUpper, kDigitOct, kDigitBin, kDigitChar }; enum FloatClass : unsigned char @@ -94,9 +95,7 @@ namespace plFormat_Private struct FormatSpec { - const char *fEnd; - - int fPrecisionLeft = 0; + int fPrecisionLeft = 0; // Also used for padding int fPrecisionRight = 0; char fPadChar = 0; @@ -120,7 +119,7 @@ namespace plFormat_Private #define PL_FORMAT_TYPE(_type) \ extern plStringBuffer _impl_plFormat_DataHandler( \ - plFormat_Private::FormatSpec &format, _type value); \ + const plFormat_Private::FormatSpec &format, _type value); \ namespace plFormat_Private \ { \ template \ @@ -143,7 +142,7 @@ namespace plFormat_Private #define PL_FORMAT_IMPL(_type) \ plStringBuffer _impl_plFormat_DataHandler( \ - plFormat_Private::FormatSpec &format, _type value) + const plFormat_Private::FormatSpec &format, _type value) PL_FORMAT_TYPE(char) PL_FORMAT_TYPE(wchar_t) @@ -157,8 +156,6 @@ PL_FORMAT_TYPE(long) PL_FORMAT_TYPE(unsigned long) PL_FORMAT_TYPE(int64_t) PL_FORMAT_TYPE(uint64_t) -PL_FORMAT_TYPE(float) -PL_FORMAT_TYPE(double) PL_FORMAT_TYPE(const char *) PL_FORMAT_TYPE(const wchar_t *) PL_FORMAT_TYPE(const plString &) @@ -167,6 +164,9 @@ PL_FORMAT_TYPE(const plString &) PL_FORMAT_TYPE(const std::string &) PL_FORMAT_TYPE(const std::wstring &) +// TODO: Implement floating point types (float, double). They're harder +// than the others, so I'll get around to them later >.> + // End of the chain -- emits the last piece (if any) and builds the final string namespace plFormat_Private {