Add initial support for printf format specifications.

This commit is contained in:
Victor Zverovich 2014-06-06 06:38:37 -07:00
parent 7042d14341
commit bf8b29fbe7
8 changed files with 958 additions and 371 deletions

View File

@ -91,19 +91,24 @@ enable_testing()
include_directories(.) include_directories(.)
set(TEST_MAIN_SRC test/test-main.cc test/gtest-extra.cc test/gtest-extra.h) set(TEST_MAIN_SRC
test/test-main.cc test/gtest-extra.cc test/gtest-extra.h test/util.cc)
add_library(test-main ${TEST_MAIN_SRC}) add_library(test-main ${TEST_MAIN_SRC})
target_link_libraries(test-main gtest format) target_link_libraries(test-main gtest format)
cxx_test(gtest-extra-test test-main) cxx_test(gtest-extra-test test-main)
cxx_test(format-test test-main) cxx_test(format-test test-main)
if (CMAKE_COMPILER_IS_GNUCXX) cxx_test(printf-test test-main)
set_target_properties(format-test PROPERTIES COMPILE_FLAGS foreach (target format-test printf-test)
"-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros") if (CMAKE_COMPILER_IS_GNUCXX)
endif () set_target_properties(${target} PROPERTIES COMPILE_FLAGS
if (CPP11_FLAG) "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros")
set_target_properties(format-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) endif ()
endif () if (CPP11_FLAG)
set_target_properties(${target} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
endif ()
endforeach ()
cxx_test(util-test test-main)
if (HAVE_OPEN) if (HAVE_OPEN)
add_executable(posix-test test/posix-test.cc ${TEST_MAIN_SRC}) add_executable(posix-test test/posix-test.cc ${TEST_MAIN_SRC})

504
format.cc
View File

@ -289,6 +289,44 @@ void fmt::internal::FormatWinErrorMessage(
} }
#endif #endif
template <typename Char>
void fmt::internal::FormatErrorReporter<Char>::operator()(
const Char *s, fmt::StringRef message) const {
for (int n = num_open_braces; *s; ++s) {
if (*s == '{') {
++n;
} else if (*s == '}') {
if (--n == 0)
throw fmt::FormatError(message);
}
}
throw fmt::FormatError("unmatched '{' in format");
}
// 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.
template <typename Char>
int fmt::internal::ParseNonnegativeInt(
const Char *&s, const char *&error) FMT_NOEXCEPT(true) {
assert('0' <= *s && *s <= '9');
unsigned value = 0;
do {
unsigned new_value = value * 10 + (*s++ - '0');
// Check if value wrapped around.
value = new_value >= value ? new_value : UINT_MAX;
} while ('0' <= *s && *s <= '9');
if (value > INT_MAX) {
if (!error)
error = "number is too big in format";
return 0;
}
return value;
}
template <typename Char>
const typename fmt::BasicWriter<Char>::ArgInfo
fmt::BasicWriter<Char>::DUMMY_ARG = {fmt::BasicWriter<Char>::INT, 0};
// Fills the padding around the content and returns the pointer to the // Fills the padding around the content and returns the pointer to the
// content area. // content area.
template <typename Char> template <typename Char>
@ -494,60 +532,31 @@ void fmt::BasicWriter<Char>::FormatDouble(
} }
} }
// Throws Exception(message) if format contains '}', otherwise throws
// FormatError reporting unmatched '{'. The idea is that unmatched '{'
// should override other errors.
template <typename Char>
void fmt::BasicWriter<Char>::FormatParser::ReportError(
const Char *s, StringRef message) const {
for (int num_open_braces = num_open_braces_; *s; ++s) {
if (*s == '{') {
++num_open_braces;
} else if (*s == '}') {
if (--num_open_braces == 0)
throw fmt::FormatError(message);
}
}
throw fmt::FormatError("unmatched '{' in format");
}
// 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.
template <typename Char>
unsigned fmt::BasicWriter<Char>::FormatParser::ParseUInt(const Char *&s) const {
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.
ReportError(s, "number is too big in format");
value = new_value;
} while ('0' <= *s && *s <= '9');
return value;
}
template <typename Char> template <typename Char>
inline const typename fmt::BasicWriter<Char>::ArgInfo inline const typename fmt::BasicWriter<Char>::ArgInfo
&fmt::BasicWriter<Char>::FormatParser::ParseArgIndex(const Char *&s) { &fmt::BasicWriter<Char>::FormatParser::ParseArgIndex(const Char *&s) {
unsigned arg_index = 0; unsigned arg_index = 0;
if (*s < '0' || *s > '9') { if (*s < '0' || *s > '9') {
if (*s != '}' && *s != ':') if (*s != '}' && *s != ':')
ReportError(s, "invalid argument index in format string"); report_error_(s, "invalid argument index in format string");
if (next_arg_index_ < 0) { if (next_arg_index_ < 0) {
ReportError(s, report_error_(s,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
} }
arg_index = next_arg_index_++; arg_index = next_arg_index_++;
} else { } else {
if (next_arg_index_ > 0) { if (next_arg_index_ > 0) {
ReportError(s, report_error_(s,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
} }
next_arg_index_ = -1; next_arg_index_ = -1;
arg_index = ParseUInt(s); const char *error = 0;
arg_index = internal::ParseNonnegativeInt(s, error);
if (error)
report_error_(s, error); // TODO
} }
if (arg_index >= num_args_) if (arg_index >= num_args_)
ReportError(s, "argument index is out of range in format"); report_error_(s, "argument index is out of range in format");
return args_[arg_index]; return args_[arg_index];
} }
@ -556,18 +565,18 @@ void fmt::BasicWriter<Char>::FormatParser::CheckSign(
const Char *&s, const ArgInfo &arg) { const Char *&s, const ArgInfo &arg) {
char sign = static_cast<char>(*s); char sign = static_cast<char>(*s);
if (arg.type > LAST_NUMERIC_TYPE) { if (arg.type > LAST_NUMERIC_TYPE) {
ReportError(s, report_error_(s,
fmt::Format("format specifier '{}' requires numeric argument") << sign); fmt::Format("format specifier '{}' requires numeric argument") << sign);
} }
if (arg.type == UINT || arg.type == ULONG || arg.type == ULONG_LONG) { if (arg.type == UINT || arg.type == ULONG || arg.type == ULONG_LONG) {
ReportError(s, report_error_(s,
fmt::Format("format specifier '{}' requires signed argument") << sign); fmt::Format("format specifier '{}' requires signed argument") << sign);
} }
++s; ++s;
} }
template <typename Char> template <typename Char>
void fmt::BasicWriter<Char>::FormatParser::Format( void fmt::BasicWriter<Char>::PrintfParser::Format(
BasicWriter<Char> &writer, BasicStringRef<Char> format, BasicWriter<Char> &writer, BasicStringRef<Char> format,
std::size_t num_args, const ArgInfo *args) { std::size_t num_args, const ArgInfo *args) {
const Char *start = format.c_str(); const Char *start = format.c_str();
@ -577,24 +586,86 @@ void fmt::BasicWriter<Char>::FormatParser::Format(
const Char *s = start; const Char *s = start;
while (*s) { while (*s) {
Char c = *s++; Char c = *s++;
if (c != '{' && c != '}') continue; if (c != '%') continue;
if (*s == c) { if (*s == c) {
writer.buffer_.append(start, s); writer.buffer_.append(start, s);
start = ++s; start = ++s;
continue; continue;
} }
if (c == '}')
throw FormatError("unmatched '}' in format");
num_open_braces_= 1;
writer.buffer_.append(start, s - 1); writer.buffer_.append(start, s - 1);
const ArgInfo &arg = ParseArgIndex(s);
FormatSpec spec; FormatSpec spec;
int precision = -1; spec.align_ = ALIGN_RIGHT;
if (*s == ':') {
++s;
// Reporting errors is delayed till the format specification is
// completely parsed. This is done to avoid potentially confusing
// error messages for incomplete format strings. For example, in
// sprintf("%2$", 42);
// the format specification is incomplete. In naive approach we
// would parse 2 as an argument index and report an error that the
// index is out of range which would be rather confusing if the
// use meant "%2d$" rather than "%2$d". If we delay an error, the
// user will get an error that the format string is invalid which
// is OK for both cases.
const char *error = 0;
unsigned arg_index = 0;
bool have_width = false, have_arg_index = false;
if (*s >= '0' && *s <= '9') {
unsigned value = internal::ParseNonnegativeInt(s, error);
if (*s != '$') {
// TODO: handle '0'
have_width = true;
spec.width_ = value;
} else {
++s;
if (next_arg_index_ <= 0) {
have_arg_index = true;
next_arg_index_ = -1;
arg_index = value - 1;
} else if (!error) {
error = "cannot switch from automatic to manual argument indexing";
}
}
}
if (!have_arg_index) {
if (next_arg_index_ >= 0) {
arg_index = next_arg_index_++;
} else {
// We don't check if error has already been set because argument
// index is semantically the first part of the format string, so
// indexing errors are reported first even though parsing width
// above can cause another error.
error = "cannot switch from manual to automatic argument indexing";
}
}
const ArgInfo *arg = 0;
if (arg_index < num_args) {
arg = &args_[arg_index];
} else {
arg = &DUMMY_ARG;
if (!error)
error = "argument index is out of range in format";
}
int precision = -1;
switch (have_width) {
case false:
// TODO: parse optional flags
switch (*s) {
case '-':
++s;
spec.align_ = ALIGN_LEFT;
break;
case '+':
case ' ':
case '#':
//++s;
break;
}
/*
// Parse fill and alignment. // Parse fill and alignment.
if (Char c = *s) { if (Char c = *s) {
const Char *p = s + 1; const Char *p = s + 1;
@ -649,22 +720,272 @@ void fmt::BasicWriter<Char>::FormatParser::Format(
ReportError(s, "format specifier '#' requires numeric argument"); ReportError(s, "format specifier '#' requires numeric argument");
spec.flags_ |= HASH_FLAG; spec.flags_ |= HASH_FLAG;
++s; ++s;
}*/
// Parse width and zero flag.
if (*s < '0' || *s > '9')
break;
if (*s == '0') {
spec.align_ = ALIGN_NUMERIC;
spec.fill_ = '0';
}
// Zero may be parsed again as a part of the width, but it is simpler
// and more efficient than checking if the next char is a digit.
spec.width_ = internal::ParseNonnegativeInt(s, error);
// Fall through.
default:
if (spec.fill_ == '0' && arg->type > LAST_NUMERIC_TYPE)
throw FormatError("format specifier '0' requires numeric argument");
break;
}
// Parse precision.
/*if (*s == '.') {
++s;
precision = 0;
if ('0' <= *s && *s <= '9') {
unsigned value = ParseNonnegativeInt(s);
if (value > INT_MAX)
ReportError(s, "number is too big in format");
precision = value;
} else if (*s == '{') {
++s;
++num_open_braces_;
const ArgInfo &precision_arg = ParseArgIndex(s);
ULongLong value = 0;
switch (precision_arg.type) {
case INT:
if (precision_arg.int_value < 0)
ReportError(s, "negative precision in format");
value = precision_arg.int_value;
break;
case UINT:
value = precision_arg.uint_value;
break;
case LONG:
if (precision_arg.long_value < 0)
ReportError(s, "negative precision in format");
value = precision_arg.long_value;
break;
case ULONG:
value = precision_arg.ulong_value;
break;
case LONG_LONG:
if (precision_arg.long_long_value < 0)
ReportError(s, "negative precision in format");
value = precision_arg.long_long_value;
break;
case ULONG_LONG:
value = precision_arg.ulong_long_value;
break;
default:
ReportError(s, "precision is not integer");
}
if (value > INT_MAX)
ReportError(s, "number is too big in format");
precision = static_cast<int>(value);
if (*s++ != '}')
throw FormatError("unmatched '{' in format");
--num_open_braces_;
} else {
ReportError(s, "missing precision in format");
}
if (arg.type != DOUBLE && arg.type != LONG_DOUBLE) {
ReportError(s,
"precision specifier requires floating-point argument");
}
}*/
// Parse type.
if (!*s)
throw FormatError("invalid format string");
if (error)
throw FormatError(error);
spec.type_ = static_cast<char>(*s++);
start = s;
// Format argument.
switch (arg->type) {
case INT:
writer.FormatInt(arg->int_value, spec);
break;
case UINT:
writer.FormatInt(arg->uint_value, spec);
break;
case LONG:
writer.FormatInt(arg->long_value, spec);
break;
case ULONG:
writer.FormatInt(arg->ulong_value, spec);
break;
case LONG_LONG:
writer.FormatInt(arg->long_long_value, spec);
break;
case ULONG_LONG:
writer.FormatInt(arg->ulong_long_value, spec);
break;
case DOUBLE:
writer.FormatDouble(arg->double_value, spec, precision);
break;
case LONG_DOUBLE:
writer.FormatDouble(arg->long_double_value, spec, precision);
break;
case CHAR: {
if (spec.type_ && spec.type_ != 'c')
internal::ReportUnknownType(spec.type_, "char");
typedef typename BasicWriter<Char>::CharPtr CharPtr;
CharPtr out = CharPtr();
if (spec.width_ > 1) {
Char fill = static_cast<Char>(spec.fill());
out = writer.GrowBuffer(spec.width_);
if (spec.align_ == ALIGN_RIGHT) {
std::fill_n(out, spec.width_ - 1, fill);
out += spec.width_ - 1;
} else if (spec.align_ == ALIGN_CENTER) {
out = writer.FillPadding(out, spec.width_, 1, fill);
} else {
std::fill_n(out + 1, spec.width_ - 1, fill);
}
} else {
out = writer.GrowBuffer(1);
}
*out = static_cast<Char>(arg->int_value);
break;
}
case STRING: {
if (spec.type_ && spec.type_ != 's')
internal::ReportUnknownType(spec.type_, "string");
const Char *str = arg->string.value;
std::size_t size = arg->string.size;
if (size == 0) {
if (!str)
throw FormatError("string pointer is null");
if (*str)
size = std::char_traits<Char>::length(str);
}
writer.FormatString(str, size, spec);
break;
}
case POINTER:
if (spec.type_ && spec.type_ != 'p')
internal::ReportUnknownType(spec.type_, "pointer");
spec.flags_= HASH_FLAG;
spec.type_ = 'x';
writer.FormatInt(reinterpret_cast<uintptr_t>(arg->pointer_value), spec);
break;
case CUSTOM:
if (spec.type_)
internal::ReportUnknownType(spec.type_, "object");
arg->custom.format(writer, arg->custom.value, spec);
break;
default:
assert(false);
break;
}
}
writer.buffer_.append(start, s);
}
template <typename Char>
void fmt::BasicWriter<Char>::FormatParser::Format(
BasicWriter<Char> &writer, BasicStringRef<Char> format,
std::size_t num_args, const ArgInfo *args) {
const char *error = 0;
const Char *start = format.c_str();
num_args_ = num_args;
args_ = args;
next_arg_index_ = 0;
const Char *s = start;
while (*s) {
Char c = *s++;
if (c != '{' && c != '}') continue;
if (*s == c) {
writer.buffer_.append(start, s);
start = ++s;
continue;
}
if (c == '}')
throw FormatError("unmatched '}' in format");
report_error_.num_open_braces = 1;
writer.buffer_.append(start, s - 1);
const ArgInfo &arg = ParseArgIndex(s);
FormatSpec spec;
int precision = -1;
if (*s == ':') {
++s;
// Parse fill and alignment.
if (Char c = *s) {
const Char *p = s + 1;
spec.align_ = ALIGN_DEFAULT;
do {
switch (*p) {
case '<':
spec.align_ = ALIGN_LEFT;
break;
case '>':
spec.align_ = ALIGN_RIGHT;
break;
case '=':
spec.align_ = ALIGN_NUMERIC;
break;
case '^':
spec.align_ = ALIGN_CENTER;
break;
}
if (spec.align_ != ALIGN_DEFAULT) {
if (p != s) {
if (c == '}') break;
if (c == '{')
report_error_(s, "invalid fill character '{'");
s += 2;
spec.fill_ = c;
} else ++s;
if (spec.align_ == ALIGN_NUMERIC && arg.type > LAST_NUMERIC_TYPE)
report_error_(s, "format specifier '=' requires numeric argument");
break;
}
} while (--p >= s);
}
// Parse sign.
switch (*s) {
case '+':
CheckSign(s, arg);
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
break;
case '-':
CheckSign(s, arg);
break;
case ' ':
CheckSign(s, arg);
spec.flags_ |= SIGN_FLAG;
break;
}
if (*s == '#') {
if (arg.type > LAST_NUMERIC_TYPE)
report_error_(s, "format specifier '#' requires numeric argument");
spec.flags_ |= HASH_FLAG;
++s;
} }
// Parse width and zero flag. // Parse width and zero flag.
if ('0' <= *s && *s <= '9') { if ('0' <= *s && *s <= '9') {
if (*s == '0') { if (*s == '0') {
if (arg.type > LAST_NUMERIC_TYPE) if (arg.type > LAST_NUMERIC_TYPE)
ReportError(s, "format specifier '0' requires numeric argument"); report_error_(s, "format specifier '0' requires numeric argument");
spec.align_ = ALIGN_NUMERIC; spec.align_ = ALIGN_NUMERIC;
spec.fill_ = '0'; spec.fill_ = '0';
} }
// Zero may be parsed again as a part of the width, but it is simpler // Zero may be parsed again as a part of the width, but it is simpler
// and more efficient than checking if the next char is a digit. // and more efficient than checking if the next char is a digit.
unsigned value = ParseUInt(s); spec.width_ = internal::ParseNonnegativeInt(s, error);
if (value > INT_MAX) if (error)
ReportError(s, "number is too big in format"); report_error_(s, error);
spec.width_ = value;
} }
// Parse precision. // Parse precision.
@ -672,19 +993,18 @@ void fmt::BasicWriter<Char>::FormatParser::Format(
++s; ++s;
precision = 0; precision = 0;
if ('0' <= *s && *s <= '9') { if ('0' <= *s && *s <= '9') {
unsigned value = ParseUInt(s); precision = internal::ParseNonnegativeInt(s, error);
if (value > INT_MAX) if (error)
ReportError(s, "number is too big in format"); report_error_(s, error);
precision = value;
} else if (*s == '{') { } else if (*s == '{') {
++s; ++s;
++num_open_braces_; ++report_error_.num_open_braces;
const ArgInfo &precision_arg = ParseArgIndex(s); const ArgInfo &precision_arg = ParseArgIndex(s);
ULongLong value = 0; ULongLong value = 0;
switch (precision_arg.type) { switch (precision_arg.type) {
case INT: case INT:
if (precision_arg.int_value < 0) if (precision_arg.int_value < 0)
ReportError(s, "negative precision in format"); report_error_(s, "negative precision in format");
value = precision_arg.int_value; value = precision_arg.int_value;
break; break;
case UINT: case UINT:
@ -692,7 +1012,7 @@ void fmt::BasicWriter<Char>::FormatParser::Format(
break; break;
case LONG: case LONG:
if (precision_arg.long_value < 0) if (precision_arg.long_value < 0)
ReportError(s, "negative precision in format"); report_error_(s, "negative precision in format");
value = precision_arg.long_value; value = precision_arg.long_value;
break; break;
case ULONG: case ULONG:
@ -700,26 +1020,26 @@ void fmt::BasicWriter<Char>::FormatParser::Format(
break; break;
case LONG_LONG: case LONG_LONG:
if (precision_arg.long_long_value < 0) if (precision_arg.long_long_value < 0)
ReportError(s, "negative precision in format"); report_error_(s, "negative precision in format");
value = precision_arg.long_long_value; value = precision_arg.long_long_value;
break; break;
case ULONG_LONG: case ULONG_LONG:
value = precision_arg.ulong_long_value; value = precision_arg.ulong_long_value;
break; break;
default: default:
ReportError(s, "precision is not integer"); report_error_(s, "precision is not integer");
} }
if (value > INT_MAX) if (value > INT_MAX)
ReportError(s, "number is too big in format"); report_error_(s, "number is too big in format");
precision = static_cast<int>(value); precision = static_cast<int>(value);
if (*s++ != '}') if (*s++ != '}')
throw FormatError("unmatched '{' in format"); throw FormatError("unmatched '{' in format");
--num_open_braces_; --report_error_.num_open_braces;
} else { } else {
ReportError(s, "missing precision in format"); report_error_(s, "missing precision in format");
} }
if (arg.type != DOUBLE && arg.type != LONG_DOUBLE) { if (arg.type != DOUBLE && arg.type != LONG_DOUBLE) {
ReportError(s, report_error_(s,
"precision specifier requires floating-point argument"); "precision specifier requires floating-point argument");
} }
} }
@ -852,12 +1172,6 @@ void fmt::ANSITerminalSink::operator()(
// Explicit instantiations for char. // Explicit instantiations for char.
template void fmt::BasicWriter<char>::FormatDouble<double>(
double value, const FormatSpec &spec, int precision);
template void fmt::BasicWriter<char>::FormatDouble<long double>(
long double value, const FormatSpec &spec, int precision);
template fmt::BasicWriter<char>::CharPtr template fmt::BasicWriter<char>::CharPtr
fmt::BasicWriter<char>::FillPadding(CharPtr buffer, fmt::BasicWriter<char>::FillPadding(CharPtr buffer,
unsigned total_size, std::size_t content_size, wchar_t fill); unsigned total_size, std::size_t content_size, wchar_t fill);
@ -866,53 +1180,31 @@ template fmt::BasicWriter<char>::CharPtr
fmt::BasicWriter<char>::PrepareFilledBuffer( fmt::BasicWriter<char>::PrepareFilledBuffer(
unsigned size, const AlignSpec &spec, char sign); unsigned size, const AlignSpec &spec, char sign);
template void fmt::BasicWriter<char>::FormatParser::ReportError(
const char *s, StringRef message) const;
template unsigned fmt::BasicWriter<char>::FormatParser::ParseUInt(
const char *&s) const;
template const fmt::BasicWriter<char>::ArgInfo
&fmt::BasicWriter<char>::FormatParser::ParseArgIndex(const char *&s);
template void fmt::BasicWriter<char>::FormatParser::CheckSign(
const char *&s, const ArgInfo &arg);
template void fmt::BasicWriter<char>::FormatParser::Format( template void fmt::BasicWriter<char>::FormatParser::Format(
BasicWriter<char> &writer, BasicStringRef<char> format, BasicWriter<char> &writer, BasicStringRef<char> format,
std::size_t num_args, const ArgInfo *args); std::size_t num_args, const ArgInfo *args);
template void fmt::BasicWriter<char>::PrintfParser::Format(
BasicWriter<char> &writer, BasicStringRef<char> format,
std::size_t num_args, const ArgInfo *args);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template void fmt::BasicWriter<wchar_t>::FormatDouble<double>(
double value, const FormatSpec &spec, int precision);
template void fmt::BasicWriter<wchar_t>::FormatDouble<long double>(
long double value, const FormatSpec &spec, int precision);
template fmt::BasicWriter<wchar_t>::CharPtr template fmt::BasicWriter<wchar_t>::CharPtr
fmt::BasicWriter<wchar_t>::FillPadding(CharPtr buffer, fmt::BasicWriter<wchar_t>::FillPadding(CharPtr buffer,
unsigned total_size, std::size_t content_size, wchar_t fill); unsigned total_size, std::size_t content_size, wchar_t fill);
template fmt::BasicWriter<wchar_t>::CharPtr template fmt::BasicWriter<wchar_t>::CharPtr
fmt::BasicWriter<wchar_t>::PrepareFilledBuffer( fmt::BasicWriter<wchar_t>::PrepareFilledBuffer(
unsigned size, const AlignSpec &spec, char sign); unsigned size, const AlignSpec &spec, char sign);
template void fmt::BasicWriter<wchar_t>::FormatParser::ReportError(
const wchar_t *s, StringRef message) const;
template unsigned fmt::BasicWriter<wchar_t>::FormatParser::ParseUInt(
const wchar_t *&s) const;
template const fmt::BasicWriter<wchar_t>::ArgInfo
&fmt::BasicWriter<wchar_t>::FormatParser::ParseArgIndex(const wchar_t *&s);
template void fmt::BasicWriter<wchar_t>::FormatParser::CheckSign(
const wchar_t *&s, const ArgInfo &arg);
template void fmt::BasicWriter<wchar_t>::FormatParser::Format( template void fmt::BasicWriter<wchar_t>::FormatParser::Format(
BasicWriter<wchar_t> &writer, BasicStringRef<wchar_t> format, BasicWriter<wchar_t> &writer, BasicStringRef<wchar_t> format,
std::size_t num_args, const ArgInfo *args); std::size_t num_args, const ArgInfo *args);
template void fmt::BasicWriter<wchar_t>::PrintfParser::Format(
BasicWriter<wchar_t> &writer, BasicStringRef<wchar_t> format,
std::size_t num_args, const ArgInfo *args);
#if _MSC_VER #if _MSC_VER
# pragma warning(pop) # pragma warning(pop)

View File

@ -186,6 +186,15 @@ class BasicStringRef {
typedef BasicStringRef<char> StringRef; typedef BasicStringRef<char> StringRef;
typedef BasicStringRef<wchar_t> WStringRef; typedef BasicStringRef<wchar_t> WStringRef;
/**
A formatting error such as invalid format string.
*/
class FormatError : public std::runtime_error {
public:
explicit FormatError(const std::string &message)
: std::runtime_error(message) {}
};
namespace internal { namespace internal {
// The number of characters to store in the Array object, representing the // The number of characters to store in the Array object, representing the
@ -509,17 +518,29 @@ void FormatWinErrorMessage(
fmt::Writer &out, int error_code, fmt::StringRef message); fmt::Writer &out, int error_code, fmt::StringRef message);
#endif #endif
} // namespace internal struct SimpleErrorReporter {
void operator()(const void *, fmt::StringRef message) const {
/** throw fmt::FormatError(message);
A formatting error such as invalid format string. }
*/
class FormatError : public std::runtime_error {
public:
explicit FormatError(const std::string &message)
: std::runtime_error(message) {}
}; };
// Throws Exception(message) if format contains '}', otherwise throws
// FormatError reporting unmatched '{'. The idea is that unmatched '{'
// should override other errors.
template <typename Char>
struct FormatErrorReporter {
int num_open_braces;
void operator()(const Char *s, fmt::StringRef message) const;
};
// Parses a nonnegative integer advancing s to the end of the parsed input.
// This function assumes that the first character of s is a digit.
template <typename Char>
int ParseNonnegativeInt(
const Char *&s, const char *&error) FMT_NOEXCEPT(true);
} // namespace internal
/** /**
An error returned by an operating system or a language runtime, An error returned by an operating system or a language runtime,
for example a file opening error. for example a file opening error.
@ -881,6 +902,8 @@ class BasicWriter {
}; };
}; };
static const ArgInfo DUMMY_ARG;
// An argument action that does nothing. // An argument action that does nothing.
struct NullArgAction { struct NullArgAction {
void operator()() const {} void operator()() const {}
@ -978,12 +1001,8 @@ class BasicWriter {
private: private:
std::size_t num_args_; std::size_t num_args_;
const ArgInfo *args_; const ArgInfo *args_;
int num_open_braces_;
int next_arg_index_; int next_arg_index_;
fmt::internal::FormatErrorReporter<Char> report_error_;
void ReportError(const Char *s, StringRef message) const;
unsigned ParseUInt(const Char *&s) const;
// Parses argument index and returns an argument with this index. // Parses argument index and returns an argument with this index.
const ArgInfo &ParseArgIndex(const Char *&s); const ArgInfo &ParseArgIndex(const Char *&s);
@ -995,6 +1014,18 @@ class BasicWriter {
BasicStringRef<Char> format, std::size_t num_args, const ArgInfo *args); BasicStringRef<Char> format, std::size_t num_args, const ArgInfo *args);
}; };
// Printf format string parser.
class PrintfParser {
private:
std::size_t num_args_;
const ArgInfo *args_;
int next_arg_index_;
public:
void Format(BasicWriter<Char> &writer,
BasicStringRef<Char> format, std::size_t num_args, const ArgInfo *args);
};
public: public:
/** /**
Constructs a ``BasicWriter`` object. Constructs a ``BasicWriter`` object.
@ -1052,6 +1083,11 @@ class BasicWriter {
FormatParser().Format(*this, format, num_args, args); FormatParser().Format(*this, format, num_args, args);
} }
inline void vprintf(BasicStringRef<Char> format,
std::size_t num_args, const ArgInfo *args) {
PrintfParser().Format(*this, format, num_args, args);
}
/** /**
\rst \rst
Formats a string sending the output to the writer. Arguments are Formats a string sending the output to the writer. Arguments are
@ -1112,6 +1148,12 @@ class BasicWriter {
Arg arg_array[] = {args...}; Arg arg_array[] = {args...};
VFormat(format, sizeof...(Args), arg_array); VFormat(format, sizeof...(Args), arg_array);
} }
template<typename... Args>
void printf(BasicStringRef<Char> format, const Args & ... args) {
Arg arg_array[] = {args...};
vprintf(format, sizeof...(Args), arg_array);
}
#endif #endif
BasicWriter &operator<<(int value) { BasicWriter &operator<<(int value) {
@ -1773,6 +1815,20 @@ void Print(std::FILE *f, StringRef format, const Args & ... args) {
std::fwrite(w.data(), 1, w.size(), f); std::fwrite(w.data(), 1, w.size(), f);
} }
template<typename... Args>
inline Writer sprintf(StringRef format, const Args & ... args) {
Writer w;
w.printf(format, args...);
return std::move(w);
}
template<typename... Args>
void printf(StringRef format, const Args & ... args) {
Writer w;
w.printf(format, args...);
std::fwrite(w.data(), 1, w.size(), stdout);
}
#endif // FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES #endif // FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES
/** /**

View File

@ -49,6 +49,7 @@ FILE *FOpen(const char *filename, const char *mode) {
#endif #endif
#include "format.h" #include "format.h"
#include "util.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include <stdint.h> #include <stdint.h>
@ -110,19 +111,6 @@ struct WriteChecker {
#define CHECK_WRITE_WCHAR(value) \ #define CHECK_WRITE_WCHAR(value) \
EXPECT_PRED_FORMAT1(WriteChecker<wchar_t>(), value) EXPECT_PRED_FORMAT1(WriteChecker<wchar_t>(), value)
// Increment a number in a string.
void Increment(char *s) {
for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
if (s[i] != '9') {
++s[i];
break;
}
s[i] = '0';
}
}
enum {BUFFER_SIZE = 256};
#ifdef _MSC_VER #ifdef _MSC_VER
# define vsnprintf vsprintf_s # define vsnprintf vsprintf_s
#endif #endif
@ -134,234 +122,13 @@ void SPrintf(char *buffer, const char *format, ...) {
va_end(args); va_end(args);
} }
std::string ReadFile(fmt::StringRef filename) { std::string ReadFile(StringRef filename) {
std::ifstream out(filename.c_str()); std::ifstream out(filename.c_str());
std::stringstream content; std::stringstream content;
content << out.rdbuf(); content << out.rdbuf();
return content.str(); return content.str();
} }
std::string GetSystemErrorMessage(int error_code) {
#ifndef _WIN32
return strerror(error_code);
#else
enum { BUFFER_SIZE = 200 };
char buffer[BUFFER_SIZE];
EXPECT_EQ(0, strerror_s(buffer, BUFFER_SIZE, error_code));
std::size_t max_len = BUFFER_SIZE - 1;
EXPECT_LT(std::strlen(buffer), max_len);
return buffer;
#endif
} }
}
TEST(UtilTest, Increment) {
char s[10] = "123";
Increment(s);
EXPECT_STREQ("124", s);
s[2] = '8';
Increment(s);
EXPECT_STREQ("129", s);
Increment(s);
EXPECT_STREQ("130", s);
s[1] = s[2] = '9';
Increment(s);
EXPECT_STREQ("200", s);
}
// Tests fmt::internal::CountDigits for integer type Int.
template <typename Int>
void TestCountDigits(Int) {
for (Int i = 0; i < 10; ++i)
EXPECT_EQ(1u, fmt::internal::CountDigits(i));
for (Int i = 1, n = 1,
end = std::numeric_limits<Int>::max() / 10; n <= end; ++i) {
n *= 10;
EXPECT_EQ(i, fmt::internal::CountDigits(n - 1));
EXPECT_EQ(i + 1, fmt::internal::CountDigits(n));
}
}
TEST(UtilTest, CountDigits) {
TestCountDigits(uint32_t());
TestCountDigits(uint64_t());
}
#ifdef _WIN32
TEST(UtilTest, UTF16ToUTF8) {
std::string s = "ёжик";
fmt::internal::UTF16ToUTF8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, fmt::str(u));
EXPECT_EQ(s.size(), u.size());
}
TEST(UtilTest, UTF8ToUTF16) {
std::string s = "лошадка";
fmt::internal::UTF8ToUTF16 u(s.c_str());
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", fmt::str(u));
EXPECT_EQ(7, u.size());
}
template <typename Converter>
void CheckUTFConversionError(const char *message) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_INVALID_PARAMETER, message);
fmt::SystemError error("", 0);
try {
Converter(0);
} catch (const fmt::SystemError &e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
EXPECT_EQ(out.str(), error.what());
}
TEST(UtilTest, UTF16ToUTF8Error) {
CheckUTFConversionError<fmt::internal::UTF16ToUTF8>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(UtilTest, UTF8ToUTF16Error) {
CheckUTFConversionError<fmt::internal::UTF8ToUTF16>(
"cannot convert string from UTF-8 to UTF-16");
}
TEST(UtilTest, UTF16ToUTF8Convert) {
fmt::internal::UTF16ToUTF8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.Convert(0));
}
#endif // _WIN32
TEST(UtilTest, StrError) {
using fmt::internal::StrError;
char *message = 0;
char buffer[BUFFER_SIZE];
#ifndef NDEBUG
EXPECT_DEBUG_DEATH(StrError(EDOM, message = 0, 0), "Assertion");
EXPECT_DEBUG_DEATH(StrError(EDOM, message = buffer, 0), "Assertion");
#endif
buffer[0] = 'x';
#ifdef _GNU_SOURCE
// Use invalid error code to make sure that StrError returns an error
// message in the buffer rather than a pointer to a static string.
int error_code = -1;
#else
int error_code = EDOM;
#endif
int result = StrError(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
result = StrError(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(0, result);
std::size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(GetSystemErrorMessage(error_code), message);
result = StrError(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
}
TEST(UtilTest, SystemError) {
fmt::SystemError e(fmt::StringRef("test"), 42);
EXPECT_STREQ("test", e.what());
EXPECT_EQ(42, e.error_code());
}
typedef void (*FormatErrorMessage)(
fmt::Writer &out, int error_code, fmt::StringRef message);
template <typename Sink>
void CheckErrorSink(int error_code, FormatErrorMessage format) {
fmt::SystemError error("", 0);
Sink sink(error_code);
fmt::Writer w;
w << "test";
try {
sink(w);
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
template <typename Sink>
void CheckThrowError(int error_code, FormatErrorMessage format,
fmt::Formatter<Sink> (*throw_error)(int error_code, StringRef format)) {
fmt::SystemError error("", 0);
try {
throw_error(error_code, "test {}") << "error";
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test error");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
TEST(UtilTest, FormatSystemErrorMessage) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EDOM, "test");
EXPECT_EQ(str(fmt::Format("test: {}")
<< GetSystemErrorMessage(EDOM)), fmt::str(message));
}
TEST(UtilTest, SystemErrorSink) {
CheckErrorSink<fmt::SystemErrorSink>(
EDOM, fmt::internal::FormatSystemErrorMessage);
}
TEST(UtilTest, ThrowSystemError) {
CheckThrowError(EDOM,
fmt::internal::FormatSystemErrorMessage, fmt::ThrowSystemError);
}
TEST(ErrorTest, ReportSystemError) {
fmt::Writer out;
fmt::internal::FormatSystemErrorMessage(out, EDOM, "test error");
out << '\n';
EXPECT_WRITE(stderr, fmt::ReportSystemError(EDOM, "test error"), out.str());
}
#ifdef _WIN32
TEST(UtilTest, FormatWinErrorMessage) {
LPWSTR message = 0;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0,
ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::internal::UTF16ToUTF8 utf8_message(message);
LocalFree(message);
fmt::Writer actual_message;
fmt::internal::FormatWinErrorMessage(
actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(str(fmt::Format("test: {}") << fmt::str(utf8_message)),
fmt::str(actual_message));
}
TEST(UtilTest, WinErrorSink) {
CheckErrorSink<fmt::WinErrorSink>(
ERROR_FILE_EXISTS, fmt::internal::FormatWinErrorMessage);
}
TEST(UtilTest, ThrowWinError) {
CheckThrowError(ERROR_FILE_EXISTS,
fmt::internal::FormatWinErrorMessage, fmt::ThrowWinError);
}
TEST(ErrorTest, ReportWinError) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_FILE_EXISTS, "test error");
out << '\n';
EXPECT_WRITE(stderr,
fmt::ReportWinError(ERROR_FILE_EXISTS, "test error"), out.str());
}
#endif // _WIN32
class TestString { class TestString {
private: private:
@ -849,18 +616,15 @@ TEST(FormatterTest, ArgErrors) {
"argument index is out of range in format"); "argument index is out of range in format");
char format[BUFFER_SIZE]; char format[BUFFER_SIZE];
SPrintf(format, "{%u", UINT_MAX); SPrintf(format, "{%u", INT_MAX);
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format"); EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
SPrintf(format, "{%u}", UINT_MAX); SPrintf(format, "{%u}", INT_MAX);
EXPECT_THROW_MSG(Format(format), FormatError, EXPECT_THROW_MSG(Format(format), FormatError,
"argument index is out of range in format"); "argument index is out of range in format");
SPrintf(format, "{%u", UINT_MAX); SPrintf(format, "{%u", INT_MAX + 1u);
Increment(format + 1);
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format"); EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
std::size_t size = std::strlen(format); SPrintf(format, "{%u}", INT_MAX + 1u);
format[size] = '}';
format[size + 1] = 0;
EXPECT_THROW_MSG(Format(format), FormatError, "number is too big in format"); EXPECT_THROW_MSG(Format(format), FormatError, "number is too big in format");
} }

144
test/printf-test.cc Normal file
View File

@ -0,0 +1,144 @@
/*
printf tests.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <climits>
#include <cstring>
#include "format.h"
#include "gtest-extra.h"
#include "util.h"
#if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES
using fmt::Format;
using fmt::FormatError;
// Returns a number UINT_MAX + 1 formatted as a string.
std::string GetBigNumber() {
char format[BUFFER_SIZE];
sprintf(format, "%u", UINT_MAX);
Increment(format + 1);
return format;
}
const unsigned BIG_NUM = INT_MAX + 1u;
TEST(PrintfTest, NoArgs) {
EXPECT_EQ("test", str(fmt::sprintf("test")));
}
TEST(PrintfTest, Escape) {
EXPECT_EQ("%", str(fmt::sprintf("%%")));
EXPECT_EQ("before %", str(fmt::sprintf("before %%")));
EXPECT_EQ("% after", str(fmt::sprintf("%% after")));
EXPECT_EQ("before % after", str(fmt::sprintf("before %% after")));
EXPECT_EQ("%s", str(fmt::sprintf("%%s")));
}
TEST(PrintfTest, PositionalArgs) {
EXPECT_EQ("42", str(fmt::sprintf("%1$d", 42)));
EXPECT_EQ("before 42", str(fmt::sprintf("before %1$d", 42)));
EXPECT_EQ("42 after", str(fmt::sprintf("%1$d after",42)));
EXPECT_EQ("before 42 after", str(fmt::sprintf("before %1$d after", 42)));
EXPECT_EQ("answer = 42", str(fmt::sprintf("%1$s = %2$d", "answer", 42)));
EXPECT_EQ("42 is the answer",
str(fmt::sprintf("%2$d is the %1$s", "answer", 42)));
EXPECT_EQ("abracadabra", str(fmt::sprintf("%1$s%2$s%1$s", "abra", "cad")));
}
TEST(PrintfTest, AutomaticArgIndexing) {
EXPECT_EQ("abc", str(fmt::sprintf("%c%c%c", 'a', 'b', 'c')));
}
TEST(PrintfTest, NumberIsTooBigInArgIndex) {
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$", BIG_NUM))),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", BIG_NUM))),
FormatError, "number is too big in format");
}
TEST(PrintfTest, SwitchArgIndexing) {
EXPECT_THROW_MSG(fmt::sprintf("%1$d%", 1, 2),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1$d%{}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::sprintf("%1$d%d", 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::sprintf("%d%1$", 1, 2),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%d%{}$d", BIG_NUM)), 1, 2),
FormatError, "number is too big in format");
EXPECT_THROW_MSG(fmt::sprintf("%d%1$d", 1, 2),
FormatError, "cannot switch from automatic to manual argument indexing");
// Indexing errors override width errors.
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%d%1${}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1$d%{}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
}
TEST(PrintfTest, InvalidArgIndex) {
EXPECT_THROW_MSG(fmt::sprintf("%0$d", 42), FormatError,
"argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf("%2$d", 42), FormatError,
"argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", INT_MAX)), 42),
FormatError, "argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf("%2$", 42),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", BIG_NUM)), 42),
FormatError, "number is too big in format");
}
TEST(PrintfTest, Width) {
EXPECT_EQ(" abc", str(fmt::sprintf("%5s", "abc")));
EXPECT_EQ(" abc", str(fmt::sprintf("%1$5s", "abc")));
// Width cannot be specified twice.
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), FormatError,
"unknown format code '-' for integer");
}
TEST(PrintfTest, InvalidWidth) {
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}d", BIG_NUM)), 42),
FormatError, "number is too big in format");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1${}d", BIG_NUM)), 42),
FormatError, "number is too big in format");
}
// TODO
TEST(PrintfTest, Align) {
EXPECT_EQ(" abc", str(fmt::sprintf("%5s", "abc")));
EXPECT_EQ("abc ", str(fmt::sprintf("%-5s", "abc")));
}
#endif

256
test/util-test.cc Normal file
View File

@ -0,0 +1,256 @@
/*
Utility tests.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstring>
#include "gtest-extra.h"
#include "format.h"
#include "util.h"
using fmt::StringRef;
namespace {
std::string GetSystemErrorMessage(int error_code) {
#ifndef _WIN32
return strerror(error_code);
#else
enum { BUFFER_SIZE = 200 };
char buffer[BUFFER_SIZE];
EXPECT_EQ(0, strerror_s(buffer, BUFFER_SIZE, error_code));
std::size_t max_len = BUFFER_SIZE - 1;
EXPECT_LT(std::strlen(buffer), max_len);
return buffer;
#endif
}
}
TEST(UtilTest, Increment) {
char s[10] = "123";
Increment(s);
EXPECT_STREQ("124", s);
s[2] = '8';
Increment(s);
EXPECT_STREQ("129", s);
Increment(s);
EXPECT_STREQ("130", s);
s[1] = s[2] = '9';
Increment(s);
EXPECT_STREQ("200", s);
}
// Tests fmt::internal::CountDigits for integer type Int.
template <typename Int>
void TestCountDigits(Int) {
for (Int i = 0; i < 10; ++i)
EXPECT_EQ(1u, fmt::internal::CountDigits(i));
for (Int i = 1, n = 1,
end = std::numeric_limits<Int>::max() / 10; n <= end; ++i) {
n *= 10;
EXPECT_EQ(i, fmt::internal::CountDigits(n - 1));
EXPECT_EQ(i + 1, fmt::internal::CountDigits(n));
}
}
TEST(UtilTest, CountDigits) {
TestCountDigits(uint32_t());
TestCountDigits(uint64_t());
}
#ifdef _WIN32
TEST(UtilTest, UTF16ToUTF8) {
std::string s = "ёжик";
fmt::internal::UTF16ToUTF8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, fmt::str(u));
EXPECT_EQ(s.size(), u.size());
}
TEST(UtilTest, UTF8ToUTF16) {
std::string s = "лошадка";
fmt::internal::UTF8ToUTF16 u(s.c_str());
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", fmt::str(u));
EXPECT_EQ(7, u.size());
}
template <typename Converter>
void CheckUTFConversionError(const char *message) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_INVALID_PARAMETER, message);
fmt::SystemError error("", 0);
try {
Converter(0);
} catch (const fmt::SystemError &e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
EXPECT_EQ(out.str(), error.what());
}
TEST(UtilTest, UTF16ToUTF8Error) {
CheckUTFConversionError<fmt::internal::UTF16ToUTF8>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(UtilTest, UTF8ToUTF16Error) {
CheckUTFConversionError<fmt::internal::UTF8ToUTF16>(
"cannot convert string from UTF-8 to UTF-16");
}
TEST(UtilTest, UTF16ToUTF8Convert) {
fmt::internal::UTF16ToUTF8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.Convert(0));
}
#endif // _WIN32
TEST(UtilTest, StrError) {
using fmt::internal::StrError;
char *message = 0;
char buffer[BUFFER_SIZE];
#ifndef NDEBUG
EXPECT_DEBUG_DEATH(StrError(EDOM, message = 0, 0), "Assertion");
EXPECT_DEBUG_DEATH(StrError(EDOM, message = buffer, 0), "Assertion");
#endif
buffer[0] = 'x';
#ifdef _GNU_SOURCE
// Use invalid error code to make sure that StrError returns an error
// message in the buffer rather than a pointer to a static string.
int error_code = -1;
#else
int error_code = EDOM;
#endif
int result = StrError(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
result = StrError(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(0, result);
std::size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(GetSystemErrorMessage(error_code), message);
result = StrError(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
}
TEST(UtilTest, SystemError) {
fmt::SystemError e(StringRef("test"), 42);
EXPECT_STREQ("test", e.what());
EXPECT_EQ(42, e.error_code());
}
typedef void (*FormatErrorMessage)(
fmt::Writer &out, int error_code, StringRef message);
template <typename Sink>
void CheckErrorSink(int error_code, FormatErrorMessage format) {
fmt::SystemError error("", 0);
Sink sink(error_code);
fmt::Writer w;
w << "test";
try {
sink(w);
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
template <typename Sink>
void CheckThrowError(int error_code, FormatErrorMessage format,
fmt::Formatter<Sink> (*throw_error)(int error_code, StringRef format)) {
fmt::SystemError error("", 0);
try {
throw_error(error_code, "test {}") << "error";
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test error");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
TEST(UtilTest, FormatSystemErrorMessage) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EDOM, "test");
EXPECT_EQ(str(fmt::Format("test: {}")
<< GetSystemErrorMessage(EDOM)), fmt::str(message));
}
TEST(UtilTest, SystemErrorSink) {
CheckErrorSink<fmt::SystemErrorSink>(
EDOM, fmt::internal::FormatSystemErrorMessage);
}
TEST(UtilTest, ThrowSystemError) {
CheckThrowError(EDOM,
fmt::internal::FormatSystemErrorMessage, fmt::ThrowSystemError);
}
TEST(UtilTest, ReportSystemError) {
fmt::Writer out;
fmt::internal::FormatSystemErrorMessage(out, EDOM, "test error");
out << '\n';
EXPECT_WRITE(stderr, fmt::ReportSystemError(EDOM, "test error"), out.str());
}
#ifdef _WIN32
TEST(UtilTest, FormatWinErrorMessage) {
LPWSTR message = 0;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0,
ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::internal::UTF16ToUTF8 utf8_message(message);
LocalFree(message);
fmt::Writer actual_message;
fmt::internal::FormatWinErrorMessage(
actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(str(fmt::Format("test: {}") << fmt::str(utf8_message)),
fmt::str(actual_message));
}
TEST(UtilTest, WinErrorSink) {
CheckErrorSink<fmt::WinErrorSink>(
ERROR_FILE_EXISTS, fmt::internal::FormatWinErrorMessage);
}
TEST(UtilTest, ThrowWinError) {
CheckThrowError(ERROR_FILE_EXISTS,
fmt::internal::FormatWinErrorMessage, fmt::ThrowWinError);
}
TEST(UtilTest, ReportWinError) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_FILE_EXISTS, "test error");
out << '\n';
EXPECT_WRITE(stderr,
fmt::ReportWinError(ERROR_FILE_EXISTS, "test error"), out.str());
}
#endif // _WIN32

39
test/util.cc Normal file
View File

@ -0,0 +1,39 @@
/*
Test utilities.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "util.h"
#include <cstring>
void Increment(char *s) {
for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
if (s[i] != '9') {
++s[i];
break;
}
s[i] = '0';
}
}

31
test/util.h Normal file
View File

@ -0,0 +1,31 @@
/*
Test utilities.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
enum {BUFFER_SIZE = 256};
// Increment a number in a string.
void Increment(char *s);