mirror of
https://github.com/fmtlib/fmt.git
synced 2024-12-24 03:17:53 +00:00
Add support for custom memory allocator to BasicWriter
This commit is contained in:
parent
70205edd6e
commit
b9a568b1dd
223
format.cc
223
format.cc
@ -62,42 +62,8 @@ using fmt::internal::Arg;
|
||||
namespace {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
|
||||
// Portable version of signbit.
|
||||
// When compiled in C++11 mode signbit is no longer a macro but a function
|
||||
// defined in namespace std and the macro is undefined.
|
||||
inline int getsign(double x) {
|
||||
#ifdef signbit
|
||||
return signbit(x);
|
||||
#else
|
||||
return std::signbit(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Portable version of isinf.
|
||||
#ifdef isinf
|
||||
inline int isinfinity(double x) { return isinf(x); }
|
||||
inline int isinfinity(long double x) { return isinf(x); }
|
||||
#else
|
||||
inline int isinfinity(double x) { return std::isinf(x); }
|
||||
inline int isinfinity(long double x) { return std::isinf(x); }
|
||||
#endif
|
||||
|
||||
#define FMT_SNPRINTF snprintf
|
||||
|
||||
# define FMT_SNPRINTF snprintf
|
||||
#else // _MSC_VER
|
||||
|
||||
inline int getsign(double value) {
|
||||
if (value < 0) return 1;
|
||||
if (value == value) return 0;
|
||||
int dec = 0, sign = 0;
|
||||
char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail.
|
||||
_ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign);
|
||||
return sign;
|
||||
}
|
||||
|
||||
inline int isinfinity(double x) { return !_finite(x); }
|
||||
|
||||
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
@ -105,16 +71,9 @@ inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
#define FMT_SNPRINTF fmt_snprintf
|
||||
|
||||
# define FMT_SNPRINTF fmt_snprintf
|
||||
#endif // _MSC_VER
|
||||
|
||||
template <typename T>
|
||||
struct IsLongDouble { enum {VALUE = 0}; };
|
||||
|
||||
template <>
|
||||
struct IsLongDouble<long double> { enum {VALUE = 1}; };
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned>
|
||||
@ -600,175 +559,9 @@ class fmt::internal::ArgFormatter :
|
||||
}
|
||||
};
|
||||
|
||||
// Fills the padding around the content and returns the pointer to the
|
||||
// content area.
|
||||
template <typename Char>
|
||||
typename fmt::BasicWriter<Char>::CharPtr
|
||||
fmt::BasicWriter<Char>::fill_padding(CharPtr buffer,
|
||||
unsigned total_size, std::size_t content_size, wchar_t fill) {
|
||||
std::size_t padding = total_size - content_size;
|
||||
std::size_t left_padding = padding / 2;
|
||||
Char fill_char = static_cast<Char>(fill);
|
||||
std::fill_n(buffer, left_padding, fill_char);
|
||||
buffer += left_padding;
|
||||
CharPtr content = buffer;
|
||||
std::fill_n(buffer + content_size, padding - left_padding, fill_char);
|
||||
return content;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
template <typename T>
|
||||
void fmt::BasicWriter<Char>::write_double(T value, const FormatSpec &spec) {
|
||||
// Check type.
|
||||
char type = spec.type();
|
||||
bool upper = false;
|
||||
switch (type) {
|
||||
case 0:
|
||||
type = 'g';
|
||||
break;
|
||||
case 'e': case 'f': case 'g': case 'a':
|
||||
break;
|
||||
case 'F':
|
||||
#ifdef _MSC_VER
|
||||
// MSVC's printf doesn't support 'F'.
|
||||
type = 'f';
|
||||
#endif
|
||||
// Fall through.
|
||||
case 'E': case 'G': case 'A':
|
||||
upper = true;
|
||||
break;
|
||||
default:
|
||||
internal::report_unknown_type(type, "double");
|
||||
break;
|
||||
}
|
||||
|
||||
char sign = 0;
|
||||
// Use getsign instead of value < 0 because the latter is always
|
||||
// false for NaN.
|
||||
if (getsign(static_cast<double>(value))) {
|
||||
sign = '-';
|
||||
value = -value;
|
||||
} else if (spec.flag(SIGN_FLAG)) {
|
||||
sign = spec.flag(PLUS_FLAG) ? '+' : ' ';
|
||||
}
|
||||
|
||||
if (value != value) {
|
||||
// Format NaN ourselves because sprintf's output is not consistent
|
||||
// across platforms.
|
||||
std::size_t size = 4;
|
||||
const char *nan = upper ? " NAN" : " nan";
|
||||
if (!sign) {
|
||||
--size;
|
||||
++nan;
|
||||
}
|
||||
CharPtr out = write_str(nan, size, spec);
|
||||
if (sign)
|
||||
*out = sign;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isinfinity(value)) {
|
||||
// Format infinity ourselves because sprintf's output is not consistent
|
||||
// across platforms.
|
||||
std::size_t size = 4;
|
||||
const char *inf = upper ? " INF" : " inf";
|
||||
if (!sign) {
|
||||
--size;
|
||||
++inf;
|
||||
}
|
||||
CharPtr out = write_str(inf, size, spec);
|
||||
if (sign)
|
||||
*out = sign;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t offset = buffer_.size();
|
||||
unsigned width = spec.width();
|
||||
if (sign) {
|
||||
buffer_.reserve(buffer_.size() + (std::max)(width, 1u));
|
||||
if (width > 0)
|
||||
--width;
|
||||
++offset;
|
||||
}
|
||||
|
||||
// Build format string.
|
||||
enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg
|
||||
Char format[MAX_FORMAT_SIZE];
|
||||
Char *format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
unsigned width_for_sprintf = width;
|
||||
if (spec.flag(HASH_FLAG))
|
||||
*format_ptr++ = '#';
|
||||
if (spec.align() == ALIGN_CENTER) {
|
||||
width_for_sprintf = 0;
|
||||
} else {
|
||||
if (spec.align() == ALIGN_LEFT)
|
||||
*format_ptr++ = '-';
|
||||
if (width != 0)
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (spec.precision() >= 0) {
|
||||
*format_ptr++ = '.';
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (IsLongDouble<T>::VALUE)
|
||||
*format_ptr++ = 'L';
|
||||
*format_ptr++ = type;
|
||||
*format_ptr = '\0';
|
||||
|
||||
// Format using snprintf.
|
||||
Char fill = static_cast<Char>(spec.fill());
|
||||
for (;;) {
|
||||
std::size_t size = buffer_.capacity() - offset;
|
||||
#if _MSC_VER
|
||||
// MSVC's vsnprintf_s doesn't work with zero size, so reserve
|
||||
// space for at least one extra character to make the size non-zero.
|
||||
// Note that the buffer's capacity will increase by more than 1.
|
||||
if (size == 0) {
|
||||
buffer_.reserve(offset + 1);
|
||||
size = buffer_.capacity() - offset;
|
||||
}
|
||||
#endif
|
||||
Char *start = &buffer_[offset];
|
||||
int n = internal::CharTraits<Char>::format_float(
|
||||
start, size, format, width_for_sprintf, spec.precision(), value);
|
||||
if (n >= 0 && offset + n < buffer_.capacity()) {
|
||||
if (sign) {
|
||||
if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||
|
||||
*start != ' ') {
|
||||
*(start - 1) = sign;
|
||||
sign = 0;
|
||||
} else {
|
||||
*(start - 1) = fill;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
if (spec.align() == ALIGN_CENTER &&
|
||||
spec.width() > static_cast<unsigned>(n)) {
|
||||
unsigned width = spec.width();
|
||||
CharPtr p = grow_buffer(width);
|
||||
std::copy(p, p + n, p + (width - n) / 2);
|
||||
fill_padding(p, spec.width(), n, fill);
|
||||
return;
|
||||
}
|
||||
if (spec.fill() != ' ' || sign) {
|
||||
while (*start == ' ')
|
||||
*start++ = fill;
|
||||
if (sign)
|
||||
*(start - 1) = sign;
|
||||
}
|
||||
grow_buffer(n);
|
||||
return;
|
||||
}
|
||||
// If n is negative we ask to increase the capacity by at least 1,
|
||||
// but as std::vector, the buffer grows exponentially.
|
||||
buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator>
|
||||
template <typename StrChar>
|
||||
void fmt::BasicWriter<Char>::write_str(
|
||||
void fmt::BasicWriter<Char, Allocator>::write_str(
|
||||
const Arg::StringValue<StrChar> &str, const FormatSpec &spec) {
|
||||
// Check if StrChar is convertible to Char.
|
||||
internal::CharTraits<Char>::convert(StrChar());
|
||||
@ -1259,10 +1052,6 @@ int fmt::fprintf(std::FILE *f, StringRef format, const ArgList &args) {
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template fmt::BasicWriter<char>::CharPtr
|
||||
fmt::BasicWriter<char>::fill_padding(CharPtr buffer,
|
||||
unsigned total_size, std::size_t content_size, wchar_t fill);
|
||||
|
||||
template void fmt::BasicFormatter<char>::format(
|
||||
BasicStringRef<char> format, const ArgList &args);
|
||||
|
||||
@ -1271,10 +1060,6 @@ template void fmt::internal::PrintfFormatter<char>::format(
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template fmt::BasicWriter<wchar_t>::CharPtr
|
||||
fmt::BasicWriter<wchar_t>::fill_padding(CharPtr buffer,
|
||||
unsigned total_size, std::size_t content_size, wchar_t fill);
|
||||
|
||||
template void fmt::BasicFormatter<wchar_t>::format(
|
||||
BasicStringRef<wchar_t> format, const ArgList &args);
|
||||
|
||||
|
268
format.h
268
format.h
@ -31,6 +31,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef> // for std::ptrdiff_t
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
@ -122,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong;
|
||||
using std::move;
|
||||
#endif
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator = std::allocator<Char> >
|
||||
class BasicWriter;
|
||||
|
||||
typedef BasicWriter<char> Writer;
|
||||
@ -253,8 +254,19 @@ class Array : private Allocator {
|
||||
if (ptr_ != data_) this->deallocate(ptr_, capacity_);
|
||||
}
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(Array);
|
||||
|
||||
public:
|
||||
explicit Array(const Allocator &alloc = Allocator())
|
||||
: Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {}
|
||||
~Array() { free(); }
|
||||
|
||||
#if FMT_USE_RVALUE_REFERENCES
|
||||
private:
|
||||
// Move data from other to this array.
|
||||
void move(Array &other) {
|
||||
Allocator &this_alloc = *this, &other_alloc = other;
|
||||
this_alloc = std::move(other_alloc);
|
||||
size_ = other.size_;
|
||||
capacity_ = other.capacity_;
|
||||
if (other.ptr_ == other.data_) {
|
||||
@ -268,14 +280,7 @@ class Array : private Allocator {
|
||||
}
|
||||
}
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(Array);
|
||||
|
||||
public:
|
||||
explicit Array(const Allocator &alloc = Allocator())
|
||||
: Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {}
|
||||
~Array() { free(); }
|
||||
|
||||
#if FMT_USE_RVALUE_REFERENCES
|
||||
Array(Array &&other) {
|
||||
move(other);
|
||||
}
|
||||
@ -351,6 +356,44 @@ void Array<T, SIZE, Allocator>::append(const T *begin, const T *end) {
|
||||
size_ += num_elements;
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// Portable version of signbit.
|
||||
// When compiled in C++11 mode signbit is no longer a macro but a function
|
||||
// defined in namespace std and the macro is undefined.
|
||||
inline int getsign(double x) {
|
||||
# ifdef signbit
|
||||
return signbit(x);
|
||||
# else
|
||||
return std::signbit(x);
|
||||
# endif
|
||||
}
|
||||
|
||||
// Portable version of isinf.
|
||||
# ifdef isinf
|
||||
inline int isinfinity(double x) { return isinf(x); }
|
||||
inline int isinfinity(long double x) { return isinf(x); }
|
||||
# else
|
||||
inline int isinfinity(double x) { return std::isinf(x); }
|
||||
inline int isinfinity(long double x) { return std::isinf(x); }
|
||||
# endif
|
||||
#else
|
||||
inline int getsign(double value) {
|
||||
if (value < 0) return 1;
|
||||
if (value == value) return 0;
|
||||
int dec = 0, sign = 0;
|
||||
char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail.
|
||||
_ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign);
|
||||
return sign;
|
||||
}
|
||||
inline int isinfinity(double x) { return !_finite(x); }
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct IsLongDouble { enum {VALUE = 0}; };
|
||||
|
||||
template <>
|
||||
struct IsLongDouble<long double> { enum {VALUE = 1}; };
|
||||
|
||||
template <typename Char>
|
||||
class BasicCharTraits {
|
||||
public:
|
||||
@ -1274,15 +1317,16 @@ class SystemError : public internal::RuntimeError {
|
||||
a character stream. The output is stored in a memory buffer that grows
|
||||
dynamically.
|
||||
|
||||
You can use one of the following typedefs for common character types:
|
||||
You can use one of the following typedefs for common character types
|
||||
and the standard allocator:
|
||||
|
||||
+---------+----------------------+
|
||||
| Type | Definition |
|
||||
+=========+======================+
|
||||
| Writer | BasicWriter<char> |
|
||||
+---------+----------------------+
|
||||
| WWriter | BasicWriter<wchar_t> |
|
||||
+---------+----------------------+
|
||||
+---------+-----------------------------------------------+
|
||||
| Type | Definition |
|
||||
+=========+===============================================+
|
||||
| Writer | BasicWriter<char, std::allocator<char>> |
|
||||
+---------+-----------------------------------------------+
|
||||
| WWriter | BasicWriter<wchar_t, std::allocator<wchar_t>> |
|
||||
+---------+-----------------------------------------------+
|
||||
|
||||
**Example**::
|
||||
|
||||
@ -1301,11 +1345,12 @@ class SystemError : public internal::RuntimeError {
|
||||
accessed as a C string with ``out.c_str()``.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator>
|
||||
class BasicWriter {
|
||||
private:
|
||||
// Output buffer.
|
||||
mutable internal::Array<Char, internal::INLINE_BUFFER_SIZE> buffer_;
|
||||
typedef internal::Array<Char, internal::INLINE_BUFFER_SIZE, Allocator> Array;
|
||||
mutable Array buffer_;
|
||||
|
||||
typedef typename internal::CharTraits<Char>::CharPtr CharPtr;
|
||||
|
||||
@ -1316,6 +1361,8 @@ class BasicWriter {
|
||||
static Char *get(Char *p) { return p; }
|
||||
#endif
|
||||
|
||||
// Fills the padding around the content and returns the pointer to the
|
||||
// content area.
|
||||
static CharPtr fill_padding(CharPtr buffer,
|
||||
unsigned total_size, std::size_t content_size, wchar_t fill);
|
||||
|
||||
@ -1370,7 +1417,7 @@ class BasicWriter {
|
||||
/**
|
||||
Constructs a ``BasicWriter`` object.
|
||||
*/
|
||||
BasicWriter() {}
|
||||
BasicWriter(const Allocator &alloc = Allocator()) : buffer_(alloc) {}
|
||||
|
||||
#if FMT_USE_RVALUE_REFERENCES
|
||||
/**
|
||||
@ -1446,7 +1493,7 @@ class BasicWriter {
|
||||
void write(BasicStringRef<Char> format, const ArgList &args) {
|
||||
BasicFormatter<Char>(*this).format(format, args);
|
||||
}
|
||||
FMT_VARIADIC_VOID(write, fmt::BasicStringRef<Char>)
|
||||
FMT_VARIADIC_VOID(write, BasicStringRef<Char>)
|
||||
|
||||
BasicWriter &operator<<(int value) {
|
||||
return *this << IntFormatSpec<int>(value);
|
||||
@ -1525,9 +1572,10 @@ class BasicWriter {
|
||||
void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator>
|
||||
template <typename StrChar>
|
||||
typename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str(
|
||||
typename BasicWriter<Char, Allocator>::CharPtr
|
||||
BasicWriter<Char, Allocator>::write_str(
|
||||
const StrChar *s, std::size_t size, const AlignSpec &spec) {
|
||||
CharPtr out = CharPtr();
|
||||
if (spec.width() > size) {
|
||||
@ -1548,10 +1596,25 @@ typename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str(
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator>
|
||||
typename BasicWriter<Char, Allocator>::CharPtr
|
||||
BasicWriter<Char, Allocator>::fill_padding(
|
||||
CharPtr buffer, unsigned total_size,
|
||||
std::size_t content_size, wchar_t fill) {
|
||||
std::size_t padding = total_size - content_size;
|
||||
std::size_t left_padding = padding / 2;
|
||||
Char fill_char = static_cast<Char>(fill);
|
||||
std::fill_n(buffer, left_padding, fill_char);
|
||||
buffer += left_padding;
|
||||
CharPtr content = buffer;
|
||||
std::fill_n(buffer + content_size, padding - left_padding, fill_char);
|
||||
return content;
|
||||
}
|
||||
|
||||
template <typename Char, typename Allocator>
|
||||
template <typename Spec>
|
||||
typename fmt::BasicWriter<Char>::CharPtr
|
||||
fmt::BasicWriter<Char>::prepare_int_buffer(
|
||||
typename BasicWriter<Char, Allocator>::CharPtr
|
||||
BasicWriter<Char, Allocator>::prepare_int_buffer(
|
||||
unsigned num_digits, const Spec &spec,
|
||||
const char *prefix, unsigned prefix_size) {
|
||||
unsigned width = spec.width();
|
||||
@ -1611,9 +1674,9 @@ typename fmt::BasicWriter<Char>::CharPtr
|
||||
return p - 1;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename Allocator>
|
||||
template <typename T, typename Spec>
|
||||
void BasicWriter<Char>::write_int(T value, const Spec &spec) {
|
||||
void BasicWriter<Char, Allocator>::write_int(T value, const Spec &spec) {
|
||||
unsigned prefix_size = 0;
|
||||
typedef typename internal::IntTraits<T>::MainType UnsignedType;
|
||||
UnsignedType abs_value = value;
|
||||
@ -1693,6 +1756,157 @@ void BasicWriter<Char>::write_int(T value, const Spec &spec) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename Allocator>
|
||||
template <typename T>
|
||||
void BasicWriter<Char, Allocator>::write_double(
|
||||
T value, const FormatSpec &spec) {
|
||||
// Check type.
|
||||
char type = spec.type();
|
||||
bool upper = false;
|
||||
switch (type) {
|
||||
case 0:
|
||||
type = 'g';
|
||||
break;
|
||||
case 'e': case 'f': case 'g': case 'a':
|
||||
break;
|
||||
case 'F':
|
||||
#ifdef _MSC_VER
|
||||
// MSVC's printf doesn't support 'F'.
|
||||
type = 'f';
|
||||
#endif
|
||||
// Fall through.
|
||||
case 'E': case 'G': case 'A':
|
||||
upper = true;
|
||||
break;
|
||||
default:
|
||||
internal::report_unknown_type(type, "double");
|
||||
break;
|
||||
}
|
||||
|
||||
char sign = 0;
|
||||
// Use getsign instead of value < 0 because the latter is always
|
||||
// false for NaN.
|
||||
if (internal::getsign(static_cast<double>(value))) {
|
||||
sign = '-';
|
||||
value = -value;
|
||||
} else if (spec.flag(SIGN_FLAG)) {
|
||||
sign = spec.flag(PLUS_FLAG) ? '+' : ' ';
|
||||
}
|
||||
|
||||
if (value != value) {
|
||||
// Format NaN ourselves because sprintf's output is not consistent
|
||||
// across platforms.
|
||||
std::size_t size = 4;
|
||||
const char *nan = upper ? " NAN" : " nan";
|
||||
if (!sign) {
|
||||
--size;
|
||||
++nan;
|
||||
}
|
||||
CharPtr out = write_str(nan, size, spec);
|
||||
if (sign)
|
||||
*out = sign;
|
||||
return;
|
||||
}
|
||||
|
||||
if (internal::isinfinity(value)) {
|
||||
// Format infinity ourselves because sprintf's output is not consistent
|
||||
// across platforms.
|
||||
std::size_t size = 4;
|
||||
const char *inf = upper ? " INF" : " inf";
|
||||
if (!sign) {
|
||||
--size;
|
||||
++inf;
|
||||
}
|
||||
CharPtr out = write_str(inf, size, spec);
|
||||
if (sign)
|
||||
*out = sign;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t offset = buffer_.size();
|
||||
unsigned width = spec.width();
|
||||
if (sign) {
|
||||
buffer_.reserve(buffer_.size() + (std::max)(width, 1u));
|
||||
if (width > 0)
|
||||
--width;
|
||||
++offset;
|
||||
}
|
||||
|
||||
// Build format string.
|
||||
enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg
|
||||
Char format[MAX_FORMAT_SIZE];
|
||||
Char *format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
unsigned width_for_sprintf = width;
|
||||
if (spec.flag(HASH_FLAG))
|
||||
*format_ptr++ = '#';
|
||||
if (spec.align() == ALIGN_CENTER) {
|
||||
width_for_sprintf = 0;
|
||||
} else {
|
||||
if (spec.align() == ALIGN_LEFT)
|
||||
*format_ptr++ = '-';
|
||||
if (width != 0)
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (spec.precision() >= 0) {
|
||||
*format_ptr++ = '.';
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (internal::IsLongDouble<T>::VALUE)
|
||||
*format_ptr++ = 'L';
|
||||
*format_ptr++ = type;
|
||||
*format_ptr = '\0';
|
||||
|
||||
// Format using snprintf.
|
||||
Char fill = static_cast<Char>(spec.fill());
|
||||
for (;;) {
|
||||
std::size_t size = buffer_.capacity() - offset;
|
||||
#if _MSC_VER
|
||||
// MSVC's vsnprintf_s doesn't work with zero size, so reserve
|
||||
// space for at least one extra character to make the size non-zero.
|
||||
// Note that the buffer's capacity will increase by more than 1.
|
||||
if (size == 0) {
|
||||
buffer_.reserve(offset + 1);
|
||||
size = buffer_.capacity() - offset;
|
||||
}
|
||||
#endif
|
||||
Char *start = &buffer_[offset];
|
||||
int n = internal::CharTraits<Char>::format_float(
|
||||
start, size, format, width_for_sprintf, spec.precision(), value);
|
||||
if (n >= 0 && offset + n < buffer_.capacity()) {
|
||||
if (sign) {
|
||||
if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||
|
||||
*start != ' ') {
|
||||
*(start - 1) = sign;
|
||||
sign = 0;
|
||||
} else {
|
||||
*(start - 1) = fill;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
if (spec.align() == ALIGN_CENTER &&
|
||||
spec.width() > static_cast<unsigned>(n)) {
|
||||
unsigned width = spec.width();
|
||||
CharPtr p = grow_buffer(width);
|
||||
std::copy(p, p + n, p + (width - n) / 2);
|
||||
fill_padding(p, spec.width(), n, fill);
|
||||
return;
|
||||
}
|
||||
if (spec.fill() != ' ' || sign) {
|
||||
while (*start == ' ')
|
||||
*start++ = fill;
|
||||
if (sign)
|
||||
*(start - 1) = sign;
|
||||
}
|
||||
grow_buffer(n);
|
||||
return;
|
||||
}
|
||||
// If n is negative we ask to increase the capacity by at least 1,
|
||||
// but as std::vector, the buffer grows exponentially.
|
||||
buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Formats a value.
|
||||
template <typename Char, typename T>
|
||||
void format(BasicFormatter<Char> &f, const Char *&format_str, const T &value) {
|
||||
|
@ -46,7 +46,7 @@ TEST(FormatTest, ArgConverter) {
|
||||
|
||||
TEST(FormatTest, FormatNegativeNaN) {
|
||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
if (getsign(-nan))
|
||||
if (fmt::internal::getsign(-nan))
|
||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
||||
else
|
||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
||||
|
@ -126,6 +126,82 @@ class TestString {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class MockAllocator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
MOCK_METHOD1_T(allocate, T* (std::size_t n));
|
||||
MOCK_METHOD2_T(deallocate, void (T* p, std::size_t n));
|
||||
};
|
||||
|
||||
template <typename Allocator>
|
||||
class AllocatorRef {
|
||||
private:
|
||||
Allocator *alloc_;
|
||||
|
||||
public:
|
||||
typedef typename Allocator::value_type value_type;
|
||||
|
||||
explicit AllocatorRef(Allocator *alloc = 0) : alloc_(alloc) {}
|
||||
|
||||
AllocatorRef(const AllocatorRef &other) : alloc_(other.alloc_) {}
|
||||
|
||||
AllocatorRef& operator=(const AllocatorRef &other) {
|
||||
alloc_ = other.alloc_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if FMT_USE_RVALUE_REFERENCES
|
||||
private:
|
||||
void move(AllocatorRef &other) {
|
||||
alloc_ = other.alloc_;
|
||||
other.alloc_ = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
AllocatorRef(AllocatorRef &&other) {
|
||||
move(other);
|
||||
}
|
||||
|
||||
AllocatorRef& operator=(AllocatorRef &&other) {
|
||||
assert(this != &other);
|
||||
move(other);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
Allocator *get() const { return alloc_; }
|
||||
|
||||
value_type* allocate(std::size_t n) { return alloc_->allocate(n); }
|
||||
void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); }
|
||||
};
|
||||
|
||||
void CheckForwarding(
|
||||
MockAllocator<int> &alloc, AllocatorRef< MockAllocator<int> > &ref) {
|
||||
int mem;
|
||||
// Check if value_type is properly defined.
|
||||
AllocatorRef< MockAllocator<int> >::value_type *ptr = &mem;
|
||||
// Check forwarding.
|
||||
EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr));
|
||||
ref.allocate(42);
|
||||
EXPECT_CALL(alloc, deallocate(ptr, 42));
|
||||
ref.deallocate(ptr, 42);
|
||||
}
|
||||
|
||||
TEST(AllocatorTest, AllocatorRef) {
|
||||
testing::StrictMock< MockAllocator<int> > alloc;
|
||||
typedef AllocatorRef< MockAllocator<int> > TestAllocatorRef;
|
||||
TestAllocatorRef ref(&alloc);
|
||||
// Check if AllocatorRef forwards to the underlying allocator.
|
||||
CheckForwarding(alloc, ref);
|
||||
TestAllocatorRef ref2(ref);
|
||||
CheckForwarding(alloc, ref2);
|
||||
TestAllocatorRef ref3;
|
||||
EXPECT_EQ(0, ref3.get());
|
||||
ref3 = ref;
|
||||
CheckForwarding(alloc, ref3);
|
||||
}
|
||||
|
||||
TEST(ArrayTest, Ctor) {
|
||||
Array<char, 123> array;
|
||||
EXPECT_EQ(0u, array.size());
|
||||
@ -134,16 +210,23 @@ TEST(ArrayTest, Ctor) {
|
||||
|
||||
#if FMT_USE_RVALUE_REFERENCES
|
||||
|
||||
void check_move_array(const char *str, Array<char, 5> &array) {
|
||||
Array<char, 5> array2(std::move(array));
|
||||
typedef AllocatorRef< std::allocator<char> > TestAllocator;
|
||||
|
||||
void check_move_array(const char *str, Array<char, 5, TestAllocator> &array) {
|
||||
std::allocator<char> *alloc = array.get_allocator().get();
|
||||
Array<char, 5, TestAllocator> array2(std::move(array));
|
||||
// Move shouldn't destroy the inline content of the first array.
|
||||
EXPECT_EQ(str, std::string(&array[0], array.size()));
|
||||
EXPECT_EQ(str, std::string(&array2[0], array2.size()));
|
||||
EXPECT_EQ(5, array2.capacity());
|
||||
// Move should transfer allocator.
|
||||
EXPECT_EQ(0, array.get_allocator().get());
|
||||
EXPECT_EQ(alloc, array2.get_allocator().get());
|
||||
}
|
||||
|
||||
TEST(ArrayTest, MoveCtor) {
|
||||
Array<char, 5> array;
|
||||
std::allocator<char> alloc;
|
||||
Array<char, 5, TestAllocator> array((TestAllocator(&alloc)));
|
||||
const char test[] = "test";
|
||||
array.append(test, test + 4);
|
||||
check_move_array("test", array);
|
||||
@ -155,7 +238,7 @@ TEST(ArrayTest, MoveCtor) {
|
||||
// Adding one more character causes the content to move from the inline to
|
||||
// a dynamically allocated buffer.
|
||||
array.push_back('b');
|
||||
Array<char, 5> array2(std::move(array));
|
||||
Array<char, 5, TestAllocator> array2(std::move(array));
|
||||
// Move should rip the guts of the first array.
|
||||
EXPECT_EQ(inline_buffer_ptr, &array[0]);
|
||||
EXPECT_EQ("testab", std::string(&array2[0], array2.size()));
|
||||
@ -274,56 +357,6 @@ TEST(ArrayTest, AppendAllocatesEnoughStorage) {
|
||||
EXPECT_EQ(19u, array.capacity());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class MockAllocator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
MOCK_METHOD1_T(allocate, T* (std::size_t n));
|
||||
MOCK_METHOD2_T(deallocate, void (T* p, std::size_t n));
|
||||
};
|
||||
|
||||
template <typename Allocator>
|
||||
class AllocatorRef {
|
||||
private:
|
||||
Allocator *alloc_;
|
||||
|
||||
public:
|
||||
typedef typename Allocator::value_type value_type;
|
||||
|
||||
explicit AllocatorRef(Allocator *alloc = 0) : alloc_(alloc) {}
|
||||
|
||||
Allocator *get() const { return alloc_; }
|
||||
|
||||
value_type* allocate(std::size_t n) { return alloc_->allocate(n); }
|
||||
void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); }
|
||||
};
|
||||
|
||||
void CheckForwarding(
|
||||
MockAllocator<int> &alloc, AllocatorRef< MockAllocator<int> > &ref) {
|
||||
int mem;
|
||||
// Check if value_type is properly defined.
|
||||
AllocatorRef< MockAllocator<int> >::value_type *ptr = &mem;
|
||||
// Check forwarding.
|
||||
EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr));
|
||||
ref.allocate(42);
|
||||
EXPECT_CALL(alloc, deallocate(ptr, 42));
|
||||
ref.deallocate(ptr, 42);
|
||||
}
|
||||
|
||||
TEST(AllocatorTest, AllocatorRef) {
|
||||
testing::StrictMock< MockAllocator<int> > alloc;
|
||||
typedef AllocatorRef< MockAllocator<int> > TestAllocatorRef;
|
||||
TestAllocatorRef ref(&alloc);
|
||||
// Check if AllocatorRef forwards to the underlying allocator.
|
||||
CheckForwarding(alloc, ref);
|
||||
TestAllocatorRef ref2(ref);
|
||||
CheckForwarding(alloc, ref2);
|
||||
TestAllocatorRef ref3;
|
||||
EXPECT_EQ(0, ref3.get());
|
||||
ref3 = ref;
|
||||
CheckForwarding(alloc, ref3);
|
||||
}
|
||||
|
||||
TEST(ArrayTest, Allocator) {
|
||||
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
||||
Array<char, 10, TestAllocator> array;
|
||||
@ -340,7 +373,7 @@ TEST(ArrayTest, Allocator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ArrayTest, DeallocateException) {
|
||||
TEST(ArrayTest, ExceptionInDeallocate) {
|
||||
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
||||
testing::StrictMock< MockAllocator<char> > alloc;
|
||||
Array<char, 10, TestAllocator> array((TestAllocator(&alloc)));
|
||||
@ -434,6 +467,17 @@ TEST(WriterTest, MoveAssignment) {
|
||||
|
||||
#endif // FMT_USE_RVALUE_REFERENCES
|
||||
|
||||
TEST(WriterTest, Allocator) {
|
||||
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
||||
MockAllocator<char> alloc;
|
||||
BasicWriter<char, TestAllocator> w((TestAllocator(&alloc)));
|
||||
std::size_t size = 1.5 * fmt::internal::INLINE_BUFFER_SIZE;
|
||||
std::vector<char> mem(size);
|
||||
EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0]));
|
||||
for (int i = 0; i < fmt::internal::INLINE_BUFFER_SIZE + 1; ++i)
|
||||
w << '*';
|
||||
}
|
||||
|
||||
TEST(WriterTest, Data) {
|
||||
Writer w;
|
||||
w << 42;
|
||||
|
Loading…
Reference in New Issue
Block a user