From b98b2e984286ac9ece9f63d460a6f138c33be84f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 10 Dec 2012 11:08:16 -0800 Subject: [PATCH] Implement formatting of integers. --- format.cc | 325 ++++++++++++++++++++++++++++++------------------- format.h | 38 +++--- format_test.cc | 132 ++++++++++++++------ 3 files changed, 310 insertions(+), 185 deletions(-) diff --git a/format.cc b/format.cc index c57926bd..d31db457 100644 --- a/format.cc +++ b/format.cc @@ -5,6 +5,8 @@ #include "format.h" +#include + #include #include #include @@ -14,16 +16,16 @@ using std::size_t; namespace { +// Flags. +enum { PLUS_FLAG = 1, ZERO_FLAG = 2, HEX_PREFIX_FLAG = 4 }; + // Throws Exception(message) if format contains '}', otherwise throws // FormatError reporting unmatched '{'. The idea is that unmatched '{' // should override other errors. -template -void Throw(const char *s, const char *message) { +void ReportError(const char *s, const std::string &message) { while (*s && *s != '}') ++s; - if (!*s) - throw fmt::FormatError("unmatched '{' in format"); - throw Exception(message); + throw fmt::FormatError(*s ? message : std::string("unmatched '{' in format")); } // Parses an unsigned integer advancing s to the end of the parsed input. @@ -34,29 +36,168 @@ unsigned ParseUInt(const char *&s) { do { unsigned new_value = value * 10 + (*s++ - '0'); if (new_value < value) // Check if value wrapped around. - Throw(s, "number is too big in format"); + ReportError(s, "number is too big in format"); value = new_value; } while ('0' <= *s && *s <= '9'); return value; } + +// Maps an integer type T to its unsigned counterpart. +template +struct GetUnsigned; + +template <> +struct GetUnsigned { + typedef unsigned Type; +}; + +template <> +struct GetUnsigned { + typedef unsigned Type; +}; + +template <> +struct GetUnsigned { + typedef unsigned long Type; +}; + +template <> +struct GetUnsigned { + typedef unsigned long Type; +}; + +template +struct IsLongDouble { enum {VALUE = 0}; }; + +template <> +struct IsLongDouble { enum {VALUE = 1}; }; } template -void fmt::Formatter::FormatBuiltinArg( - const char *format, const T &arg, int width, int precision) { +void fmt::Formatter::FormatInt(T value, unsigned flags, int width, char type) { + int size = 0; + char sign = 0; + typedef typename GetUnsigned::Type UnsignedType; + UnsignedType abs_value = value; + if (value < 0) { + sign = '-'; + ++size; + abs_value = -value; + } else if ((flags & PLUS_FLAG) != 0) { + sign = '+'; + ++size; + } + char fill = (flags & ZERO_FLAG) != 0 ? '0' : ' '; + size_t start = buffer_.size(); + char *p = 0; + switch (type) { + case 0: case 'd': { + UnsignedType n = abs_value; + do { + ++size; + } while ((n /= 10) != 0); + width = std::max(width, size); + buffer_.resize(buffer_.size() + width, fill); + p = &buffer_.back(); + n = abs_value; + do { + *p-- = '0' + (n % 10); + } while ((n /= 10) != 0); + break; + } + case 'x': case 'X': { + UnsignedType n = abs_value; + bool print_prefix = (flags & HEX_PREFIX_FLAG) != 0; + if (print_prefix) size += 2; + do { + ++size; + } while ((n >>= 4) != 0); + width = std::max(width, size); + buffer_.resize(buffer_.size() + width, fill); + p = &buffer_.back(); + n = abs_value; + const char *digits = type == 'x' ? "0123456789abcdef" : "0123456789ABCDEF"; + do { + *p-- = digits[n & 0xf]; + } while ((n >>= 4) != 0); + if (print_prefix) { + *p-- = type; + *p-- = '0'; + } + break; + } + case 'o': { + UnsignedType n = abs_value; + do { + ++size; + } while ((n >>= 3) != 0); + width = std::max(width, size); + buffer_.resize(buffer_.size() + width, fill); + p = &buffer_.back(); + n = abs_value; + do { + *p-- = '0' + (n & 7); + } while ((n >>= 3) != 0); + break; + } + default: + throw FormatError( + str(fmt::Format("unknown format code '{0}' for integer") << type)); + } + if (sign) { + if ((flags & ZERO_FLAG) != 0) + buffer_[start] = sign; + else + *p = sign; + } +} + +template +void fmt::Formatter::FormatDouble( + T value, unsigned flags, int width, int precision, char type) { + // Check type. + switch (type) { + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + break; + default: + // TODO: error + break; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 9}; // longest format: %+0*.*Lg + char format[MAX_FORMAT_SIZE]; + char *format_ptr = format; + *format_ptr++ = '%'; + if ((flags & PLUS_FLAG) != 0) + *format_ptr++ = '+'; + if ((flags & ZERO_FLAG) != 0) + *format_ptr++ = '0'; + if (width > 0) + *format_ptr++ = '*'; + if (precision >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (IsLongDouble::VALUE) + *format_ptr++ = 'L'; + *format_ptr++ = type ? type : 'g'; + *format_ptr = '\0'; + + // Format using snprintf. size_t offset = buffer_.size(); buffer_.resize(buffer_.capacity()); for (;;) { size_t size = buffer_.size() - offset; int n = 0; - if (width < 0) { + if (width <= 0) { n = precision < 0 ? - snprintf(&buffer_[offset], size, format, arg) : - snprintf(&buffer_[offset], size, format, precision, arg); + snprintf(&buffer_[offset], size, format, value) : + snprintf(&buffer_[offset], size, format, precision, value); } else { n = precision < 0 ? - snprintf(&buffer_[offset], size, format, width, arg) : - snprintf(&buffer_[offset], size, format, width, precision, arg); + snprintf(&buffer_[offset], size, format, width, value) : + snprintf(&buffer_[offset], size, format, width, precision, value); } if (n >= 0 && offset + n < buffer_.size()) { buffer_.resize(offset + n); @@ -77,92 +218,64 @@ void fmt::Formatter::Format() { // Parse argument index. if (*s < '0' || *s > '9') - Throw(s, "missing argument index in format string"); + ReportError(s, "missing argument index in format string"); unsigned arg_index = ParseUInt(s); if (arg_index >= args_.size()) - Throw(s, "argument index is out of range in format"); + ReportError(s, "argument index is out of range in format"); Arg &arg = args_[arg_index]; - enum { MAX_FORMAT_SIZE = 10}; // longest format: %+0-*.*ld - char arg_format[MAX_FORMAT_SIZE]; - char *arg_format_ptr = arg_format; - *arg_format_ptr++ = '%'; - - int width = -1; + unsigned flags = 0; + int width = 0; int precision = -1; char type = 0; - bool is_floating_point = false; if (*s == ':') { ++s; if (*s == '+') { - if (arg.type > LAST_NUMERIC_TYPE) { - Throw(s, - "format specifier '+' requires numeric argument"); - } + ++s; + if (arg.type > LAST_NUMERIC_TYPE) + ReportError(s, "format specifier '+' requires numeric argument"); if (arg.type == UINT || arg.type == ULONG) { - Throw(s, + ReportError(s, "format specifier '+' requires signed argument"); } - *arg_format_ptr++ = *s++; + flags |= PLUS_FLAG; } if (*s == '0') { - if (arg.type > LAST_NUMERIC_TYPE) { - Throw(s, - "format specifier '0' requires numeric argument"); - } - *arg_format_ptr++ = *s++; + ++s; + if (arg.type > LAST_NUMERIC_TYPE) + ReportError(s, "format specifier '0' requires numeric argument"); + flags |= ZERO_FLAG; } // Parse width. if ('0' <= *s && *s <= '9') { - if (arg.type > LAST_NUMERIC_TYPE && arg.type != POINTER) - *arg_format_ptr++ = '-'; - *arg_format_ptr++ = '*'; unsigned value = ParseUInt(s); if (value > INT_MAX) - Throw(s, "number is too big in format"); + ReportError(s, "number is too big in format"); width = value; } // Parse precision. if (*s == '.') { - *arg_format_ptr++ = '.'; - *arg_format_ptr++ = '*'; ++s; precision = 0; if ('0' <= *s && *s <= '9') { unsigned value = ParseUInt(s); if (value > INT_MAX) - Throw(s, "number is too big in format"); + ReportError(s, "number is too big in format"); precision = value; } else { - Throw(s, "missing precision in format"); + ReportError(s, "missing precision in format"); } - if (arg.type > LAST_NUMERIC_TYPE || - (*s == '}' && arg.type != DOUBLE && arg.type != LONG_DOUBLE)) { - Throw(s, - "precision specifier requires floating-point type"); + if (arg.type != DOUBLE && arg.type != LONG_DOUBLE) { + ReportError(s, + "precision specifier requires floating-point argument"); } } // Parse type. - if (*s != '}' && *s) { + if (*s != '}' && *s) type = *s++; - if (arg.type <= LAST_NUMERIC_TYPE) { - switch (type) { - case 'd': case 'o': case 'x': case 'X': - // TODO: check that argument is integer - break; - case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': - is_floating_point = true; - break; - default: - // TODO: error - break; - } - } - // TODO: check non-numeric types (string, character) - } } if (*s++ != '}') @@ -172,87 +285,45 @@ void fmt::Formatter::Format() { // Format argument. switch (arg.type) { case INT: - *arg_format_ptr++ = type ? type : 'd'; - *arg_format_ptr = '\0'; - if (is_floating_point) - FormatBuiltinArg(arg_format, arg.int_value, width, precision); - else - FormatBuiltinArg(arg_format, arg.int_value, width, precision); + FormatInt(arg.int_value, flags, width, type); break; case UINT: - *arg_format_ptr++ = type ? type : 'u'; - *arg_format_ptr = '\0'; - if (is_floating_point) - FormatBuiltinArg(arg_format, arg.uint_value, width, precision); - else - FormatBuiltinArg(arg_format, arg.uint_value, width, precision); + FormatInt(arg.uint_value, flags, width, type); break; case LONG: - *arg_format_ptr++ = 'l'; - *arg_format_ptr++ = type ? type : 'd'; - *arg_format_ptr = '\0'; - if (is_floating_point) - FormatBuiltinArg(arg_format, arg.long_value, width, precision); - else - FormatBuiltinArg(arg_format, arg.long_value, width, precision); + FormatInt(arg.long_value, flags, width, type); break; case ULONG: - *arg_format_ptr++ = 'l'; - *arg_format_ptr++ = type ? type : 'u'; - *arg_format_ptr = '\0'; - if (is_floating_point) - FormatBuiltinArg(arg_format, arg.ulong_value, width, precision); - else - FormatBuiltinArg(arg_format, arg.ulong_value, width, precision); + FormatInt(arg.ulong_value, flags, width, type); break; case DOUBLE: - *arg_format_ptr++ = type ? type : 'g'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.double_value, width, precision); + FormatDouble(arg.double_value, flags, width, precision, type); break; case LONG_DOUBLE: - *arg_format_ptr++ = 'L'; - *arg_format_ptr++ = type ? type : 'g'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.long_double_value, width, precision); - break; - case POINTER: - // TODO: don't allow any type specifiers other than 'p' - *arg_format_ptr++ = 'p'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.pointer_value, width, precision); + FormatDouble(arg.long_double_value, flags, width, precision, type); break; case CHAR: // TODO: check if type is 'c' or none - if (width <= 1) { - buffer_.push_back(arg.int_value); - break; - } - *arg_format_ptr++ = 'c'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.int_value, width, precision); + buffer_.reserve(std::max(width, 1)); + buffer_.push_back(arg.int_value); + if (width > 1) + buffer_.resize(buffer_.size() + width - 1, ' '); break; - case STRING: - // TODO: check if type is 's' or none - if (width == -1 || width <= arg.size) { - const char *str = arg.string_value; - size_t size = arg.size; - if (size == 0 && *str) - size = std::strlen(str); - buffer_.reserve(buffer_.size() + size + 1); - buffer_.insert(buffer_.end(), str, str + size); - break; - } - *arg_format_ptr++ = 's'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.string_value, width, precision); + case STRING: { + const char *str = arg.string_value; + size_t size = arg.size; + if (size == 0 && *str) + size = std::strlen(str); + buffer_.reserve(buffer_.size() + std::max(width, size)); + buffer_.insert(buffer_.end(), str, str + size); + if (width > size) + buffer_.resize(buffer_.size() + width - size, ' '); break; - case WSTRING: - // TODO: check if type is 's' or none - *arg_format_ptr++ = 'l'; - *arg_format_ptr++ = 's'; - *arg_format_ptr = '\0'; - FormatBuiltinArg(arg_format, arg.wstring_value, width, precision); + } + case POINTER: + // TODO: don't allow type specifiers other than 'p' + FormatInt(reinterpret_cast( + arg.pointer_value), HEX_PREFIX_FLAG, width, 'x'); break; case CUSTOM: // TODO: check if type is 's' or none diff --git a/format.h b/format.h index 899ee7b7..d085231a 100644 --- a/format.h +++ b/format.h @@ -53,7 +53,6 @@ class Formatter { struct { union { const char *string_value; - const wchar_t *wstring_value; const void *pointer_value; }; std::size_t size; @@ -74,7 +73,6 @@ class Formatter { explicit Arg(char value) : type(CHAR), int_value(value) {} explicit Arg(const char *value, std::size_t size = 0) : type(STRING), string_value(value), size(size) {} - explicit Arg(const wchar_t *value) : type(WSTRING), wstring_value(value) {} explicit Arg(const void *value) : type(POINTER), pointer_value(value) {} explicit Arg(const void *value, FormatFunc f) : type(CUSTOM), custom_value(value), format(f) {} @@ -92,10 +90,14 @@ class Formatter { args_.push_back(arg); } - // Formats an argument of a built-in type, such as "int" or "double". + // Formats an integer. template - void FormatBuiltinArg( - const char *format, const T &arg, int width, int precision); + void FormatInt(T value, unsigned flags, int width, char type); + + // Formats a floating point number. + template + void FormatDouble( + T value, unsigned flags, int width, int precision, char type); // Formats an argument of a custom type, such as a user-defined class. template @@ -199,11 +201,6 @@ class ArgFormatter { return *this; } - ArgFormatter &operator<<(const wchar_t *value) { - formatter_->Add(Formatter::Arg(value)); - return *this; - } - ArgFormatter &operator<<(const std::string &value) { formatter_->Add(Formatter::Arg(value.c_str(), value.size())); return *this; @@ -214,10 +211,16 @@ class ArgFormatter { return *this; } - // This method is not implemented intentionally to disallow output of + // This method contains a deliberate error to disallow formatting // arbitrary pointers. If you want to output a pointer cast it to void*. template - ArgFormatter &operator<<(const T *value); + ArgFormatter &operator<<(const T *value) { + "Formatting arbitrary pointers is not allowed" = value; + } + + // This method is not implemented intentionally to disallow formatting + // wide characters. + ArgFormatter &operator<<(wchar_t value); template ArgFormatter &operator<<(T *value) { @@ -253,13 +256,10 @@ void Formatter::FormatCustomArg(const void *arg, int width) { std::ostringstream os; os << value; std::string str(os.str()); - if (width < 0) { - // Extra char is reserved for terminating '\0'. - buffer_.reserve(buffer_.size() + str.size() + 1); - buffer_.insert(buffer_.end(), str.begin(), str.end()); - return; - } - FormatBuiltinArg("%-*s", str.c_str(), width, -1); + buffer_.reserve(buffer_.size() + std::max(width, str.size())); + buffer_.insert(buffer_.end(), str.begin(), str.end()); + if (width > str.size()) + buffer_.resize(buffer_.size() + width - str.size(), ' '); } inline ArgFormatter Formatter::operator()(const char *format) { diff --git a/format_test.cc b/format_test.cc index 1ca1ae86..22e47953 100644 --- a/format_test.cc +++ b/format_test.cc @@ -71,7 +71,7 @@ TEST(FormatterTest, NoArgs) { EXPECT_EQ("test", str(Format("test"))); } -TEST(FormatterTest, Args) { +TEST(FormatterTest, ArgsInDifferentPositions) { EXPECT_EQ("42", str(Format("{0}") << 42)); EXPECT_EQ("before 42", str(Format("before {0}") << 42)); EXPECT_EQ("42 after", str(Format("{0} after") << 42)); @@ -86,13 +86,13 @@ TEST(FormatterTest, ArgErrors) { EXPECT_THROW_MSG(Format("{}"), FormatError, "missing argument index in format string"); EXPECT_THROW_MSG(Format("{0"), FormatError, "unmatched '{' in format"); - EXPECT_THROW_MSG(Format("{0}"), std::out_of_range, + EXPECT_THROW_MSG(Format("{0}"), FormatError, "argument index is out of range in format"); char format[256]; std::sprintf(format, "{%u", UINT_MAX); EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format"); std::sprintf(format, "{%u}", UINT_MAX); - EXPECT_THROW_MSG(Format(format), std::out_of_range, + EXPECT_THROW_MSG(Format(format), FormatError, "argument index is out of range in format"); if (ULONG_MAX > UINT_MAX) { std::sprintf(format, "{%lu", UINT_MAX + 1l); @@ -130,8 +130,6 @@ TEST(FormatterTest, PlusFlag) { FormatError, "format specifier '+' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:+}") << "abc", FormatError, "format specifier '+' requires numeric argument"); - EXPECT_THROW_MSG(Format("{0:+}") << L"abc", - FormatError, "format specifier '+' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:+}") << reinterpret_cast(0x42), FormatError, "format specifier '+' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:+}") << TestString(), @@ -152,8 +150,6 @@ TEST(FormatterTest, ZeroFlag) { FormatError, "format specifier '0' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:05}") << "abc", FormatError, "format specifier '0' requires numeric argument"); - EXPECT_THROW_MSG(Format("{0:05}") << L"abc", - FormatError, "format specifier '0' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:05}") << reinterpret_cast(0x42), FormatError, "format specifier '0' requires numeric argument"); EXPECT_THROW_MSG(Format("{0:05}") << TestString(), @@ -189,9 +185,8 @@ TEST(FormatterTest, Width) { EXPECT_EQ(" 0xcafe", str(Format("{0:10}") << reinterpret_cast(0xcafe))); EXPECT_EQ("x ", str(Format("{0:11}") << 'x')); - EXPECT_EQ("narrow ", str(Format("{0:12}") << "narrow")); - EXPECT_EQ("wide ", str(Format("{0:13}") << L"wide")); - EXPECT_EQ("test ", str(Format("{0:14}") << TestString("test"))); + EXPECT_EQ("str ", str(Format("{0:12}") << "str")); + EXPECT_EQ("test ", str(Format("{0:13}") << TestString("test"))); } TEST(FormatterTest, Precision) { @@ -224,55 +219,97 @@ TEST(FormatterTest, Precision) { FormatError, "unmatched '{' in format"); EXPECT_THROW_MSG(Format("{0:.2}") << 42, - FormatError, "precision specifier requires floating-point type"); - EXPECT_EQ("42.00", str(Format("{0:.2f}") << 42)); + FormatError, "precision specifier requires floating-point argument"); + EXPECT_THROW_MSG(Format("{0:.2f}") << 42, + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << 42u, - FormatError, "precision specifier requires floating-point type"); - EXPECT_EQ("42.00", str(Format("{0:.2f}") << 42u)); + FormatError, "precision specifier requires floating-point argument"); + EXPECT_THROW_MSG(Format("{0:.2f}") << 42u, + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << 42l, - FormatError, "precision specifier requires floating-point type"); - EXPECT_EQ("42.00", str(Format("{0:.2f}") << 42l)); + FormatError, "precision specifier requires floating-point argument"); + EXPECT_THROW_MSG(Format("{0:.2f}") << 42l, + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << 42ul, - FormatError, "precision specifier requires floating-point type"); - EXPECT_EQ("42.00", str(Format("{0:.2f}") << 42ul)); + FormatError, "precision specifier requires floating-point argument"); + EXPECT_THROW_MSG(Format("{0:.2f}") << 42ul, + FormatError, "precision specifier requires floating-point argument"); EXPECT_EQ("1.2", str(Format("{0:.2}") << 1.2345)); EXPECT_EQ("1.2", str(Format("{0:.2}") << 1.2345l)); EXPECT_THROW_MSG(Format("{0:.2}") << reinterpret_cast(0xcafe), - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2f}") << reinterpret_cast(0xcafe), - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << 'x', - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2f}") << 'x', - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << "str", - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2f}") << "str", - FormatError, "precision specifier requires floating-point type"); - - EXPECT_THROW_MSG(Format("{0:.2}") << L"str", - FormatError, "precision specifier requires floating-point type"); - EXPECT_THROW_MSG(Format("{0:.2f}") << L"str", - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2}") << TestString(), - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); EXPECT_THROW_MSG(Format("{0:.2f}") << TestString(), - FormatError, "precision specifier requires floating-point type"); + FormatError, "precision specifier requires floating-point argument"); } -TEST(FormatterTest, FormatInt) { +TEST(FormatterTest, Type) { + EXPECT_THROW_MSG(Format("{0:v") << 42, + FormatError, "unmatched '{' in format"); + EXPECT_THROW_MSG(Format("{0:v}") << 42, + FormatError, "unknown format code 'v' for integer"); +} + +TEST(FormatterTest, FormatDec) { + EXPECT_EQ("0", str(Format("{0}") << 0)); EXPECT_EQ("42", str(Format("{0}") << 42)); - EXPECT_EQ("-1234", str(Format("{0}") << -1234)); - std::ostringstream os; - os << INT_MIN; - EXPECT_EQ(os.str(), str(Format("{0}") << INT_MIN)); - os.str(std::string()); - os << INT_MAX; - EXPECT_EQ(os.str(), str(Format("{0}") << INT_MAX)); + EXPECT_EQ("42", str(Format("{0:d}") << 42)); + EXPECT_EQ("42", str(Format("{0}") << 42u)); + EXPECT_EQ("-42", str(Format("{0}") << -42)); + EXPECT_EQ("12345", str(Format("{0}") << 12345)); + EXPECT_EQ("67890", str(Format("{0}") << 67890)); + char buffer[256]; + sprintf(buffer, "%d", INT_MIN); + EXPECT_EQ(buffer, str(Format("{0}") << INT_MIN)); + sprintf(buffer, "%d", INT_MAX); + EXPECT_EQ(buffer, str(Format("{0}") << INT_MAX)); + sprintf(buffer, "%u", UINT_MAX); + EXPECT_EQ(buffer, str(Format("{0}") << UINT_MAX)); + sprintf(buffer, "%ld", 0ul - LONG_MIN); + EXPECT_EQ(buffer, str(Format("{0}") << LONG_MIN)); + sprintf(buffer, "%ld", LONG_MAX); + EXPECT_EQ(buffer, str(Format("{0}") << LONG_MAX)); + sprintf(buffer, "%lu", ULONG_MAX); + EXPECT_EQ(buffer, str(Format("{0}") << ULONG_MAX)); +} + +TEST(FormatterTest, FormatHex) { + EXPECT_EQ("0", str(Format("{0:x}") << 0)); + EXPECT_EQ("42", str(Format("{0:x}") << 0x42)); + EXPECT_EQ("42", str(Format("{0:x}") << 0x42u)); + EXPECT_EQ("-42", str(Format("{0:x}") << -0x42)); + EXPECT_EQ("12345678", str(Format("{0:x}") << 0x12345678)); + EXPECT_EQ("90abcdef", str(Format("{0:x}") << 0x90abcdef)); + EXPECT_EQ("12345678", str(Format("{0:X}") << 0x12345678)); + EXPECT_EQ("90ABCDEF", str(Format("{0:X}") << 0x90ABCDEF)); + char buffer[256]; + sprintf(buffer, "-%x", 0u - INT_MIN); + EXPECT_EQ(buffer, str(Format("{0:x}") << INT_MIN)); + sprintf(buffer, "%x", INT_MAX); + EXPECT_EQ(buffer, str(Format("{0:x}") << INT_MAX)); + sprintf(buffer, "%x", UINT_MAX); + EXPECT_EQ(buffer, str(Format("{0:x}") << UINT_MAX)); + sprintf(buffer, "-%lx", 0ul - LONG_MIN); + EXPECT_EQ(buffer, str(Format("{0:x}") << LONG_MIN)); + sprintf(buffer, "%lx", LONG_MAX); + EXPECT_EQ(buffer, str(Format("{0:x}") << LONG_MAX)); + sprintf(buffer, "%lx", ULONG_MAX); + EXPECT_EQ(buffer, str(Format("{0:x}") << ULONG_MAX)); } TEST(FormatterTest, FormatChar) { @@ -283,8 +320,25 @@ TEST(FormatterTest, FormatString) { EXPECT_EQ("test", str(Format("{0}") << std::string("test"))); } +TEST(FormatterTest, FormatPointer) { + EXPECT_EQ("0x0", str(Format("{0}") << reinterpret_cast(0))); +} + +class Date { + int year_, month_, day_; + public: + Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + friend std::ostream &operator<<(std::ostream &os, const Date &d) { + os << d.year_ << '-' << d.month_ << '-' << d.day_; + return os; + } +}; + TEST(FormatterTest, FormatCustomArg) { EXPECT_EQ("a string", str(Format("{0}") << TestString("a string"))); + std::string s = str(fmt::Format("The date is {0}") << Date(2012, 12, 9)); + EXPECT_EQ("The date is 2012-12-9", s); } TEST(FormatterTest, FormatStringFromSpeedTest) {