From 280ea8b5c5fb711baed38d24df1253345c8a46bc Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 9 Dec 2012 09:03:47 -0800 Subject: [PATCH] Add support for user-defined types. --- format.cc | 34 +++++++++++++++++-------------- format.h | 55 ++++++++++++++++++++++++++++++++++++-------------- format_test.cc | 55 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/format.cc b/format.cc index 0495137e..a3374ef5 100644 --- a/format.cc +++ b/format.cc @@ -56,7 +56,7 @@ void CheckFlags(unsigned flags) { } template -void fmt::Formatter::FormatArg( +void fmt::Formatter::FormatBuiltinArg( const char *format, const T &arg, int width, int precision) { size_t offset = buffer_.size(); buffer_.resize(buffer_.capacity()); @@ -95,7 +95,8 @@ void fmt::Formatter::Format() { if (arg_index >= args_.size()) Throw(s, "argument index is out of range in format"); - char arg_format[8]; // longest format: %+0*.*ld + enum { MAX_FORMAT_SIZE = 9}; // longest format: %+0*.*ld + char arg_format[MAX_FORMAT_SIZE]; char *arg_format_ptr = arg_format; *arg_format_ptr++ = '%'; @@ -158,68 +159,71 @@ void fmt::Formatter::Format() { } *arg_format_ptr++ = 'c'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.int_value, width, precision); + FormatBuiltinArg(arg_format, arg.int_value, width, precision); break; case INT: *arg_format_ptr++ = 'd'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.int_value, width, precision); + FormatBuiltinArg(arg_format, arg.int_value, width, precision); break; case UINT: *arg_format_ptr++ = 'd'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.uint_value, width, precision); + FormatBuiltinArg(arg_format, arg.uint_value, width, precision); break; case LONG: *arg_format_ptr++ = 'l'; *arg_format_ptr++ = 'd'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.long_value, width, precision); + FormatBuiltinArg(arg_format, arg.long_value, width, precision); break; case ULONG: *arg_format_ptr++ = 'l'; *arg_format_ptr++ = 'd'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.ulong_value, width, precision); + FormatBuiltinArg(arg_format, arg.ulong_value, width, precision); break; case DOUBLE: *arg_format_ptr++ = type ? type : 'g'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.double_value, width, precision); + FormatBuiltinArg(arg_format, arg.double_value, width, precision); break; case LONG_DOUBLE: *arg_format_ptr++ = 'L'; *arg_format_ptr++ = 'g'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.long_double_value, width, precision); + FormatBuiltinArg(arg_format, arg.long_double_value, width, precision); break; case STRING: CheckFlags(flags); if (width == -1 && precision == -1) { const char *str = arg.string_value; - buffer_.insert(buffer_.end(), str, str + std::strlen(str)); + std::size_t size = arg.size; + if (size == 0 && *str) + size = std::strlen(str); + buffer_.insert(buffer_.end(), str, str + size); break; } *arg_format_ptr++ = 's'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.string_value, width, precision); + FormatBuiltinArg(arg_format, arg.string_value, width, precision); break; case WSTRING: CheckFlags(flags); *arg_format_ptr++ = 'l'; *arg_format_ptr++ = 's'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.wstring_value, width, precision); + FormatBuiltinArg(arg_format, arg.wstring_value, width, precision); break; case POINTER: CheckFlags(flags); *arg_format_ptr++ = 'p'; *arg_format_ptr = '\0'; - FormatArg(arg_format, arg.pointer_value, width, precision); + FormatBuiltinArg(arg_format, arg.pointer_value, width, precision); break; - case OTHER: + case CUSTOM: CheckFlags(flags); - (this->*arg.format)(arg.other_value); + (this->*arg.format)(arg.custom_value); break; default: assert(false); diff --git a/format.h b/format.h index a2680152..0f9cfcb4 100644 --- a/format.h +++ b/format.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace format { @@ -32,9 +33,10 @@ class Formatter { enum Type { CHAR, INT, UINT, LONG, ULONG, DOUBLE, LONG_DOUBLE, - STRING, WSTRING, POINTER, OTHER + STRING, WSTRING, POINTER, CUSTOM }; + // An argument. struct Arg { Type type; union { @@ -44,11 +46,16 @@ class Formatter { long long_value; unsigned long ulong_value; long double long_double_value; - const char *string_value; - const wchar_t *wstring_value; - const void *pointer_value; struct { - const void *other_value; + union { + const char *string_value; + const wchar_t *wstring_value; + const void *pointer_value; + }; + std::size_t size; + }; + struct { + const void *custom_value; void (Formatter::*format)(const void *value); }; }; @@ -61,11 +68,12 @@ class Formatter { explicit Arg(double value) : type(DOUBLE), double_value(value) {} explicit Arg(long double value) : type(LONG_DOUBLE), long_double_value(value) {} - explicit Arg(const char *value) : type(STRING), string_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, void (Formatter::*format)(const void *)) - : type(OTHER), format(format) {} + : type(CUSTOM), custom_value(value), format(format) {} }; std::vector args_; @@ -80,14 +88,14 @@ class Formatter { args_.push_back(arg); } + // Formats an argument of a built-in type, such as "int" or "double". template - void FormatArg(const char *format, const T &arg, int width, int precision); + void FormatBuiltinArg( + const char *format, const T &arg, int width, int precision); + // Formats an argument of a custom type, such as a user-defined class. template - void FormatOtherArg(const void *value) { - const T &typed_value = *static_cast(value); - // TODO: format value - } + void FormatCustomArg(const void *arg); void Format(); @@ -100,7 +108,8 @@ class Formatter { ArgFormatterWithCallback FormatWithCallback(const char *format); const char *c_str() const { return &buffer_[0]; } - std::size_t size() const { return buffer_.size() - 1; } + const char *data() const { return &buffer_[0]; } + std::size_t size() const { return buffer_.size(); } void Swap(Formatter &f) { buffer_.swap(f.buffer_); @@ -191,6 +200,11 @@ class ArgFormatter { return *this; } + ArgFormatter &operator<<(const std::string &value) { + formatter_->Add(Formatter::Arg(value.c_str(), value.size())); + return *this; + } + ArgFormatter &operator<<(const void *value) { formatter_->Add(Formatter::Arg(value)); return *this; @@ -212,7 +226,7 @@ class ArgFormatter { // and not this method is called both for "const void*" and "void*". template ArgFormatter &operator<<(const T &value) { - formatter_->Add(Formatter::Arg(&value, &Formatter::FormatOtherArg)); + formatter_->Add(Formatter::Arg(&value, &Formatter::FormatCustomArg)); return *this; } }; @@ -229,6 +243,17 @@ class ArgFormatterWithCallback : public ArgFormatter { } }; +template +void Formatter::FormatCustomArg(const void *arg) { + const T &value = *static_cast(arg); + std::ostringstream os; + os << value; + std::string str(os.str()); + // Extra char is reserved for terminating '\0'. + buffer_.reserve(buffer_.size() + str.size() + 1); + buffer_.insert(buffer_.end(), str.begin(), str.end()); +} + inline ArgFormatter Formatter::operator()(const char *format) { format_ = format; return ArgFormatter(*this); @@ -279,7 +304,7 @@ class Print : public ArgFormatter { ~Print() { FinishFormatting(); - std::fwrite(formatter_.c_str(), 1, formatter_.size(), stdout); + std::fwrite(formatter_.data(), 1, formatter_.size(), stdout); } }; } diff --git a/format_test.cc b/format_test.cc index 9e5300d5..e8c57528 100644 --- a/format_test.cc +++ b/format_test.cc @@ -53,6 +53,19 @@ using fmt::FormatError; FORMAT_TEST_THROW_(statement, expected_exception, expected_message, \ GTEST_NONFATAL_FAILURE_) +class TestString { + private: + std::string value_; + + public: + explicit TestString(const char *value = "") : value_(value) {} + + friend std::ostream &operator<<(std::ostream &os, const TestString &s) { + os << s.value_; + return os; + } +}; + TEST(FormatterTest, FormatNoArgs) { EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad")); EXPECT_EQ("test", str(Format("test"))); @@ -96,8 +109,6 @@ TEST(FormatterTest, FormatArgErrors) { } } -struct UDT {}; - TEST(FormatterTest, FormatPlusFlag) { EXPECT_EQ("+42", str(Format("{0:+}") << 42)); EXPECT_EQ("-42", str(Format("{0:+}") << -42)); @@ -117,7 +128,7 @@ TEST(FormatterTest, FormatPlusFlag) { FormatError, "format specifier '+' used with non-numeric type"); EXPECT_THROW_MSG(Format("{0:+}") << static_cast("abc"), FormatError, "format specifier '+' used with non-numeric type"); - EXPECT_THROW_MSG(Format("{0:+}") << UDT(), + EXPECT_THROW_MSG(Format("{0:+}") << TestString(), FormatError, "format specifier '+' used with non-numeric type"); } @@ -139,20 +150,42 @@ TEST(FormatterTest, FormatZeroFlag) { FormatError, "format specifier '0' used with non-numeric type"); EXPECT_THROW_MSG(Format("{0:05}") << static_cast("abc"), FormatError, "format specifier '0' used with non-numeric type"); - EXPECT_THROW_MSG(Format("{0:05}") << UDT(), + EXPECT_THROW_MSG(Format("{0:05}") << TestString(), FormatError, "format specifier '0' used with non-numeric type"); } -TEST(FormatterTest, FormatBig) { +TEST(FormatterTest, FormatWidth) { + // TODO +} + +TEST(FormatterTest, FormatChar) { + EXPECT_EQ("a*b", str(Format("{0}{1}{2}") << 'a' << '*' << 'b')); +} + +TEST(FormatterTest, FormatInt) { + 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)); +} + +TEST(FormatterTest, FormatString) { + EXPECT_EQ("test", str(Format("{0}") << std::string("test"))); +} + +TEST(FormatterTest, FormatCustomArg) { + EXPECT_EQ("a string", str(Format("{0}") << TestString("a string"))); +} + +TEST(FormatterTest, FormatStringFromSpeedTest) { EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%", str(Format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%") << 1.234 << 42 << 3.13 << "str" << reinterpret_cast(1000) << 'X')); } -TEST(FormatterTest, FormatInt) { - EXPECT_EQ("42", str(Format("{0}") << 42)); - EXPECT_EQ("before 42 after", str(Format("before {0} after") << 42)); -} - -// TODO +// TODO: more tests