diff --git a/format.cc b/format.cc index b6dc7415..0495137e 100644 --- a/format.cc +++ b/format.cc @@ -12,11 +12,13 @@ using std::size_t; -// Throws Exception(message) if s contains '}' and FormatError reporting -// unmatched '{' otherwise. The idea is that unmatched '{' should override -// other errors. +namespace { + +// Throws Exception(message) if format contains '}', otherwise throws +// FormatError reporting unmatched '{'. The idea is that unmatched '{' +// should override other errors. template -static void Throw(const char *s, const char *message) { +void Throw(const char *s, const char *message) { while (*s && *s != '}') ++s; if (!*s) @@ -24,6 +26,35 @@ static void Throw(const char *s, const char *message) { throw Exception(message); } +// Parses an unsigned integer advancing s to the end of the parsed input. +// This function assumes that the first character of s is a digit. +unsigned ParseUInt(const char *&s) { + assert('0' <= *s && *s <= '9'); + unsigned value = 0; + 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"); + value = new_value; + } while ('0' <= *s && *s <= '9'); + return value; +} + +// Flags. +enum { + PLUS_FLAG = 1, + ZERO_FLAG = 2 +}; + +void CheckFlags(unsigned flags) { + if (flags == 0) return; + if ((flags & PLUS_FLAG) != 0) + throw fmt::FormatError("format specifier '+' used with non-numeric type"); + if ((flags & ZERO_FLAG) != 0) + throw fmt::FormatError("format specifier '0' used with non-numeric type"); +} +} + template void fmt::Formatter::FormatArg( const char *format, const T &arg, int width, int precision) { @@ -58,17 +89,9 @@ void fmt::Formatter::Format() { buffer_.insert(buffer_.end(), start, s - 1); // Parse argument index. - unsigned arg_index = 0; - if ('0' <= *s && *s <= '9') { - do { - unsigned index = arg_index * 10 + (*s++ - '0'); - if (index < arg_index) // Check if index wrapped around. - Throw(s, "argument index is too big"); // TODO: test - arg_index = index; - } while ('0' <= *s && *s <= '9'); - } else { - Throw(s, "missing argument index in format string"); - } + if (*s < '0' || *s > '9') + Throw(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"); @@ -76,24 +99,27 @@ void fmt::Formatter::Format() { char *arg_format_ptr = arg_format; *arg_format_ptr++ = '%'; - char type = 0; + unsigned flags = 0; int width = -1; int precision = -1; + char type = 0; if (*s == ':') { ++s; - if (*s == '+') + if (*s == '+') { + flags |= PLUS_FLAG; *arg_format_ptr++ = *s++; - if (*s == '0') + } + if (*s == '0') { + flags |= ZERO_FLAG; *arg_format_ptr++ = *s++; + } // Parse width. if ('0' <= *s && *s <= '9') { *arg_format_ptr++ = '*'; - width = 0; - do { - // TODO: check overflow - width = width * 10 + (*s++ - '0'); - } while ('0' <= *s && *s <= '9'); + unsigned number = ParseUInt(s); + if (number > INT_MAX) ; // TODO: error + width = number; } // Parse precision. @@ -118,13 +144,14 @@ void fmt::Formatter::Format() { } if (*s++ != '}') - throw FormatError("single '{' encountered in format string"); + throw FormatError("unmatched '{' in format"); start = s; // Format argument. Arg &arg = args_[arg_index]; switch (arg.type) { case CHAR: + CheckFlags(flags); if (width == -1 && precision == -1) { buffer_.push_back(arg.int_value); break; @@ -161,12 +188,13 @@ void fmt::Formatter::Format() { FormatArg(arg_format, arg.double_value, width, precision); break; case LONG_DOUBLE: - *arg_format_ptr++ = 'l'; + *arg_format_ptr++ = 'L'; *arg_format_ptr++ = 'g'; *arg_format_ptr = '\0'; FormatArg(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)); @@ -177,17 +205,20 @@ void fmt::Formatter::Format() { FormatArg(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); break; case POINTER: + CheckFlags(flags); *arg_format_ptr++ = 'p'; *arg_format_ptr = '\0'; FormatArg(arg_format, arg.pointer_value, width, precision); break; case OTHER: + CheckFlags(flags); (this->*arg.format)(arg.other_value); break; default: diff --git a/format.h b/format.h index 7e78a28b..a2680152 100644 --- a/format.h +++ b/format.h @@ -108,13 +108,6 @@ class Formatter { } }; -template -struct AddPtrConst { typedef T Value; }; - -// Convert "T*" into "const T*". -template -struct AddPtrConst { typedef const T* Value; }; - class ArgFormatter { private: friend class Formatter; @@ -193,6 +186,11 @@ class ArgFormatter { return *this; } + ArgFormatter &operator<<(const wchar_t *value) { + formatter_->Add(Formatter::Arg(value)); + return *this; + } + ArgFormatter &operator<<(const void *value) { formatter_->Add(Formatter::Arg(value)); return *this; @@ -203,11 +201,17 @@ class ArgFormatter { template ArgFormatter &operator<<(const T *value); + template + ArgFormatter &operator<<(T *value) { + const T *const_value = value; + return *this << const_value; + } + // If T is a pointer type, say "U*", AddPtrConst::Value will be // "const U*". This additional const ensures that operator<<(const void *) // and not this method is called both for "const void*" and "void*". template - ArgFormatter &operator<<(const typename AddPtrConst::Value &value) { + ArgFormatter &operator<<(const T &value) { formatter_->Add(Formatter::Arg(&value, &Formatter::FormatOtherArg)); return *this; } diff --git a/format_test.cc b/format_test.cc index 5dfc641b..9e5300d5 100644 --- a/format_test.cc +++ b/format_test.cc @@ -68,8 +68,7 @@ TEST(FormatterTest, FormatArgs) { EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad")); } -TEST(FormatterTest, FormatErrors) { - //Format("{"); +TEST(FormatterTest, FormatArgErrors) { EXPECT_THROW_MSG(Format("{"), FormatError, "unmatched '{' in format"); EXPECT_THROW_MSG(Format("{}"), FormatError, "missing argument index in format string"); @@ -86,14 +85,62 @@ TEST(FormatterTest, FormatErrors) { std::sprintf(format, "{%lu", UINT_MAX + 1l); EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format"); std::sprintf(format, "{%lu}", UINT_MAX + 1l); - EXPECT_THROW_MSG(Format(format), FormatError, "argument index is too big"); + EXPECT_THROW_MSG(Format(format), + FormatError, "number is too big in format"); } else { std::sprintf(format, "{%u0", UINT_MAX); EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format"); std::sprintf(format, "{%u0}", UINT_MAX); - EXPECT_THROW_MSG(Format(format), FormatError, "argument index is too big"); + EXPECT_THROW_MSG(Format(format), + FormatError, "number is too big in format"); } - // TODO +} + +struct UDT {}; + +TEST(FormatterTest, FormatPlusFlag) { + EXPECT_EQ("+42", str(Format("{0:+}") << 42)); + EXPECT_EQ("-42", str(Format("{0:+}") << -42)); + EXPECT_THROW_MSG(Format("{0:+") << 'c', + FormatError, "unmatched '{' in format"); + EXPECT_THROW_MSG(Format("{0:+}") << 'c', + FormatError, "format specifier '+' used with non-numeric type"); + EXPECT_EQ("+42", str(Format("{0:+}") << 42)); + EXPECT_EQ("+42", str(Format("{0:+}") << 42u)); + EXPECT_EQ("+42", str(Format("{0:+}") << 42l)); + EXPECT_EQ("+42", str(Format("{0:+}") << 42ul)); + EXPECT_EQ("+42", str(Format("{0:+}") << 42.0)); + EXPECT_EQ("+42", str(Format("{0:+}") << 42.0l)); + EXPECT_THROW_MSG(Format("{0:+}") << "abc", + FormatError, "format specifier '+' used with non-numeric type"); + EXPECT_THROW_MSG(Format("{0:+}") << L"abc", + 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(), + FormatError, "format specifier '+' used with non-numeric type"); +} + +TEST(FormatterTest, FormatZeroFlag) { + EXPECT_EQ("42", str(Format("{0:0}") << 42)); + EXPECT_THROW_MSG(Format("{0:0") << 'c', + FormatError, "unmatched '{' in format"); + EXPECT_THROW_MSG(Format("{0:05}") << 'c', + FormatError, "format specifier '0' used with non-numeric type"); + EXPECT_EQ("-0042", str(Format("{0:05}") << -42)); + EXPECT_EQ("00042", str(Format("{0:05}") << 42u)); + EXPECT_EQ("-0042", str(Format("{0:05}") << -42l)); + EXPECT_EQ("00042", str(Format("{0:05}") << 42ul)); + EXPECT_EQ("-0042", str(Format("{0:05}") << -42.0)); + EXPECT_EQ("-0042", str(Format("{0:05}") << -42.0l)); + EXPECT_THROW_MSG(Format("{0:05}") << "abc", + FormatError, "format specifier '0' used with non-numeric type"); + EXPECT_THROW_MSG(Format("{0:05}") << L"abc", + 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(), + FormatError, "format specifier '0' used with non-numeric type"); } TEST(FormatterTest, FormatBig) {