mirror of
https://github.com/fmtlib/fmt.git
synced 2025-04-16 05:42:19 +00:00
Add support for user-defined types.
This commit is contained in:
parent
8b1c7098ec
commit
280ea8b5c5
34
format.cc
34
format.cc
@ -56,7 +56,7 @@ void CheckFlags(unsigned flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void fmt::Formatter::FormatArg(
|
void fmt::Formatter::FormatBuiltinArg(
|
||||||
const char *format, const T &arg, int width, int precision) {
|
const char *format, const T &arg, int width, int precision) {
|
||||||
size_t offset = buffer_.size();
|
size_t offset = buffer_.size();
|
||||||
buffer_.resize(buffer_.capacity());
|
buffer_.resize(buffer_.capacity());
|
||||||
@ -95,7 +95,8 @@ void fmt::Formatter::Format() {
|
|||||||
if (arg_index >= args_.size())
|
if (arg_index >= args_.size())
|
||||||
Throw<std::out_of_range>(s, "argument index is out of range in format");
|
Throw<std::out_of_range>(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;
|
char *arg_format_ptr = arg_format;
|
||||||
*arg_format_ptr++ = '%';
|
*arg_format_ptr++ = '%';
|
||||||
|
|
||||||
@ -158,68 +159,71 @@ void fmt::Formatter::Format() {
|
|||||||
}
|
}
|
||||||
*arg_format_ptr++ = 'c';
|
*arg_format_ptr++ = 'c';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.int_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.int_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case INT:
|
case INT:
|
||||||
*arg_format_ptr++ = 'd';
|
*arg_format_ptr++ = 'd';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.int_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.int_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case UINT:
|
case UINT:
|
||||||
*arg_format_ptr++ = 'd';
|
*arg_format_ptr++ = 'd';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.uint_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.uint_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case LONG:
|
case LONG:
|
||||||
*arg_format_ptr++ = 'l';
|
*arg_format_ptr++ = 'l';
|
||||||
*arg_format_ptr++ = 'd';
|
*arg_format_ptr++ = 'd';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.long_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.long_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case ULONG:
|
case ULONG:
|
||||||
*arg_format_ptr++ = 'l';
|
*arg_format_ptr++ = 'l';
|
||||||
*arg_format_ptr++ = 'd';
|
*arg_format_ptr++ = 'd';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.ulong_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.ulong_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case DOUBLE:
|
case DOUBLE:
|
||||||
*arg_format_ptr++ = type ? type : 'g';
|
*arg_format_ptr++ = type ? type : 'g';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.double_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.double_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case LONG_DOUBLE:
|
case LONG_DOUBLE:
|
||||||
*arg_format_ptr++ = 'L';
|
*arg_format_ptr++ = 'L';
|
||||||
*arg_format_ptr++ = 'g';
|
*arg_format_ptr++ = 'g';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.long_double_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.long_double_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case STRING:
|
||||||
CheckFlags(flags);
|
CheckFlags(flags);
|
||||||
if (width == -1 && precision == -1) {
|
if (width == -1 && precision == -1) {
|
||||||
const char *str = arg.string_value;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
*arg_format_ptr++ = 's';
|
*arg_format_ptr++ = 's';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.string_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.string_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case WSTRING:
|
case WSTRING:
|
||||||
CheckFlags(flags);
|
CheckFlags(flags);
|
||||||
*arg_format_ptr++ = 'l';
|
*arg_format_ptr++ = 'l';
|
||||||
*arg_format_ptr++ = 's';
|
*arg_format_ptr++ = 's';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.wstring_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.wstring_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case POINTER:
|
case POINTER:
|
||||||
CheckFlags(flags);
|
CheckFlags(flags);
|
||||||
*arg_format_ptr++ = 'p';
|
*arg_format_ptr++ = 'p';
|
||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.pointer_value, width, precision);
|
FormatBuiltinArg(arg_format, arg.pointer_value, width, precision);
|
||||||
break;
|
break;
|
||||||
case OTHER:
|
case CUSTOM:
|
||||||
CheckFlags(flags);
|
CheckFlags(flags);
|
||||||
(this->*arg.format)(arg.other_value);
|
(this->*arg.format)(arg.custom_value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
|
55
format.h
55
format.h
@ -10,6 +10,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace format {
|
namespace format {
|
||||||
@ -32,9 +33,10 @@ class Formatter {
|
|||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
CHAR, INT, UINT, LONG, ULONG, DOUBLE, LONG_DOUBLE,
|
CHAR, INT, UINT, LONG, ULONG, DOUBLE, LONG_DOUBLE,
|
||||||
STRING, WSTRING, POINTER, OTHER
|
STRING, WSTRING, POINTER, CUSTOM
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// An argument.
|
||||||
struct Arg {
|
struct Arg {
|
||||||
Type type;
|
Type type;
|
||||||
union {
|
union {
|
||||||
@ -44,11 +46,16 @@ class Formatter {
|
|||||||
long long_value;
|
long long_value;
|
||||||
unsigned long ulong_value;
|
unsigned long ulong_value;
|
||||||
long double long_double_value;
|
long double long_double_value;
|
||||||
const char *string_value;
|
|
||||||
const wchar_t *wstring_value;
|
|
||||||
const void *pointer_value;
|
|
||||||
struct {
|
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);
|
void (Formatter::*format)(const void *value);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -61,11 +68,12 @@ class Formatter {
|
|||||||
explicit Arg(double value) : type(DOUBLE), double_value(value) {}
|
explicit Arg(double value) : type(DOUBLE), double_value(value) {}
|
||||||
explicit Arg(long double value)
|
explicit Arg(long double value)
|
||||||
: type(LONG_DOUBLE), long_double_value(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 wchar_t *value) : type(WSTRING), wstring_value(value) {}
|
||||||
explicit Arg(const void *value) : type(POINTER), pointer_value(value) {}
|
explicit Arg(const void *value) : type(POINTER), pointer_value(value) {}
|
||||||
explicit Arg(const void *value, void (Formatter::*format)(const void *))
|
explicit Arg(const void *value, void (Formatter::*format)(const void *))
|
||||||
: type(OTHER), format(format) {}
|
: type(CUSTOM), custom_value(value), format(format) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Arg> args_;
|
std::vector<Arg> args_;
|
||||||
@ -80,14 +88,14 @@ class Formatter {
|
|||||||
args_.push_back(arg);
|
args_.push_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Formats an argument of a built-in type, such as "int" or "double".
|
||||||
template <typename T>
|
template <typename T>
|
||||||
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 <typename T>
|
template <typename T>
|
||||||
void FormatOtherArg(const void *value) {
|
void FormatCustomArg(const void *arg);
|
||||||
const T &typed_value = *static_cast<const T*>(value);
|
|
||||||
// TODO: format value
|
|
||||||
}
|
|
||||||
|
|
||||||
void Format();
|
void Format();
|
||||||
|
|
||||||
@ -100,7 +108,8 @@ class Formatter {
|
|||||||
ArgFormatterWithCallback<Callback> FormatWithCallback(const char *format);
|
ArgFormatterWithCallback<Callback> FormatWithCallback(const char *format);
|
||||||
|
|
||||||
const char *c_str() const { return &buffer_[0]; }
|
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) {
|
void Swap(Formatter &f) {
|
||||||
buffer_.swap(f.buffer_);
|
buffer_.swap(f.buffer_);
|
||||||
@ -191,6 +200,11 @@ class ArgFormatter {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArgFormatter &operator<<(const std::string &value) {
|
||||||
|
formatter_->Add(Formatter::Arg(value.c_str(), value.size()));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
ArgFormatter &operator<<(const void *value) {
|
ArgFormatter &operator<<(const void *value) {
|
||||||
formatter_->Add(Formatter::Arg(value));
|
formatter_->Add(Formatter::Arg(value));
|
||||||
return *this;
|
return *this;
|
||||||
@ -212,7 +226,7 @@ class ArgFormatter {
|
|||||||
// and not this method is called both for "const void*" and "void*".
|
// and not this method is called both for "const void*" and "void*".
|
||||||
template <typename T>
|
template <typename T>
|
||||||
ArgFormatter &operator<<(const T &value) {
|
ArgFormatter &operator<<(const T &value) {
|
||||||
formatter_->Add(Formatter::Arg(&value, &Formatter::FormatOtherArg<T>));
|
formatter_->Add(Formatter::Arg(&value, &Formatter::FormatCustomArg<T>));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -229,6 +243,17 @@ class ArgFormatterWithCallback : public ArgFormatter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Formatter::FormatCustomArg(const void *arg) {
|
||||||
|
const T &value = *static_cast<const T*>(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) {
|
inline ArgFormatter Formatter::operator()(const char *format) {
|
||||||
format_ = format;
|
format_ = format;
|
||||||
return ArgFormatter(*this);
|
return ArgFormatter(*this);
|
||||||
@ -279,7 +304,7 @@ class Print : public ArgFormatter {
|
|||||||
|
|
||||||
~Print() {
|
~Print() {
|
||||||
FinishFormatting();
|
FinishFormatting();
|
||||||
std::fwrite(formatter_.c_str(), 1, formatter_.size(), stdout);
|
std::fwrite(formatter_.data(), 1, formatter_.size(), stdout);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,19 @@ using fmt::FormatError;
|
|||||||
FORMAT_TEST_THROW_(statement, expected_exception, expected_message, \
|
FORMAT_TEST_THROW_(statement, expected_exception, expected_message, \
|
||||||
GTEST_NONFATAL_FAILURE_)
|
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) {
|
TEST(FormatterTest, FormatNoArgs) {
|
||||||
EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad"));
|
EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad"));
|
||||||
EXPECT_EQ("test", str(Format("test")));
|
EXPECT_EQ("test", str(Format("test")));
|
||||||
@ -96,8 +109,6 @@ TEST(FormatterTest, FormatArgErrors) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UDT {};
|
|
||||||
|
|
||||||
TEST(FormatterTest, FormatPlusFlag) {
|
TEST(FormatterTest, FormatPlusFlag) {
|
||||||
EXPECT_EQ("+42", str(Format("{0:+}") << 42));
|
EXPECT_EQ("+42", str(Format("{0:+}") << 42));
|
||||||
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");
|
FormatError, "format specifier '+' used with non-numeric type");
|
||||||
EXPECT_THROW_MSG(Format("{0:+}") << static_cast<const void*>("abc"),
|
EXPECT_THROW_MSG(Format("{0:+}") << static_cast<const void*>("abc"),
|
||||||
FormatError, "format specifier '+' used with non-numeric type");
|
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");
|
FormatError, "format specifier '+' used with non-numeric type");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,20 +150,42 @@ TEST(FormatterTest, FormatZeroFlag) {
|
|||||||
FormatError, "format specifier '0' used with non-numeric type");
|
FormatError, "format specifier '0' used with non-numeric type");
|
||||||
EXPECT_THROW_MSG(Format("{0:05}") << static_cast<const void*>("abc"),
|
EXPECT_THROW_MSG(Format("{0:05}") << static_cast<const void*>("abc"),
|
||||||
FormatError, "format specifier '0' used with non-numeric type");
|
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");
|
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:%",
|
EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%",
|
||||||
str(Format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%")
|
str(Format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%")
|
||||||
<< 1.234 << 42 << 3.13 << "str"
|
<< 1.234 << 42 << 3.13 << "str"
|
||||||
<< reinterpret_cast<void*>(1000) << 'X'));
|
<< reinterpret_cast<void*>(1000) << 'X'));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FormatterTest, FormatInt) {
|
// TODO: more tests
|
||||||
EXPECT_EQ("42", str(Format("{0}") << 42));
|
|
||||||
EXPECT_EQ("before 42 after", str(Format("before {0} after") << 42));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user