From 6daa6ce9639ffd9a85fb9686015e85b29e66c5e7 Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Wed, 8 Jun 2016 01:23:32 +0200 Subject: [PATCH 1/6] Implemented #335: custom printf support --- fmt/format.cc | 573 +-------------------------- fmt/format.h | 115 ++---- fmt/ostream.cc | 4 +- fmt/printf.h | 710 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 3 +- test/custom-formatter-test.cc | 98 +++++ test/printf-test.cc | 1 + 7 files changed, 844 insertions(+), 660 deletions(-) create mode 100644 fmt/printf.h create mode 100644 test/custom-formatter-test.cc diff --git a/fmt/format.cc b/fmt/format.cc index ad3a1cb0008c..0efcbb6f2139 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -26,336 +26,19 @@ */ #include "fmt/format.h" +#include "printf.h" #include #include -#include -#include + #include #include #include // for std::ptrdiff_t -#if defined(_WIN32) && defined(__MINGW32__) -# include -#endif - -#if FMT_USE_WINDOWS_H -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include -# else -# define NOMINMAX -# include -# undef NOMINMAX -# endif -#endif - -using fmt::internal::Arg; - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4702) // unreachable code -// Disable deprecation warning for strerror. The latter is not called but -// MSVC fails to detect it. -# pragma warning(disable: 4996) -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -static inline fmt::internal::Null<> strerror_r(int, char *, ...) { - return fmt::internal::Null<>(); -} -static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { - return fmt::internal::Null<>(); -} namespace fmt { -namespace { - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) -# define FMT_SWPRINTF snwprintf -#else -# define FMT_SWPRINTF swprintf -#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) - -// Checks if a value fits in int - used to avoid warnings about comparing -// signed and unsigned integers. -template -struct IntChecker { - template - static bool fits_in_int(T value) { - unsigned max = INT_MAX; - return value <= max; - } - static bool fits_in_int(bool) { return true; } -}; - -template <> -struct IntChecker { - template - static bool fits_in_int(T value) { - return value >= INT_MIN && value <= INT_MAX; - } - static bool fits_in_int(int) { return true; } -}; - -const char RESET_COLOR[] = "\x1b[0m"; - -typedef void (*FormatFunc)(Writer &, int, StringRef); - -// Portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -int safe_strerror( - int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); - - class StrError { - private: - int error_code_; - char *&buffer_; - std::size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const StrError &) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - int handle(char *message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - int handle(internal::Null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? - ERANGE : result; - } - - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::Null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } - - public: - StrError(int err_code, char *&buf, std::size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { - strerror_r(0, 0, ""); // Suppress a warning about unused strerror_r. - return handle(strerror_r(error_code_, buffer_, buffer_size_)); - } - }; - return StrError(error_code, buffer, buffer_size).run(); -} - -void format_error_code(Writer &out, int error_code, - StringRef message) FMT_NOEXCEPT { - // Report error code making sure that the output fits into - // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential - // bad_alloc. - out.clear(); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::IntTraits::MainType MainType; - MainType abs_value = static_cast(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::count_digits(abs_value); - if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) - out << message << SEP; - out << ERROR_STR << error_code; - assert(out.size() <= internal::INLINE_BUFFER_SIZE); -} - -void report_error(FormatFunc func, int error_code, - StringRef message) FMT_NOEXCEPT { - MemoryWriter full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); -} - -// IsZeroInt::visit(arg) returns true iff arg is a zero integer. -class IsZeroInt : public ArgVisitor { - public: - template - bool visit_any_int(T value) { return value == 0; } -}; - -// Checks if an argument is a valid printf width specifier and sets -// left alignment if it is negative. -class WidthHandler : public ArgVisitor { - private: - FormatSpec &spec_; - - FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); - - public: - explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} - - void report_unhandled_arg() { - FMT_THROW(FormatError("width is not integer")); - } - - template - unsigned visit_any_int(T value) { - typedef typename internal::IntTraits::MainType UnsignedType; - UnsignedType width = static_cast(value); - if (internal::is_negative(value)) { - spec_.align_ = ALIGN_LEFT; - width = 0 - width; - } - if (width > INT_MAX) - FMT_THROW(FormatError("number is too big")); - return static_cast(width); - } -}; - -class PrecisionHandler : public ArgVisitor { - public: - void report_unhandled_arg() { - FMT_THROW(FormatError("precision is not integer")); - } - - template - int visit_any_int(T value) { - if (!IntChecker::is_signed>::fits_in_int(value)) - FMT_THROW(FormatError("number is too big")); - return static_cast(value); - } -}; - -template -struct is_same { - enum { value = 0 }; -}; - -template -struct is_same { - enum { value = 1 }; -}; - -// An argument visitor that converts an integer argument to T for printf, -// if T is an integral type. If T is void, the argument is converted to -// corresponding signed or unsigned type depending on the type specifier: -// 'd' and 'i' - signed, other - unsigned) -template -class ArgConverter : public ArgVisitor, void> { - private: - internal::Arg &arg_; - wchar_t type_; - - FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); - - public: - ArgConverter(internal::Arg &arg, wchar_t type) - : arg_(arg), type_(type) {} - - void visit_bool(bool value) { - if (type_ != 's') - visit_any_int(value); - } - - template - void visit_any_int(U value) { - bool is_signed = type_ == 'd' || type_ == 'i'; - using internal::Arg; - typedef typename internal::Conditional< - is_same::value, U, T>::type TargetType; - if (sizeof(TargetType) <= sizeof(int)) { - // Extra casts are used to silence warnings. - if (is_signed) { - arg_.type = Arg::INT; - arg_.int_value = static_cast(static_cast(value)); - } else { - arg_.type = Arg::UINT; - typedef typename internal::MakeUnsigned::Type Unsigned; - arg_.uint_value = static_cast(static_cast(value)); - } - } else { - if (is_signed) { - arg_.type = Arg::LONG_LONG; - // glibc's printf doesn't sign extend arguments of smaller types: - // std::printf("%lld", -42); // prints "4294967254" - // but we don't have to do the same because it's a UB. - arg_.long_long_value = static_cast(value); - } else { - arg_.type = Arg::ULONG_LONG; - arg_.ulong_long_value = - static_cast::Type>(value); - } - } - } -}; -// Converts an integer argument to char for printf. -class CharConverter : public ArgVisitor { - private: - internal::Arg &arg_; - - FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); - - public: - explicit CharConverter(internal::Arg &arg) : arg_(arg) {} - - template - void visit_any_int(T value) { - arg_.type = internal::Arg::CHAR; - arg_.int_value = static_cast(value); - } -}; -} // namespace namespace internal { @@ -366,75 +49,9 @@ FMT_FUNC void format_system_error( fmt::format_system_error(out, error_code, message); } -template -class PrintfArgFormatter : - public ArgFormatterBase, Char> { - - void write_null_pointer() { - this->spec().type_ = 0; - this->write("(nil)"); - } - - typedef ArgFormatterBase, Char> Base; - - public: - PrintfArgFormatter(BasicWriter &w, FormatSpec &s) - : ArgFormatterBase, Char>(w, s) {} - - void visit_bool(bool value) { - FormatSpec &fmt_spec = this->spec(); - if (fmt_spec.type_ != 's') - return this->visit_any_int(value); - fmt_spec.type_ = 0; - this->write(value); - } - - void visit_char(int value) { - const FormatSpec &fmt_spec = this->spec(); - BasicWriter &w = this->writer(); - if (fmt_spec.type_ && fmt_spec.type_ != 'c') - w.write_int(value, fmt_spec); - typedef typename BasicWriter::CharPtr CharPtr; - CharPtr out = CharPtr(); - if (fmt_spec.width_ > 1) { - Char fill = ' '; - out = w.grow_buffer(fmt_spec.width_); - if (fmt_spec.align_ != ALIGN_LEFT) { - std::fill_n(out, fmt_spec.width_ - 1, fill); - out += fmt_spec.width_ - 1; - } else { - std::fill_n(out + 1, fmt_spec.width_ - 1, fill); - } - } else { - out = w.grow_buffer(1); - } - *out = static_cast(value); - } - - void visit_cstring(const char *value) { - if (value) - Base::visit_cstring(value); - else if (this->spec().type_ == 'p') - write_null_pointer(); - else - this->write("(null)"); - } +} // namespace internal - void visit_pointer(const void *value) { - if (value) - return Base::visit_pointer(value); - this->spec().type_ = 0; - write_null_pointer(); - } - void visit_custom(Arg::CustomValue c) { - BasicFormatter formatter(ArgList(), this->writer()); - const Char format_str[] = {'}', 0}; - const Char *format = format_str; - c.format(&formatter, c.value, &format); - } -}; -} // namespace internal } // namespace fmt FMT_FUNC void fmt::SystemError::init( @@ -686,177 +303,7 @@ FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg( return arg; } -template -void fmt::internal::PrintfFormatter::parse_flags( - FormatSpec &spec, const Char *&s) { - for (;;) { - switch (*s++) { - case '-': - spec.align_ = ALIGN_LEFT; - break; - case '+': - spec.flags_ |= SIGN_FLAG | PLUS_FLAG; - break; - case '0': - spec.fill_ = '0'; - break; - case ' ': - spec.flags_ |= SIGN_FLAG; - break; - case '#': - spec.flags_ |= HASH_FLAG; - break; - default: - --s; - return; - } - } -} -template -Arg fmt::internal::PrintfFormatter::get_arg( - const Char *s, unsigned arg_index) { - (void)s; - const char *error = 0; - Arg arg = arg_index == UINT_MAX ? - next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); - if (error) - FMT_THROW(FormatError(!*s ? "invalid format string" : error)); - return arg; -} - -template -unsigned fmt::internal::PrintfFormatter::parse_header( - const Char *&s, FormatSpec &spec) { - unsigned arg_index = UINT_MAX; - Char c = *s; - if (c >= '0' && c <= '9') { - // Parse an argument index (if followed by '$') or a width possibly - // preceded with '0' flag(s). - unsigned value = parse_nonnegative_int(s); - if (*s == '$') { // value is an argument index - ++s; - arg_index = value; - } else { - if (c == '0') - spec.fill_ = '0'; - if (value != 0) { - // Nonzero value means that we parsed width and don't need to - // parse it or flags again, so return now. - spec.width_ = value; - return arg_index; - } - } - } - parse_flags(spec, s); - // Parse width. - if (*s >= '0' && *s <= '9') { - spec.width_ = parse_nonnegative_int(s); - } else if (*s == '*') { - ++s; - spec.width_ = WidthHandler(spec).visit(get_arg(s)); - } - return arg_index; -} - -template -void fmt::internal::PrintfFormatter::format( - BasicWriter &writer, BasicCStringRef format_str) { - const Char *start = format_str.c_str(); - const Char *s = start; - while (*s) { - Char c = *s++; - if (c != '%') continue; - if (*s == c) { - write(writer, start, s); - start = ++s; - continue; - } - write(writer, start, s - 1); - - FormatSpec spec; - spec.align_ = ALIGN_RIGHT; - - // Parse argument index, flags and width. - unsigned arg_index = parse_header(s, spec); - - // Parse precision. - if (*s == '.') { - ++s; - if ('0' <= *s && *s <= '9') { - spec.precision_ = static_cast(parse_nonnegative_int(s)); - } else if (*s == '*') { - ++s; - spec.precision_ = PrecisionHandler().visit(get_arg(s)); - } - } - - Arg arg = get_arg(s, arg_index); - if (spec.flag(HASH_FLAG) && IsZeroInt().visit(arg)) - spec.flags_ &= ~to_unsigned(HASH_FLAG); - if (spec.fill_ == '0') { - if (arg.type <= Arg::LAST_NUMERIC_TYPE) - spec.align_ = ALIGN_NUMERIC; - else - spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. - } - - // Parse length and convert the argument to the required type. - switch (*s++) { - case 'h': - if (*s == 'h') - ArgConverter(arg, *++s).visit(arg); - else - ArgConverter(arg, *s).visit(arg); - break; - case 'l': - if (*s == 'l') - ArgConverter(arg, *++s).visit(arg); - else - ArgConverter(arg, *s).visit(arg); - break; - case 'j': - ArgConverter(arg, *s).visit(arg); - break; - case 'z': - ArgConverter(arg, *s).visit(arg); - break; - case 't': - ArgConverter(arg, *s).visit(arg); - break; - case 'L': - // printf produces garbage when 'L' is omitted for long double, no - // need to do the same. - break; - default: - --s; - ArgConverter(arg, *s).visit(arg); - } - - // Parse type. - if (!*s) - FMT_THROW(FormatError("invalid format string")); - spec.type_ = static_cast(*s++); - if (arg.type <= Arg::LAST_INTEGER_TYPE) { - // Normalize type. - switch (spec.type_) { - case 'i': case 'u': - spec.type_ = 'd'; - break; - case 'c': - // TODO: handle wchar_t - CharConverter(arg).visit(arg); - break; - } - } - - start = s; - - // Format argument. - internal::PrintfArgFormatter(writer, spec).visit(arg); - } - write(writer, start, s); -} FMT_FUNC void fmt::report_system_error( int error_code, fmt::StringRef message) FMT_NOEXCEPT { @@ -890,12 +337,10 @@ FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) { std::fputs(RESET_COLOR, stdout); } -FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) { - MemoryWriter w; - printf(w, format, args); - std::size_t size = w.size(); - return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); -} + + + + #ifndef FMT_HEADER_ONLY @@ -907,8 +352,6 @@ template void fmt::internal::FixedBuffer::grow(std::size_t); template void fmt::internal::ArgMap::init(const fmt::ArgList &args); -template void fmt::internal::PrintfFormatter::format( - BasicWriter &writer, CStringRef format); template int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, @@ -924,8 +367,6 @@ template void fmt::internal::FixedBuffer::grow(std::size_t); template void fmt::internal::ArgMap::init(const fmt::ArgList &args); -template void fmt::internal::PrintfFormatter::format( - BasicWriter &writer, WCStringRef format); template int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, diff --git a/fmt/format.h b/fmt/format.h index 7cdac979a80d..bf71e2daab47 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -237,7 +237,7 @@ typedef __int64 intmax_t; # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) #endif -// Some compilers masquerade as both MSVC and GCC-likes or +// Some compilers masquerade as both MSVC and GCC-likes or // otherwise support __builtin_clz and __builtin_clzll, so // only define FMT_BUILTIN_CLZ using the MSVC intrinsics // if the clz and clzll builtins are not available. @@ -253,7 +253,7 @@ inline uint32_t clz(uint32_t x) { assert(x != 0); // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, + // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. # pragma warning(suppress: 6102) return 31 - r; @@ -279,7 +279,7 @@ inline uint32_t clzll(uint64_t x) { assert(x != 0); // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, + // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. # pragma warning(suppress: 6102) return 63 - r; @@ -1296,7 +1296,7 @@ class MakeArg : public Arg { MakeArg() { type = Arg::NONE; } - + template MakeArg(const T &value) : Arg(MakeValue(value)) { @@ -1318,8 +1318,6 @@ class RuntimeError : public std::runtime_error { RuntimeError() : std::runtime_error("") {} }; -template -class PrintfArgFormatter; template class ArgMap; @@ -1938,25 +1936,6 @@ class FormatterBase { } }; -// A printf formatter. -template -class PrintfFormatter : private FormatterBase { - private: - void parse_flags(FormatSpec &spec, const Char *&s); - - // Returns the argument with specified index or, if arg_index is equal - // to the maximum unsigned value, the next argument. - Arg get_arg(const Char *s, - unsigned arg_index = (std::numeric_limits::max)()); - - // Parses argument index, flags and width and returns the argument index. - unsigned parse_header(const Char *&s, FormatSpec &spec); - - public: - explicit PrintfFormatter(const ArgList &args) : FormatterBase(args) {} - FMT_API void format(BasicWriter &writer, - BasicCStringRef format_str); -}; } // namespace internal /** @@ -1978,11 +1957,11 @@ class PrintfFormatter : private FormatterBase { */ template class BasicArgFormatter : public internal::ArgFormatterBase { - private: +private: BasicFormatter &formatter_; const Char *format_; - public: +public: /** \rst Constructs an argument formatter object. @@ -1991,10 +1970,10 @@ class BasicArgFormatter : public internal::ArgFormatterBase { to the part of the format string being parsed for custom argument types. \endrst */ - BasicArgFormatter(BasicFormatter &formatter, - FormatSpec &spec, const Char *fmt) - : internal::ArgFormatterBase(formatter.writer(), spec), - formatter_(formatter), format_(fmt) {} + BasicArgFormatter(BasicFormatter &formatter, FormatSpec &spec, + const Char *fmt) + : internal::ArgFormatterBase(formatter.writer(), spec), + formatter_(formatter), format_(fmt) {} /** Formats argument of a custom (user-defined) type. */ void visit_custom(internal::Arg::CustomValue c) { @@ -2091,7 +2070,7 @@ struct ArgArray; template struct ArgArray { typedef Value Type[N > 0 ? N : 1]; - + template static Value make(const T &value) { #ifdef __clang__ @@ -2287,7 +2266,7 @@ class SystemError : public internal::RuntimeError { Formats an error returned by an operating system or a language runtime, for example a file opening error, and writes it to *out* in the following form: - + .. parsed-literal:: **: ** @@ -2301,6 +2280,11 @@ class SystemError : public internal::RuntimeError { FMT_API void format_system_error(fmt::Writer &out, int error_code, fmt::StringRef message) FMT_NOEXCEPT; + +namespace internal{ +template class PrintfArgFormatter; +} + /** \rst This template provides operations for formatting and writing data into @@ -2405,8 +2389,7 @@ class BasicWriter { // pointer as std::ostream does, cast it to const void*. // Do not implement! void operator<<(typename internal::WCharHelper::Unsupported); - void operator<<( - typename internal::WCharHelper::Unsupported); + void operator<<(typename internal::WCharHelper::Unsupported); // Appends floating-point length specifier to the format string. // The second argument is only used for overload resolution. @@ -2420,7 +2403,8 @@ class BasicWriter { template friend class internal::ArgFormatterBase; - friend class internal::PrintfArgFormatter; + template + friend class internal::PrintfArgFormatter; protected: /** @@ -3187,55 +3171,7 @@ FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); */ FMT_API void print(CStringRef format_str, ArgList args); -template -void printf(BasicWriter &w, BasicCStringRef format, ArgList args) { - internal::PrintfFormatter(args).format(w, format); -} - -/** - \rst - Formats arguments and returns the result as a string. - **Example**:: - - std::string message = fmt::sprintf("The answer is %d", 42); - \endrst -*/ -inline std::string sprintf(CStringRef format, ArgList args) { - MemoryWriter w; - printf(w, format, args); - return w.str(); -} - -inline std::wstring sprintf(WCStringRef format, ArgList args) { - WMemoryWriter w; - printf(w, format, args); - return w.str(); -} - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - fmt::fprintf(stderr, "Don't %s!", "panic"); - \endrst - */ -FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - fmt::printf("Elapsed time: %.2f seconds", 1.23); - \endrst - */ -inline int printf(CStringRef format, ArgList args) { - return fprintf(stdout, format, args); -} /** Fast integer formatter. @@ -3504,10 +3440,7 @@ FMT_VARIADIC(void, print, CStringRef) FMT_VARIADIC(void, print, std::FILE *, CStringRef) FMT_VARIADIC(void, print_colored, Color, CStringRef) -FMT_VARIADIC(std::string, sprintf, CStringRef) -FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) -FMT_VARIADIC(int, printf, CStringRef) -FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) + namespace internal { template @@ -3597,8 +3530,8 @@ inline internal::Arg BasicFormatter::parse_arg_name(const Char *&s) { return arg; } -template -const Char *BasicFormatter::format( +template +const Char *BasicFormatter::format( const Char *&format_str, const internal::Arg &arg) { using internal::Arg; const Char *s = format_str; @@ -3763,7 +3696,7 @@ const Char *BasicFormatter::format( FMT_THROW(FormatError("missing '}' in format string")); // Format argument. - ArgFormatter(*this, spec, s - 1).visit(arg); + ArgFormatter_Type(*this, spec, s - 1).visit(arg); return s; } diff --git a/fmt/ostream.cc b/fmt/ostream.cc index 3868d3849741..deb1dbf8b7b3 100644 --- a/fmt/ostream.cc +++ b/fmt/ostream.cc @@ -26,7 +26,7 @@ */ #include "fmt/ostream.h" - +#include "fmt/printf.h" namespace fmt { namespace { @@ -54,7 +54,7 @@ FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { FMT_FUNC int fprintf(std::ostream &os, CStringRef format, ArgList args) { MemoryWriter w; - printf(w, format, args); + fmt::printf(w, format, args); write(os, w); return static_cast(w.size()); } diff --git a/fmt/printf.h b/fmt/printf.h new file mode 100644 index 000000000000..897fb8f53987 --- /dev/null +++ b/fmt/printf.h @@ -0,0 +1,710 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, 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. + */ + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + + +#include "format.h" +#include +#include + + + +#if defined(_WIN32) && defined(__MINGW32__) +# include +#endif + +#if FMT_USE_WINDOWS_H +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif +#endif + +using fmt::internal::Arg; + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +static inline fmt::internal::Null<> strerror_r(int, char *, ...) { + return fmt::internal::Null<>(); +} +static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::Null<>(); +} + +namespace fmt { +namespace internal { + +template class DefaultPrintfArgFormatter; + +template > +class PrintfFormatter; + +template +class PrintfArgFormatter : public internal::ArgFormatterBase { + +private: + void write_null_pointer() { + this->spec().type_ = 0; + this->write("(nil)"); + } + +public: + PrintfArgFormatter(BasicWriter &w, FormatSpec &s) + : internal::ArgFormatterBase(w, s) {} + + void visit_bool(bool value) { + FormatSpec &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return this->visit_any_int(value); + fmt_spec.type_ = 0; + this->write(value); + } + + void visit_char(int value) { + const FormatSpec &fmt_spec = this->spec(); + BasicWriter &w = this->writer(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + w.write_int(value, fmt_spec); + typedef typename BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (fmt_spec.width_ > 1) { + CharT fill = ' '; + out = w.grow_buffer(fmt_spec.width_); + if (fmt_spec.align_ != ALIGN_LEFT) { + std::fill_n(out, fmt_spec.width_ - 1, fill); + out += fmt_spec.width_ - 1; + } else { + std::fill_n(out + 1, fmt_spec.width_ - 1, fill); + } + } else { + out = w.grow_buffer(1); + } + *out = static_cast(value); + } + + typedef ArgFormatterBase Base; + void visit_cstring(const char *value) { + if (value) + Base::visit_cstring(value); + else if (this->spec().type_ == 'p') + write_null_pointer(); + else + this->write("(null)"); + } + + void visit_pointer(const void *value) { + if (value) + return Base::visit_pointer(value); + this->spec().type_ = 0; + write_null_pointer(); + } + + void visit_custom(Arg::CustomValue c) { + BasicFormatter formatter(ArgList(), this->writer()); + const CharT format_str[] = {'}', 0}; + const CharT *format = format_str; + c.format(&formatter, c.value, &format); + } +}; + +/** The default printf argument formatter. */ +template +class DefaultPrintfArgFormatter + : public PrintfArgFormatter, CharT> { +public: + /** Constructs an argument formatter object. */ + DefaultPrintfArgFormatter(BasicWriter &w, FormatSpec &spec) + : PrintfArgFormatter, CharT>(w, spec) {} +}; + + template + class PrintfFormatter : private FormatterBase { + private: + void parse_flags(FormatSpec &spec, const Char *&s); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + Arg get_arg(const Char *s, + unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(const Char *&s, FormatSpec &spec); + +public: + explicit PrintfFormatter(const ArgList &args) : FormatterBase(args) {} + FMT_API void format(BasicWriter &writer, + BasicCStringRef format_str); +}; + + +} + +template +inline void printf(BasicWriter &w, BasicCStringRef format, ArgList args) { + internal::PrintfFormatter(args).format(w, format); +} + + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +inline std::string sprintf(CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + return w.str(); +} + +inline std::wstring sprintf(WCStringRef format, ArgList args) { + WMemoryWriter w; + printf(w, format, args); + return w.str(); +} + + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +inline FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +inline int printf(CStringRef format, ArgList args) { + return fprintf(stdout, format, args); +} + +FMT_VARIADIC(std::string, sprintf, CStringRef) +FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) +FMT_VARIADIC(int, printf, CStringRef) +FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) + +FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + std::size_t size = w.size(); + return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); +} + + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct IntChecker { + template + static bool fits_in_int(T value) { + unsigned max = INT_MAX; + return value <= max; + } + static bool fits_in_int(bool) { return true; } +}; + +template <> +struct IntChecker { + template + static bool fits_in_int(T value) { + return value >= INT_MIN && value <= INT_MAX; + } + static bool fits_in_int(int) { return true; } +}; + +const char RESET_COLOR[] = "\x1b[0m"; + +typedef void (*FormatFunc)(Writer &, int, StringRef); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +///GAS :take out of header! +inline int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); + + class StrError { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const StrError &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::Null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::Null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + StrError(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + strerror_r(0, 0, ""); // Suppress a warning about unused strerror_r. + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return StrError(error_code, buffer, buffer_size).run(); +} + +inline void format_error_code(Writer &out, int error_code, + StringRef message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential + // bad_alloc. + out.clear(); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) + out << message << SEP; + out << ERROR_STR << error_code; + assert(out.size() <= internal::INLINE_BUFFER_SIZE); +} + +inline void report_error(FormatFunc func, int error_code, + StringRef message) FMT_NOEXCEPT { + MemoryWriter full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} + +// IsZeroInt::visit(arg) returns true iff arg is a zero integer. +class IsZeroInt : public ArgVisitor { + public: + template + bool visit_any_int(T value) { return value == 0; } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class WidthHandler : public ArgVisitor { + private: + FormatSpec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); + + public: + explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} + + void report_unhandled_arg() { + FMT_THROW(FormatError("width is not integer")); + } + + template + unsigned visit_any_int(T value) { + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType width = static_cast(value); + if (internal::is_negative(value)) { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + if (width > INT_MAX) + FMT_THROW(FormatError("number is too big")); + return static_cast(width); + } +}; + +class PrecisionHandler : public ArgVisitor { + public: + void report_unhandled_arg() { + FMT_THROW(FormatError("precision is not integer")); + } + + template + int visit_any_int(T value) { + if (!IntChecker::is_signed>::fits_in_int(value)) + FMT_THROW(FormatError("number is too big")); + return static_cast(value); + } +}; + +template +struct is_same { + enum { value = 0 }; +}; + +template +struct is_same { + enum { value = 1 }; +}; + +// An argument visitor that converts an integer argument to T for printf, +// if T is an integral type. If T is void, the argument is converted to +// corresponding signed or unsigned type depending on the type specifier: +// 'd' and 'i' - signed, other - unsigned) +template +class ArgConverter : public ArgVisitor, void> { + private: + internal::Arg &arg_; + wchar_t type_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); + + public: + ArgConverter(internal::Arg &arg, wchar_t type) + : arg_(arg), type_(type) {} + + void visit_bool(bool value) { + if (type_ != 's') + visit_any_int(value); + } + + template + void visit_any_int(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using internal::Arg; + typedef typename internal::Conditional< + is_same::value, U, T>::type TargetType; + if (sizeof(TargetType) <= sizeof(int)) { + // Extra casts are used to silence warnings. + if (is_signed) { + arg_.type = Arg::INT; + arg_.int_value = static_cast(static_cast(value)); + } else { + arg_.type = Arg::UINT; + typedef typename internal::MakeUnsigned::Type Unsigned; + arg_.uint_value = static_cast(static_cast(value)); + } + } else { + if (is_signed) { + arg_.type = Arg::LONG_LONG; + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_.long_long_value = static_cast(value); + } else { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } + } + } +}; + +// Converts an integer argument to char for printf. +class CharConverter : public ArgVisitor { + private: + internal::Arg &arg_; + + FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); + + public: + explicit CharConverter(internal::Arg &arg) : arg_(arg) {} + + template + void visit_any_int(T value) { + arg_.type = internal::Arg::CHAR; + arg_.int_value = static_cast(value); + } +}; + +template +void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, BasicCStringRef format_str) { + const Char *start = format_str.c_str(); + const Char *s = start; + while (*s) { + Char c = *s++; + if (c != '%') continue; + if (*s == c) { + write(writer, start, s); + start = ++s; + continue; + } + write(writer, start, s - 1); + + FormatSpec spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(s, spec); + + // Parse precision. + if (*s == '.') { + ++s; + if ('0' <= *s && *s <= '9') { + spec.precision_ = static_cast(parse_nonnegative_int(s)); + } else if (*s == '*') { + ++s; + spec.precision_ = PrecisionHandler().visit(get_arg(s)); + } + } + + Arg arg = get_arg(s, arg_index); + if (spec.flag(HASH_FLAG) && IsZeroInt().visit(arg)) + spec.flags_ &= ~to_unsigned(HASH_FLAG); + if (spec.fill_ == '0') { + if (arg.type <= Arg::LAST_NUMERIC_TYPE) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + switch (*s++) { + case 'h': + if (*s == 'h') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'l': + if (*s == 'l') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'j': + ArgConverter(arg, *s).visit(arg); + break; + case 'z': + ArgConverter(arg, *s).visit(arg); + break; + case 't': + ArgConverter(arg, *s).visit(arg); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --s; + ArgConverter(arg, *s).visit(arg); + } + + // Parse type. + if (!*s) + FMT_THROW(FormatError("invalid format string")); + spec.type_ = static_cast(*s++); + if (arg.type <= Arg::LAST_INTEGER_TYPE) { + // Normalize type. + switch (spec.type_) { + case 'i': case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t + CharConverter(arg).visit(arg); + break; + } + } + + start = s; + + // Format argument. + PAF(writer, spec).visit(arg); + } + write(writer, start, s); +} + +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, CStringRef format); + +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, WCStringRef format); + +} + +template +void fmt::internal::PrintfFormatter::parse_flags( + FormatSpec &spec, const Char *&s) { + for (;;) { + switch (*s++) { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --s; + return; + } + } +} + +template +Arg fmt::internal::PrintfFormatter::get_arg( + const Char *s, unsigned arg_index) { + (void)s; + const char *error = 0; + Arg arg = arg_index == UINT_MAX ? + next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); + if (error) + FMT_THROW(FormatError(!*s ? "invalid format string" : error)); + return arg; +} + +template +unsigned fmt::internal::PrintfFormatter::parse_header( + const Char *&s, FormatSpec &spec) { + unsigned arg_index = UINT_MAX; + Char c = *s; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + unsigned value = parse_nonnegative_int(s); + if (*s == '$') { // value is an argument index + ++s; + arg_index = value; + } else { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, s); + // Parse width. + if (*s >= '0' && *s <= '9') { + spec.width_ = parse_nonnegative_int(s); + } else if (*s == '*') { + ++s; + spec.width_ = WidthHandler(spec).visit(get_arg(s)); + } + return arg_index; +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8758e47ef624..2371ae2140eb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -80,6 +80,7 @@ add_fmt_test(printf-test) add_fmt_test(string-test) add_fmt_test(util-test mock-allocator.h) add_fmt_test(macro-test) +add_fmt_test(custom-formatter-test) # Enable stricter options for one test to make sure that the header is free of # warnings. @@ -129,7 +130,7 @@ if (FMT_PEDANTIC) "${CMAKE_CURRENT_BINARY_DIR}/compile-test" --build-generator ${CMAKE_GENERATOR} --build-makeprogram ${CMAKE_MAKE_PROGRAM} - --build-options + --build-options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCPP11_FLAG=${CPP11_FLAG}" "-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}") diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc new file mode 100644 index 000000000000..e3fc83a034f6 --- /dev/null +++ b/test/custom-formatter-test.cc @@ -0,0 +1,98 @@ +/* + 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 "fmt/format.h" +#include "fmt/printf.h" +#include "gtest-extra.h" + +#include + + +// A custom argument formatter that doesn't print `-` for floating-point values +// rounded to 0. +class CustomArgFormatter : + public fmt::BasicArgFormatter { + public: + CustomArgFormatter(fmt::BasicFormatter &f, + fmt::FormatSpec &s, const char *fmt) + : fmt::BasicArgFormatter(f, s, fmt) {} + + void visit_double(double value) { + if (round(value * pow(10, spec().precision())) == 0) + value = 0; + fmt::BasicArgFormatter::visit_double(value); + } +}; + + +// A custom argument formatter that doesn't print `-` for floating-point values +// rounded to 0. +class CustomPAF : public fmt::internal::PrintfArgFormatter +{ +public: + CustomPAF(fmt::BasicWriter &writer, fmt::FormatSpec &spec): + fmt::internal::PrintfArgFormatter(writer, spec) {} + + void visit_double(double value) { + if (round(value * pow(10, spec().precision())) == 0) + value = 0; + fmt::internal::PrintfArgFormatter< CustomPAF, char>::visit_double(value); + } +}; + +std::string custom_format(const char *format_str, fmt::ArgList args) { + fmt::MemoryWriter writer; + // Pass custom argument formatter as a template arg to BasicFormatter. + fmt::BasicFormatter formatter(args, writer); + formatter.format(format_str); + return writer.str(); +} +FMT_VARIADIC(std::string, custom_format, const char *) + + +std::string printfer(const char* fstr, fmt::ArgList args){ + fmt::MemoryWriter writer; + fmt::internal::PrintfFormatter< char, CustomPAF > pfer( args); + pfer.format(writer, fstr); + return writer.str(); +} +FMT_VARIADIC(std::string, printfer, const char*); + + + +// Makes format string argument positional. +std::string make_positional(fmt::StringRef format) { + std::string s(format.to_string()); + s.replace(s.find('%'), 1, "%1$"); + return s; +} + + +TEST(custom, foo){ + EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001)); + EXPECT_EQ("0.00", printfer("%.2f", -.00001)); +} diff --git a/test/printf-test.cc b/test/printf-test.cc index c38a1e52faa1..06d5f383cee8 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -29,6 +29,7 @@ #include #include +#include "fmt/printf.h" #include "fmt/format.h" #include "gtest-extra.h" #include "util.h" From d25b78aa2cfe8892fb3d985cf0740bb90fc1b625 Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Wed, 8 Jun 2016 01:24:53 +0200 Subject: [PATCH 2/6] Fixed clang errors --- fmt/format.cc | 25 ------------------------- fmt/format.h | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/fmt/format.cc b/fmt/format.cc index 0efcbb6f2139..5fbfd5edc123 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -99,31 +99,6 @@ const char fmt::internal::BasicData::DIGITS[] = "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; -#define FMT_POWERS_OF_10(factor) \ - factor * 10, \ - factor * 100, \ - factor * 1000, \ - factor * 10000, \ - factor * 100000, \ - factor * 1000000, \ - factor * 10000000, \ - factor * 100000000, \ - factor * 1000000000 - -template -const uint32_t fmt::internal::BasicData::POWERS_OF_10_32[] = { - 0, FMT_POWERS_OF_10(1) -}; - -template -const uint64_t fmt::internal::BasicData::POWERS_OF_10_64[] = { - 0, - FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(fmt::ULongLong(1000000000)), - // Multiply several constants instead of using a single long long constant - // to avoid warnings about C++98 not supporting long long. - fmt::ULongLong(1000000000) * fmt::ULongLong(1000000000) * 10 -}; FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) { (void)type; diff --git a/fmt/format.h b/fmt/format.h index bf71e2daab47..6a4f60999fa6 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -836,15 +836,45 @@ struct FMT_API BasicData { static const char DIGITS[]; }; -#ifndef FMT_USE_EXTERN_TEMPLATES -// Clang doesn't have a feature check for extern templates so we check -// for variadic templates which were introduced in the same version. -# define FMT_USE_EXTERN_TEMPLATES (__clang__ && FMT_USE_VARIADIC_TEMPLATES) -#endif +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 -#if FMT_USE_EXTERN_TEMPLATES -extern template struct BasicData; -#endif +template +const uint32_t BasicData::POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template +const uint64_t fmt::internal::BasicData::POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(fmt::ULongLong(1000000000)), + // Multiply several constants instead of using a single long long constant + // to avoid warnings about C++98 not supporting long long. + fmt::ULongLong(1000000000) * fmt::ULongLong(1000000000) * 10 +}; + + + +/// After the changes for 335, cland reported unedefined references to +/// BasicData, so I put their definitions in the header. +// #ifndef FMT_USE_EXTERN_TEMPLATES +// // Clang doesn't have a feature check for extern templates so we check +// // for variadic templates which were introduced in the same version. +// # define FMT_USE_EXTERN_TEMPLATES (__clang__ && FMT_USE_VARIADIC_TEMPLATES) +// #endif + +// #if FMT_USE_EXTERN_TEMPLATES +// extern template struct BasicData; +// #endif typedef BasicData<> Data; From 930cd6c5527201f9fcfa65ed83538c2035af4467 Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Wed, 8 Jun 2016 09:45:15 +0200 Subject: [PATCH 3/6] Fixed one build error for windows --- fmt/format.cc | 2 +- fmt/printf.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fmt/format.cc b/fmt/format.cc index 5fbfd5edc123..a22a3a4575f1 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -33,7 +33,7 @@ #include #include -#include + #include // for std::ptrdiff_t diff --git a/fmt/printf.h b/fmt/printf.h index 897fb8f53987..ce323aeef831 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -33,6 +33,7 @@ #include #include +#include //< Only for windows? #if defined(_WIN32) && defined(__MINGW32__) From caaf17d0ca0f3043374312cfbb5b542642d080cd Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Wed, 8 Jun 2016 09:53:02 +0200 Subject: [PATCH 4/6] don't expect this to work, but... --- fmt/printf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fmt/printf.h b/fmt/printf.h index ce323aeef831..2749a88bfd98 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -83,8 +83,8 @@ namespace internal { template class DefaultPrintfArgFormatter; -template > +template > class PrintfFormatter; template From 60b8b0bca496b21b047771cc23cb37ca788862c3 Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Wed, 8 Jun 2016 12:00:05 +0200 Subject: [PATCH 5/6] fixed windows build problem --- fmt/printf.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fmt/printf.h b/fmt/printf.h index 2749a88bfd98..3ef410371e7c 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -81,11 +81,11 @@ static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { namespace fmt { namespace internal { -template class DefaultPrintfArgFormatter; +// template class DefaultPrintfArgFormatter; -template > -class PrintfFormatter; +// template > +// class PrintfFormatter; template class PrintfArgFormatter : public internal::ArgFormatterBase { @@ -165,8 +165,9 @@ class DefaultPrintfArgFormatter : PrintfArgFormatter, CharT>(w, spec) {} }; - template - class PrintfFormatter : private FormatterBase { + template > + class PrintfFormatter : private FormatterBase { private: void parse_flags(FormatSpec &spec, const Char *&s); From c188cfe298f5d1f229d77004bcde4c5ed9a33b0d Mon Sep 17 00:00:00 2001 From: Glen Stark Date: Thu, 9 Jun 2016 13:25:35 +0200 Subject: [PATCH 6/6] Corrections according to feedback --- fmt/format.cc | 10 +--------- fmt/format.h | 2 +- fmt/printf.h | 8 +------- test/custom-formatter-test.cc | 12 ------------ 4 files changed, 3 insertions(+), 29 deletions(-) diff --git a/fmt/format.cc b/fmt/format.cc index a22a3a4575f1..80c7b4e7ed3d 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -26,7 +26,7 @@ */ #include "fmt/format.h" -#include "printf.h" +#include "fmt/printf.h" #include @@ -38,8 +38,6 @@ namespace fmt { - - namespace internal { // This method is used to preserve binary compatibility with fmt 3.0. @@ -313,10 +311,6 @@ FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) { } - - - - #ifndef FMT_HEADER_ONLY template struct fmt::internal::BasicData; @@ -327,7 +321,6 @@ template void fmt::internal::FixedBuffer::grow(std::size_t); template void fmt::internal::ArgMap::init(const fmt::ArgList &args); - template int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, double value); @@ -342,7 +335,6 @@ template void fmt::internal::FixedBuffer::grow(std::size_t); template void fmt::internal::ArgMap::init(const fmt::ArgList &args); - template int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, double value); diff --git a/fmt/format.h b/fmt/format.h index 6a4f60999fa6..0fc275a4583c 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -1987,7 +1987,7 @@ class FormatterBase { */ template class BasicArgFormatter : public internal::ArgFormatterBase { -private: + private: BasicFormatter &formatter_; const Char *format_; diff --git a/fmt/printf.h b/fmt/printf.h index 3ef410371e7c..8c75728cedad 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -33,7 +33,7 @@ #include #include -#include //< Only for windows? +#include #if defined(_WIN32) && defined(__MINGW32__) @@ -81,12 +81,6 @@ static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { namespace fmt { namespace internal { -// template class DefaultPrintfArgFormatter; - -// template > -// class PrintfFormatter; - template class PrintfArgFormatter : public internal::ArgFormatterBase { diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc index e3fc83a034f6..e8f874903b6e 100644 --- a/test/custom-formatter-test.cc +++ b/test/custom-formatter-test.cc @@ -29,9 +29,6 @@ #include "fmt/printf.h" #include "gtest-extra.h" -#include - - // A custom argument formatter that doesn't print `-` for floating-point values // rounded to 0. class CustomArgFormatter : @@ -83,15 +80,6 @@ std::string printfer(const char* fstr, fmt::ArgList args){ FMT_VARIADIC(std::string, printfer, const char*); - -// Makes format string argument positional. -std::string make_positional(fmt::StringRef format) { - std::string s(format.to_string()); - s.replace(s.find('%'), 1, "%1$"); - return s; -} - - TEST(custom, foo){ EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001)); EXPECT_EQ("0.00", printfer("%.2f", -.00001));