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 {
|
namespace {
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
|
# define FMT_SNPRINTF snprintf
|
||||||
// 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
|
|
||||||
|
|
||||||
#else // _MSC_VER
|
#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, ...) {
|
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
@ -105,16 +71,9 @@ inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
|||||||
va_end(args);
|
va_end(args);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
#define FMT_SNPRINTF fmt_snprintf
|
# define FMT_SNPRINTF fmt_snprintf
|
||||||
|
|
||||||
#endif // _MSC_VER
|
#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
|
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||||
// signed and unsigned integers.
|
// signed and unsigned integers.
|
||||||
template <bool IsSigned>
|
template <bool IsSigned>
|
||||||
@ -600,175 +559,9 @@ class fmt::internal::ArgFormatter :
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fills the padding around the content and returns the pointer to the
|
template <typename Char, typename Allocator>
|
||||||
// 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 StrChar>
|
template <typename StrChar>
|
||||||
void fmt::BasicWriter<Char>::write_str(
|
void fmt::BasicWriter<Char, Allocator>::write_str(
|
||||||
const Arg::StringValue<StrChar> &str, const FormatSpec &spec) {
|
const Arg::StringValue<StrChar> &str, const FormatSpec &spec) {
|
||||||
// Check if StrChar is convertible to Char.
|
// Check if StrChar is convertible to Char.
|
||||||
internal::CharTraits<Char>::convert(StrChar());
|
internal::CharTraits<Char>::convert(StrChar());
|
||||||
@ -1259,10 +1052,6 @@ int fmt::fprintf(std::FILE *f, StringRef format, const ArgList &args) {
|
|||||||
|
|
||||||
// Explicit instantiations for char.
|
// 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(
|
template void fmt::BasicFormatter<char>::format(
|
||||||
BasicStringRef<char> format, const ArgList &args);
|
BasicStringRef<char> format, const ArgList &args);
|
||||||
|
|
||||||
@ -1271,10 +1060,6 @@ template void fmt::internal::PrintfFormatter<char>::format(
|
|||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// 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(
|
template void fmt::BasicFormatter<wchar_t>::format(
|
||||||
BasicStringRef<wchar_t> format, const ArgList &args);
|
BasicStringRef<wchar_t> format, const ArgList &args);
|
||||||
|
|
||||||
|
268
format.h
268
format.h
@ -31,6 +31,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <cstddef> // for std::ptrdiff_t
|
#include <cstddef> // for std::ptrdiff_t
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -122,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong;
|
|||||||
using std::move;
|
using std::move;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char, typename Allocator = std::allocator<Char> >
|
||||||
class BasicWriter;
|
class BasicWriter;
|
||||||
|
|
||||||
typedef BasicWriter<char> Writer;
|
typedef BasicWriter<char> Writer;
|
||||||
@ -253,8 +254,19 @@ class Array : private Allocator {
|
|||||||
if (ptr_ != data_) this->deallocate(ptr_, capacity_);
|
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.
|
// Move data from other to this array.
|
||||||
void move(Array &other) {
|
void move(Array &other) {
|
||||||
|
Allocator &this_alloc = *this, &other_alloc = other;
|
||||||
|
this_alloc = std::move(other_alloc);
|
||||||
size_ = other.size_;
|
size_ = other.size_;
|
||||||
capacity_ = other.capacity_;
|
capacity_ = other.capacity_;
|
||||||
if (other.ptr_ == other.data_) {
|
if (other.ptr_ == other.data_) {
|
||||||
@ -268,14 +280,7 @@ class Array : private Allocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_DISALLOW_COPY_AND_ASSIGN(Array);
|
|
||||||
|
|
||||||
public:
|
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) {
|
Array(Array &&other) {
|
||||||
move(other);
|
move(other);
|
||||||
}
|
}
|
||||||
@ -351,6 +356,44 @@ void Array<T, SIZE, Allocator>::append(const T *begin, const T *end) {
|
|||||||
size_ += num_elements;
|
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>
|
template <typename Char>
|
||||||
class BasicCharTraits {
|
class BasicCharTraits {
|
||||||
public:
|
public:
|
||||||
@ -1274,15 +1317,16 @@ class SystemError : public internal::RuntimeError {
|
|||||||
a character stream. The output is stored in a memory buffer that grows
|
a character stream. The output is stored in a memory buffer that grows
|
||||||
dynamically.
|
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 |
|
| Type | Definition |
|
||||||
+=========+======================+
|
+=========+===============================================+
|
||||||
| Writer | BasicWriter<char> |
|
| Writer | BasicWriter<char, std::allocator<char>> |
|
||||||
+---------+----------------------+
|
+---------+-----------------------------------------------+
|
||||||
| WWriter | BasicWriter<wchar_t> |
|
| WWriter | BasicWriter<wchar_t, std::allocator<wchar_t>> |
|
||||||
+---------+----------------------+
|
+---------+-----------------------------------------------+
|
||||||
|
|
||||||
**Example**::
|
**Example**::
|
||||||
|
|
||||||
@ -1301,11 +1345,12 @@ class SystemError : public internal::RuntimeError {
|
|||||||
accessed as a C string with ``out.c_str()``.
|
accessed as a C string with ``out.c_str()``.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename Char>
|
template <typename Char, typename Allocator>
|
||||||
class BasicWriter {
|
class BasicWriter {
|
||||||
private:
|
private:
|
||||||
// Output buffer.
|
// 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;
|
typedef typename internal::CharTraits<Char>::CharPtr CharPtr;
|
||||||
|
|
||||||
@ -1316,6 +1361,8 @@ class BasicWriter {
|
|||||||
static Char *get(Char *p) { return p; }
|
static Char *get(Char *p) { return p; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Fills the padding around the content and returns the pointer to the
|
||||||
|
// content area.
|
||||||
static CharPtr fill_padding(CharPtr buffer,
|
static CharPtr fill_padding(CharPtr buffer,
|
||||||
unsigned total_size, std::size_t content_size, wchar_t fill);
|
unsigned total_size, std::size_t content_size, wchar_t fill);
|
||||||
|
|
||||||
@ -1370,7 +1417,7 @@ class BasicWriter {
|
|||||||
/**
|
/**
|
||||||
Constructs a ``BasicWriter`` object.
|
Constructs a ``BasicWriter`` object.
|
||||||
*/
|
*/
|
||||||
BasicWriter() {}
|
BasicWriter(const Allocator &alloc = Allocator()) : buffer_(alloc) {}
|
||||||
|
|
||||||
#if FMT_USE_RVALUE_REFERENCES
|
#if FMT_USE_RVALUE_REFERENCES
|
||||||
/**
|
/**
|
||||||
@ -1446,7 +1493,7 @@ class BasicWriter {
|
|||||||
void write(BasicStringRef<Char> format, const ArgList &args) {
|
void write(BasicStringRef<Char> format, const ArgList &args) {
|
||||||
BasicFormatter<Char>(*this).format(format, args);
|
BasicFormatter<Char>(*this).format(format, args);
|
||||||
}
|
}
|
||||||
FMT_VARIADIC_VOID(write, fmt::BasicStringRef<Char>)
|
FMT_VARIADIC_VOID(write, BasicStringRef<Char>)
|
||||||
|
|
||||||
BasicWriter &operator<<(int value) {
|
BasicWriter &operator<<(int value) {
|
||||||
return *this << IntFormatSpec<int>(value);
|
return *this << IntFormatSpec<int>(value);
|
||||||
@ -1525,9 +1572,10 @@ class BasicWriter {
|
|||||||
void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
|
void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char, typename Allocator>
|
||||||
template <typename StrChar>
|
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) {
|
const StrChar *s, std::size_t size, const AlignSpec &spec) {
|
||||||
CharPtr out = CharPtr();
|
CharPtr out = CharPtr();
|
||||||
if (spec.width() > size) {
|
if (spec.width() > size) {
|
||||||
@ -1548,10 +1596,25 @@ typename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str(
|
|||||||
return out;
|
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>
|
template <typename Spec>
|
||||||
typename fmt::BasicWriter<Char>::CharPtr
|
typename BasicWriter<Char, Allocator>::CharPtr
|
||||||
fmt::BasicWriter<Char>::prepare_int_buffer(
|
BasicWriter<Char, Allocator>::prepare_int_buffer(
|
||||||
unsigned num_digits, const Spec &spec,
|
unsigned num_digits, const Spec &spec,
|
||||||
const char *prefix, unsigned prefix_size) {
|
const char *prefix, unsigned prefix_size) {
|
||||||
unsigned width = spec.width();
|
unsigned width = spec.width();
|
||||||
@ -1611,9 +1674,9 @@ typename fmt::BasicWriter<Char>::CharPtr
|
|||||||
return p - 1;
|
return p - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char, typename Allocator>
|
||||||
template <typename T, typename Spec>
|
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;
|
unsigned prefix_size = 0;
|
||||||
typedef typename internal::IntTraits<T>::MainType UnsignedType;
|
typedef typename internal::IntTraits<T>::MainType UnsignedType;
|
||||||
UnsignedType abs_value = value;
|
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.
|
// Formats a value.
|
||||||
template <typename Char, typename T>
|
template <typename Char, typename T>
|
||||||
void format(BasicFormatter<Char> &f, const Char *&format_str, const T &value) {
|
void format(BasicFormatter<Char> &f, const Char *&format_str, const T &value) {
|
||||||
|
@ -46,7 +46,7 @@ TEST(FormatTest, ArgConverter) {
|
|||||||
|
|
||||||
TEST(FormatTest, FormatNegativeNaN) {
|
TEST(FormatTest, FormatNegativeNaN) {
|
||||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
if (getsign(-nan))
|
if (fmt::internal::getsign(-nan))
|
||||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
||||||
else
|
else
|
||||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
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) {
|
TEST(ArrayTest, Ctor) {
|
||||||
Array<char, 123> array;
|
Array<char, 123> array;
|
||||||
EXPECT_EQ(0u, array.size());
|
EXPECT_EQ(0u, array.size());
|
||||||
@ -134,16 +210,23 @@ TEST(ArrayTest, Ctor) {
|
|||||||
|
|
||||||
#if FMT_USE_RVALUE_REFERENCES
|
#if FMT_USE_RVALUE_REFERENCES
|
||||||
|
|
||||||
void check_move_array(const char *str, Array<char, 5> &array) {
|
typedef AllocatorRef< std::allocator<char> > TestAllocator;
|
||||||
Array<char, 5> array2(std::move(array));
|
|
||||||
|
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.
|
// 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(&array[0], array.size()));
|
||||||
EXPECT_EQ(str, std::string(&array2[0], array2.size()));
|
EXPECT_EQ(str, std::string(&array2[0], array2.size()));
|
||||||
EXPECT_EQ(5, array2.capacity());
|
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) {
|
TEST(ArrayTest, MoveCtor) {
|
||||||
Array<char, 5> array;
|
std::allocator<char> alloc;
|
||||||
|
Array<char, 5, TestAllocator> array((TestAllocator(&alloc)));
|
||||||
const char test[] = "test";
|
const char test[] = "test";
|
||||||
array.append(test, test + 4);
|
array.append(test, test + 4);
|
||||||
check_move_array("test", array);
|
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
|
// Adding one more character causes the content to move from the inline to
|
||||||
// a dynamically allocated buffer.
|
// a dynamically allocated buffer.
|
||||||
array.push_back('b');
|
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.
|
// Move should rip the guts of the first array.
|
||||||
EXPECT_EQ(inline_buffer_ptr, &array[0]);
|
EXPECT_EQ(inline_buffer_ptr, &array[0]);
|
||||||
EXPECT_EQ("testab", std::string(&array2[0], array2.size()));
|
EXPECT_EQ("testab", std::string(&array2[0], array2.size()));
|
||||||
@ -274,56 +357,6 @@ TEST(ArrayTest, AppendAllocatesEnoughStorage) {
|
|||||||
EXPECT_EQ(19u, array.capacity());
|
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) {
|
TEST(ArrayTest, Allocator) {
|
||||||
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
||||||
Array<char, 10, TestAllocator> array;
|
Array<char, 10, TestAllocator> array;
|
||||||
@ -340,7 +373,7 @@ TEST(ArrayTest, Allocator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ArrayTest, DeallocateException) {
|
TEST(ArrayTest, ExceptionInDeallocate) {
|
||||||
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
typedef AllocatorRef< MockAllocator<char> > TestAllocator;
|
||||||
testing::StrictMock< MockAllocator<char> > alloc;
|
testing::StrictMock< MockAllocator<char> > alloc;
|
||||||
Array<char, 10, TestAllocator> array((TestAllocator(&alloc)));
|
Array<char, 10, TestAllocator> array((TestAllocator(&alloc)));
|
||||||
@ -434,6 +467,17 @@ TEST(WriterTest, MoveAssignment) {
|
|||||||
|
|
||||||
#endif // FMT_USE_RVALUE_REFERENCES
|
#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) {
|
TEST(WriterTest, Data) {
|
||||||
Writer w;
|
Writer w;
|
||||||
w << 42;
|
w << 42;
|
||||||
|
Loading…
Reference in New Issue
Block a user