Commit 877abaf3 authored by Victor Zverovich's avatar Victor Zverovich

Parameterize integer formatting method on format spec type. Add Sprint/iomanip...

Parameterize integer formatting method on format spec type. Add Sprint/iomanip style formatting methods (oct, hex, hexu, pad).
parent 6d116e95
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
#include "format.h" #include "format.h"
#include <math.h> #include <math.h>
#include <stdint.h>
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
...@@ -44,8 +43,10 @@ ...@@ -44,8 +43,10 @@
using std::size_t; using std::size_t;
using fmt::BasicFormatter; using fmt::BasicFormatter;
using fmt::IntFormatter;
using fmt::Formatter; using fmt::Formatter;
using fmt::FormatSpec; using fmt::FormatSpec;
using fmt::WidthSpec;
using fmt::StringRef; using fmt::StringRef;
#if _MSC_VER #if _MSC_VER
...@@ -56,59 +57,12 @@ using fmt::StringRef; ...@@ -56,59 +57,12 @@ using fmt::StringRef;
namespace { namespace {
// Flags.
enum { SIGN_FLAG = 1, PLUS_FLAG = 2, HASH_FLAG = 4 };
void ReportUnknownType(char code, const char *type) {
if (std::isprint(static_cast<unsigned char>(code))) {
throw fmt::FormatError(
str(fmt::Format("unknown format code '{0}' for {1}") << code << type));
}
throw fmt::FormatError(
str(fmt::Format("unknown format code '\\x{0:02x}' for {1}")
<< static_cast<unsigned>(code) << type));
}
// Information about an integer type.
template <typename T>
struct IntTraits {
typedef T UnsignedType;
static bool IsNegative(T) { return false; }
};
template <>
struct IntTraits<int> {
typedef unsigned UnsignedType;
static bool IsNegative(int value) { return value < 0; }
};
template <>
struct IntTraits<long> {
typedef unsigned long UnsignedType;
static bool IsNegative(long value) { return value < 0; }
};
template <typename T> template <typename T>
struct IsLongDouble { enum {VALUE = 0}; }; struct IsLongDouble { enum {VALUE = 0}; };
template <> template <>
struct IsLongDouble<long double> { enum {VALUE = 1}; }; struct IsLongDouble<long double> { enum {VALUE = 1}; };
inline unsigned CountDigits(uint64_t n) {
unsigned count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
const char DIGITS[] = const char DIGITS[] =
"0001020304050607080910111213141516171819" "0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839" "2021222324252627282930313233343536373839"
...@@ -116,27 +70,6 @@ const char DIGITS[] = ...@@ -116,27 +70,6 @@ const char DIGITS[] =
"6061626364656667686970717273747576777879" "6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899"; "8081828384858687888990919293949596979899";
void FormatDecimal(char *buffer, uint64_t value, unsigned num_digits) {
--num_digits;
while (value >= 100) {
// Integer division is slow so do it for a group of two digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
unsigned index = (value % 100) * 2;
value /= 100;
buffer[num_digits] = DIGITS[index + 1];
buffer[num_digits - 1] = DIGITS[index];
num_digits -= 2;
}
if (value < 10) {
*buffer = static_cast<char>('0' + value);
return;
}
unsigned index = static_cast<unsigned>(value * 2);
buffer[1] = DIGITS[index + 1];
buffer[0] = DIGITS[index];
}
// Fills the padding around the content and returns the pointer to the // Fills the padding around the content and returns the pointer to the
// content area. // content area.
char *FillPadding(char *buffer, char *FillPadding(char *buffer,
...@@ -161,25 +94,59 @@ int signbit(double value) { ...@@ -161,25 +94,59 @@ int signbit(double value) {
#endif #endif
} }
void BasicFormatter::FormatDecimal(
char *buffer, uint64_t value, unsigned num_digits) {
--num_digits;
while (value >= 100) {
// Integer division is slow so do it for a group of two digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
unsigned index = (value % 100) * 2;
value /= 100;
buffer[num_digits] = DIGITS[index + 1];
buffer[num_digits - 1] = DIGITS[index];
num_digits -= 2;
}
if (value < 10) {
*buffer = static_cast<char>('0' + value);
return;
}
unsigned index = static_cast<unsigned>(value * 2);
buffer[1] = DIGITS[index + 1];
buffer[0] = DIGITS[index];
}
void BasicFormatter::ReportUnknownType(char code, const char *type) {
if (std::isprint(static_cast<unsigned char>(code))) {
throw fmt::FormatError(fmt::str(
fmt::Format("unknown format code '{0}' for {1}") << code << type));
}
throw fmt::FormatError(
fmt::str(fmt::Format("unknown format code '\\x{0:02x}' for {1}")
<< static_cast<unsigned>(code) << type));
}
char *BasicFormatter::PrepareFilledBuffer( char *BasicFormatter::PrepareFilledBuffer(
unsigned size, const FormatSpec &spec, char sign) { unsigned size, const AlignSpec &spec, char sign) {
if (spec.width <= size) { unsigned width = spec.width();
if (width <= size) {
char *p = GrowBuffer(size); char *p = GrowBuffer(size);
*p = sign; *p = sign;
return p + size - 1; return p + size - 1;
} }
char *p = GrowBuffer(spec.width); char *p = GrowBuffer(width);
char *end = p + spec.width; char *end = p + width;
if (spec.align == ALIGN_LEFT) { Alignment align = spec.align();
if (align == ALIGN_LEFT) {
*p = sign; *p = sign;
p += size; p += size;
std::fill(p, end, spec.fill); std::fill(p, end, spec.fill());
} else if (spec.align == ALIGN_CENTER) { } else if (align == ALIGN_CENTER) {
p = FillPadding(p, spec.width, size, spec.fill); p = FillPadding(p, width, size, spec.fill());
*p = sign; *p = sign;
p += size; p += size;
} else { } else {
if (spec.align == ALIGN_NUMERIC) { if (align == ALIGN_NUMERIC) {
if (sign) { if (sign) {
*p++ = sign; *p++ = sign;
--size; --size;
...@@ -187,81 +154,17 @@ char *BasicFormatter::PrepareFilledBuffer( ...@@ -187,81 +154,17 @@ char *BasicFormatter::PrepareFilledBuffer(
} else { } else {
*(end - size) = sign; *(end - size) = sign;
} }
std::fill(p, end - size, spec.fill); std::fill(p, end - size, spec.fill());
p = end; p = end;
} }
return p - 1; return p - 1;
} }
template <typename T>
void BasicFormatter::FormatInt(T value, const FormatSpec &spec) {
unsigned size = 0;
char sign = 0;
typedef typename IntTraits<T>::UnsignedType UnsignedType;
UnsignedType abs_value = value;
if (IntTraits<T>::IsNegative(value)) {
sign = '-';
++size;
abs_value = 0 - abs_value;
} else if ((spec.flags & SIGN_FLAG) != 0) {
sign = (spec.flags & PLUS_FLAG) != 0 ? '+' : ' ';
++size;
}
switch (spec.type) {
case 0: case 'd': {
unsigned num_digits = CountDigits(abs_value);
char *p = PrepareFilledBuffer(size + num_digits, spec, sign)
- num_digits + 1;
FormatDecimal(p, abs_value, num_digits);
break;
}
case 'x': case 'X': {
UnsignedType n = abs_value;
bool print_prefix = (spec.flags & HASH_FLAG) != 0;
if (print_prefix) size += 2;
do {
++size;
} while ((n >>= 4) != 0);
char *p = PrepareFilledBuffer(size, spec, sign);
n = abs_value;
const char *digits = spec.type == 'x' ?
"0123456789abcdef" : "0123456789ABCDEF";
do {
*p-- = digits[n & 0xf];
} while ((n >>= 4) != 0);
if (print_prefix) {
*p-- = spec.type;
*p = '0';
}
break;
}
case 'o': {
UnsignedType n = abs_value;
bool print_prefix = (spec.flags & HASH_FLAG) != 0;
if (print_prefix) ++size;
do {
++size;
} while ((n >>= 3) != 0);
char *p = PrepareFilledBuffer(size, spec, sign);
n = abs_value;
do {
*p-- = '0' + (n & 7);
} while ((n >>= 3) != 0);
if (print_prefix)
*p = '0';
break;
}
default:
ReportUnknownType(spec.type, "integer");
break;
}
}
template <typename T> template <typename T>
void BasicFormatter::FormatDouble( void BasicFormatter::FormatDouble(
T value, const FormatSpec &spec, int precision) { T value, const FormatSpec &spec, int precision) {
// Check type. // Check type.
char type = spec.type; char type = spec.type();
bool upper = false; bool upper = false;
switch (type) { switch (type) {
case 0: case 0:
...@@ -289,8 +192,8 @@ void BasicFormatter::FormatDouble( ...@@ -289,8 +192,8 @@ void BasicFormatter::FormatDouble(
if (signbit(value)) { if (signbit(value)) {
sign = '-'; sign = '-';
value = -value; value = -value;
} else if ((spec.flags & SIGN_FLAG) != 0) { } else if (spec.sign_flag()) {
sign = (spec.flags & PLUS_FLAG) != 0 ? '+' : ' '; sign = spec.plus_flag() ? '+' : ' ';
} }
if (value != value) { if (value != value) {
...@@ -324,7 +227,7 @@ void BasicFormatter::FormatDouble( ...@@ -324,7 +227,7 @@ void BasicFormatter::FormatDouble(
} }
size_t offset = buffer_.size(); size_t offset = buffer_.size();
unsigned width = spec.width; unsigned width = spec.width();
if (sign) { if (sign) {
buffer_.reserve(buffer_.size() + std::max(width, 1u)); buffer_.reserve(buffer_.size() + std::max(width, 1u));
if (width > 0) if (width > 0)
...@@ -338,12 +241,12 @@ void BasicFormatter::FormatDouble( ...@@ -338,12 +241,12 @@ void BasicFormatter::FormatDouble(
char *format_ptr = format; char *format_ptr = format;
*format_ptr++ = '%'; *format_ptr++ = '%';
unsigned width_for_sprintf = width; unsigned width_for_sprintf = width;
if ((spec.flags & HASH_FLAG) != 0) if (spec.hash_flag())
*format_ptr++ = '#'; *format_ptr++ = '#';
if (spec.align == ALIGN_CENTER) { if (spec.align() == ALIGN_CENTER) {
width_for_sprintf = 0; width_for_sprintf = 0;
} else { } else {
if (spec.align == ALIGN_LEFT) if (spec.align() == ALIGN_LEFT)
*format_ptr++ = '-'; *format_ptr++ = '-';
if (width != 0) if (width != 0)
*format_ptr++ = '*'; *format_ptr++ = '*';
...@@ -373,24 +276,25 @@ void BasicFormatter::FormatDouble( ...@@ -373,24 +276,25 @@ void BasicFormatter::FormatDouble(
} }
if (n >= 0 && offset + n < buffer_.capacity()) { if (n >= 0 && offset + n < buffer_.capacity()) {
if (sign) { if (sign) {
if ((spec.align != ALIGN_RIGHT && spec.align != ALIGN_DEFAULT) || if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||
*start != ' ') { *start != ' ') {
*(start - 1) = sign; *(start - 1) = sign;
sign = 0; sign = 0;
} else { } else {
*(start - 1) = spec.fill; *(start - 1) = spec.fill();
} }
++n; ++n;
} }
if (spec.align == ALIGN_CENTER && spec.width > static_cast<unsigned>(n)) { if (spec.align() == ALIGN_CENTER &&
char *p = GrowBuffer(spec.width); spec.width() > static_cast<unsigned>(n)) {
std::copy(p, p + n, p + (spec.width - n) / 2); char *p = GrowBuffer(spec.width());
FillPadding(p, spec.width, n, spec.fill); std::copy(p, p + n, p + (spec.width() - n) / 2);
FillPadding(p, spec.width(), n, spec.fill());
return; return;
} }
if (spec.fill != ' ' || sign) { if (spec.fill() != ' ' || sign) {
while (*start == ' ') while (*start == ' ')
*start++ = spec.fill; *start++ = spec.fill();
if (sign) if (sign)
*(start - 1) = sign; *(start - 1) = sign;
} }
...@@ -404,15 +308,15 @@ void BasicFormatter::FormatDouble( ...@@ -404,15 +308,15 @@ void BasicFormatter::FormatDouble(
char *BasicFormatter::FormatString( char *BasicFormatter::FormatString(
const char *s, std::size_t size, const FormatSpec &spec) { const char *s, std::size_t size, const FormatSpec &spec) {
char *out = 0; char *out = 0;
if (spec.width > size) { if (spec.width() > size) {
out = GrowBuffer(spec.width); out = GrowBuffer(spec.width());
if (spec.align == ALIGN_RIGHT) { if (spec.align() == ALIGN_RIGHT) {
std::fill_n(out, spec.width - size, spec.fill); std::fill_n(out, spec.width() - size, spec.fill());
out += spec.width - size; out += spec.width() - size;
} else if (spec.align == ALIGN_CENTER) { } else if (spec.align() == ALIGN_CENTER) {
out = FillPadding(out, spec.width, size, spec.fill); out = FillPadding(out, spec.width(), size, spec.fill());
} else { } else {
std::fill_n(out + size, spec.width - size, spec.fill); std::fill_n(out + size, spec.width() - size, spec.fill());
} }
} else { } else {
out = GrowBuffer(size); out = GrowBuffer(size);
...@@ -421,22 +325,6 @@ char *BasicFormatter::FormatString( ...@@ -421,22 +325,6 @@ char *BasicFormatter::FormatString(
return out; return out;
} }
void BasicFormatter::operator<<(int value) {
unsigned abs_value = value;
unsigned num_digits = 0;
char *out = 0;
if (value >= 0) {
num_digits = CountDigits(abs_value);
out = GrowBuffer(num_digits);
} else {
abs_value = 0 - abs_value;
num_digits = CountDigits(abs_value);
out = GrowBuffer(num_digits + 1);
*out++ = '-';
}
FormatDecimal(out, abs_value, num_digits);
}
// Throws Exception(message) if format contains '}', otherwise throws // Throws Exception(message) if format contains '}', otherwise throws
// FormatError reporting unmatched '{'. The idea is that unmatched '{' // FormatError reporting unmatched '{'. The idea is that unmatched '{'
// should override other errors. // should override other errors.
...@@ -529,31 +417,31 @@ void Formatter::DoFormat() { ...@@ -529,31 +417,31 @@ void Formatter::DoFormat() {
// Parse fill and alignment. // Parse fill and alignment.
if (char c = *s) { if (char c = *s) {
const char *p = s + 1; const char *p = s + 1;
spec.align = ALIGN_DEFAULT; spec.align_ = ALIGN_DEFAULT;
do { do {
switch (*p) { switch (*p) {
case '<': case '<':
spec.align = ALIGN_LEFT; spec.align_ = ALIGN_LEFT;
break; break;
case '>': case '>':
spec.align = ALIGN_RIGHT; spec.align_ = ALIGN_RIGHT;
break; break;
case '=': case '=':
spec.align = ALIGN_NUMERIC; spec.align_ = ALIGN_NUMERIC;
break; break;
case '^': case '^':
spec.align = ALIGN_CENTER; spec.align_ = ALIGN_CENTER;
break; break;
} }
if (spec.align != ALIGN_DEFAULT) { if (spec.align_ != ALIGN_DEFAULT) {
if (p != s) { if (p != s) {
if (c == '}') break; if (c == '}') break;
if (c == '{') if (c == '{')
ReportError(s, "invalid fill character '{'"); ReportError(s, "invalid fill character '{'");
s += 2; s += 2;
spec.fill = c; spec.fill_ = c;
} else ++s; } else ++s;
if (spec.align == ALIGN_NUMERIC && arg.type > LAST_NUMERIC_TYPE) if (spec.align_ == ALIGN_NUMERIC && arg.type > LAST_NUMERIC_TYPE)
ReportError(s, "format specifier '=' requires numeric argument"); ReportError(s, "format specifier '=' requires numeric argument");
break; break;
} }
...@@ -564,21 +452,21 @@ void Formatter::DoFormat() { ...@@ -564,21 +452,21 @@ void Formatter::DoFormat() {
switch (*s) { switch (*s) {
case '+': case '+':
CheckSign(s, arg); CheckSign(s, arg);
spec.flags |= SIGN_FLAG | PLUS_FLAG; spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
break; break;
case '-': case '-':
CheckSign(s, arg); CheckSign(s, arg);
break; break;
case ' ': case ' ':
CheckSign(s, arg); CheckSign(s, arg);
spec.flags |= SIGN_FLAG; spec.flags_ |= SIGN_FLAG;
break; break;
} }
if (*s == '#') { if (*s == '#') {
if (arg.type > LAST_NUMERIC_TYPE) if (arg.type > LAST_NUMERIC_TYPE)
ReportError(s, "format specifier '#' requires numeric argument"); ReportError(s, "format specifier '#' requires numeric argument");
spec.flags |= HASH_FLAG; spec.flags_ |= HASH_FLAG;
++s; ++s;
} }
...@@ -587,15 +475,15 @@ void Formatter::DoFormat() { ...@@ -587,15 +475,15 @@ void Formatter::DoFormat() {
if (*s == '0') { if (*s == '0') {
if (arg.type > LAST_NUMERIC_TYPE) if (arg.type > LAST_NUMERIC_TYPE)
ReportError(s, "format specifier '0' requires numeric argument"); ReportError(s, "format specifier '0' requires numeric argument");
spec.align = ALIGN_NUMERIC; spec.align_ = ALIGN_NUMERIC;
spec.fill = '0'; spec.fill_ = '0';
} }
// Zero may be parsed again as a part of the width, but it is simpler // Zero may be parsed again as a part of the width, but it is simpler
// and more efficient than checking if the next char is a digit. // and more efficient than checking if the next char is a digit.
unsigned value = ParseUInt(s); unsigned value = ParseUInt(s);
if (value > INT_MAX) if (value > INT_MAX)
ReportError(s, "number is too big in format"); ReportError(s, "number is too big in format");
spec.width = value; spec.width_ = value;
} }
// Parse precision. // Parse precision.
...@@ -649,7 +537,7 @@ void Formatter::DoFormat() { ...@@ -649,7 +537,7 @@ void Formatter::DoFormat() {
// Parse type. // Parse type.
if (*s != '}' && *s) if (*s != '}' && *s)
spec.type = *s++; spec.type_ = *s++;
} }
if (*s++ != '}') if (*s++ != '}')
...@@ -677,18 +565,18 @@ void Formatter::DoFormat() { ...@@ -677,18 +565,18 @@ void Formatter::DoFormat() {
FormatDouble(arg.long_double_value, spec, precision); FormatDouble(arg.long_double_value, spec, precision);
break; break;
case CHAR: { case CHAR: {
if (spec.type && spec.type != 'c') if (spec.type_ && spec.type_ != 'c')
ReportUnknownType(spec.type, "char"); ReportUnknownType(spec.type_, "char");
char *out = 0; char *out = 0;
if (spec.width > 1) { if (spec.width_ > 1) {
out = GrowBuffer(spec.width); out = GrowBuffer(spec.width_);
if (spec.align == ALIGN_RIGHT) { if (spec.align_ == ALIGN_RIGHT) {
std::fill_n(out, spec.width - 1, spec.fill); std::fill_n(out, spec.width_ - 1, spec.fill_);
out += spec.width - 1; out += spec.width_ - 1;
} else if (spec.align == ALIGN_CENTER) { } else if (spec.align_ == ALIGN_CENTER) {
out = FillPadding(out, spec.width, 1, spec.fill); out = FillPadding(out, spec.width_, 1, spec.fill_);
} else { } else {
std::fill_n(out + 1, spec.width - 1, spec.fill); std::fill_n(out + 1, spec.width_ - 1, spec.fill_);
} }
} else { } else {
out = GrowBuffer(1); out = GrowBuffer(1);
...@@ -697,8 +585,8 @@ void Formatter::DoFormat() { ...@@ -697,8 +585,8 @@ void Formatter::DoFormat() {
break; break;
} }
case STRING: { case STRING: {
if (spec.type && spec.type != 's') if (spec.type_ && spec.type_ != 's')
ReportUnknownType(spec.type, "string"); ReportUnknownType(spec.type_, "string");
const char *str = arg.string.value; const char *str = arg.string.value;
size_t size = arg.string.size; size_t size = arg.string.size;
if (size == 0) { if (size == 0) {
...@@ -711,15 +599,15 @@ void Formatter::DoFormat() { ...@@ -711,15 +599,15 @@ void Formatter::DoFormat() {
break; break;
} }
case POINTER: case POINTER:
if (spec.type && spec.type != 'p') if (spec.type_ && spec.type_ != 'p')
ReportUnknownType(spec.type, "pointer"); ReportUnknownType(spec.type_, "pointer");
spec.flags = HASH_FLAG; spec.flags_= HASH_FLAG;
spec.type = 'x'; spec.type_ = 'x';
FormatInt(reinterpret_cast<uintptr_t>(arg.pointer_value), spec); FormatInt(reinterpret_cast<uintptr_t>(arg.pointer_value), spec);
break; break;
case CUSTOM: case CUSTOM:
if (spec.type) if (spec.type_)
ReportUnknownType(spec.type, "object"); ReportUnknownType(spec.type_, "object");
(this->*arg.custom.format)(arg.custom.value, spec); (this->*arg.custom.format)(arg.custom.value, spec);
break; break;
default: default:
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#ifndef FORMAT_H_ #ifndef FORMAT_H_
#define FORMAT_H_ #define FORMAT_H_
#include <stdint.h>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
...@@ -114,6 +116,25 @@ void Array<T, SIZE>::append(const T *begin, const T *end) { ...@@ -114,6 +116,25 @@ void Array<T, SIZE>::append(const T *begin, const T *end) {
size_ += num_elements; size_ += num_elements;
} }
// Information about an integer type.
template <typename T>
struct IntTraits {
typedef T UnsignedType;
static bool IsNegative(T) { return false; }
};
template <>
struct IntTraits<int> {
typedef unsigned UnsignedType;
static bool IsNegative(int value) { return value < 0; }
};
template <>
struct IntTraits<long> {
typedef unsigned long UnsignedType;
static bool IsNegative(long value) { return value < 0; }
};
class ArgInserter; class ArgInserter;
} }
...@@ -159,19 +180,129 @@ enum Alignment { ...@@ -159,19 +180,129 @@ enum Alignment {
ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC
}; };
struct FormatSpec { // Flags.
Alignment align; enum { SIGN_FLAG = 1, PLUS_FLAG = 2, HASH_FLAG = 4 };
unsigned flags;
unsigned width; struct Spec {};
char type;
char fill; template <char TYPE>
struct TypeSpec : Spec {
Alignment align() const { return ALIGN_DEFAULT; }
unsigned width() const { return 0; }
bool sign_flag() const { return false; }
bool plus_flag() const { return false; }
bool hash_flag() const { return false; }
char type() const { return TYPE; }
char fill() const { return ' '; }
};
struct WidthSpec {
unsigned width_;
char fill_;
WidthSpec(unsigned width, char fill) : width_(width), fill_(fill) {}
unsigned width() const { return width_; }
char fill() const { return fill_; }
};
struct AlignSpec : WidthSpec {
Alignment align_;
AlignSpec(unsigned width, char fill)
: WidthSpec(width, fill), align_(ALIGN_DEFAULT) {}
Alignment align() const { return align_; }
};
template <char TYPE>
struct AlignTypeSpec : AlignSpec {
AlignTypeSpec(unsigned width, char fill) : AlignSpec(width, fill) {}
bool sign_flag() const { return false; }
bool plus_flag() const { return false; }
bool hash_flag() const { return false; }
char type() const { return TYPE; }
};
struct FormatSpec : AlignSpec {
unsigned flags_;
char type_;
FormatSpec(unsigned width = 0, char type = 0, char fill = ' ') FormatSpec(unsigned width = 0, char type = 0, char fill = ' ')
: align(ALIGN_DEFAULT), flags(0), width(width), type(type), fill(fill) {} : AlignSpec(width, fill), flags_(0), type_(type) {}
Alignment align() const { return align_; }
bool sign_flag() const { return (flags_ & SIGN_FLAG) != 0; }
bool plus_flag() const { return (flags_ & PLUS_FLAG) != 0; }
bool hash_flag() const { return (flags_ & HASH_FLAG) != 0; }
char type() const { return type_; }
};
template <typename T, typename Spec>
class IntFormatter : public Spec {
private:
T value_;
public:
IntFormatter(T value, const Spec &spec = Spec())
: Spec(spec), value_(value) {}
T value() const { return value_; }
}; };
inline IntFormatter<int, TypeSpec<'o'> > oct(int value) {
return IntFormatter<int, TypeSpec<'o'> >(value, TypeSpec<'o'>());
}
inline IntFormatter<int, TypeSpec<'x'> > hex(int value) {
return IntFormatter<int, TypeSpec<'x'> >(value, TypeSpec<'x'>());
}
inline IntFormatter<int, TypeSpec<'X'> > hexu(int value) {
return IntFormatter<int, TypeSpec<'X'> >(value, TypeSpec<'X'>());
}
template <char TYPE>
inline IntFormatter<int, AlignTypeSpec<TYPE> > pad(
IntFormatter<int, TypeSpec<TYPE> > f, unsigned width, char fill = ' ') {
return IntFormatter<int, AlignTypeSpec<TYPE> >(
f.value(), AlignTypeSpec<TYPE>(width, fill));
}
inline IntFormatter<int, AlignTypeSpec<0> > pad(
int value, unsigned width, char fill = ' ') {
return IntFormatter<int, AlignTypeSpec<0> >(
value, AlignTypeSpec<0>(width, fill));
}
class BasicFormatter { class BasicFormatter {
private:
static unsigned CountDigits(uint64_t n) {
unsigned count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
static void FormatDecimal(char *buffer, uint64_t value, unsigned num_digits);
protected: protected:
static void ReportUnknownType(char code, const char *type);
enum { INLINE_BUFFER_SIZE = 500 }; enum { INLINE_BUFFER_SIZE = 500 };
mutable internal::Array<char, INLINE_BUFFER_SIZE> buffer_; // Output buffer. mutable internal::Array<char, INLINE_BUFFER_SIZE> buffer_; // Output buffer.
...@@ -183,11 +314,19 @@ class BasicFormatter { ...@@ -183,11 +314,19 @@ class BasicFormatter {
return &buffer_[size]; return &buffer_[size];
} }
char *PrepareFilledBuffer(unsigned size, const FormatSpec &spec, char sign); char *PrepareFilledBuffer(unsigned size, const Spec &, char sign) {
char *p = GrowBuffer(size);
*p = sign;
return p + size - 1;
}
char *PrepareFilledBuffer(unsigned size, const AlignSpec &spec, char sign);
// Formats an integer. // Formats an integer.
template <typename T> template <typename T>
void FormatInt(T value, const FormatSpec &spec); void FormatInt(T value, const FormatSpec &spec) {
*this << IntFormatter<T, FormatSpec>(value, spec);
}
// Formats a floating point number (double or long double). // Formats a floating point number (double or long double).
template <typename T> template <typename T>
...@@ -231,23 +370,103 @@ class BasicFormatter { ...@@ -231,23 +370,103 @@ class BasicFormatter {
*/ */
std::string str() const { return std::string(&buffer_[0], buffer_.size()); } std::string str() const { return std::string(&buffer_[0], buffer_.size()); }
void operator<<(int value); BasicFormatter &operator<<(int value) {
return *this << IntFormatter<int, TypeSpec<0> >(value, TypeSpec<0>());
}
BasicFormatter &operator<<(unsigned value) {
return *this << IntFormatter<unsigned, TypeSpec<0> >(value, TypeSpec<0>());
}
void operator<<(char value) { BasicFormatter &operator<<(char value) {
*GrowBuffer(1) = value; *GrowBuffer(1) = value;
return *this;
} }
void operator<<(const char *value) { BasicFormatter &operator<<(const char *value) {
std::size_t size = std::strlen(value); std::size_t size = std::strlen(value);
std::strncpy(GrowBuffer(size), value, size); std::strncpy(GrowBuffer(size), value, size);
return *this;
} }
BasicFormatter &Write(int value, const FormatSpec &spec) { template <typename T, typename Spec>
FormatInt(value, spec); BasicFormatter &operator<<(const IntFormatter<T, Spec> &f);
return *this;
void Write(const std::string &s, const FormatSpec &spec) {
FormatString(s.data(), s.size(), spec);
}
void Clear() {
buffer_.clear();
} }
}; };
template <typename T, typename Spec>
BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
T value = f.value();
unsigned size = 0;
char sign = 0;
typedef typename internal::IntTraits<T>::UnsignedType UnsignedType;
UnsignedType abs_value = value;
if (internal::IntTraits<T>::IsNegative(value)) {
sign = '-';
++size;
abs_value = 0 - abs_value;
} else if (f.sign_flag()) {
sign = f.plus_flag() ? '+' : ' ';
++size;
}
switch (f.type()) {
case 0: case 'd': {
unsigned num_digits = BasicFormatter::CountDigits(abs_value);
char *p = PrepareFilledBuffer(size + num_digits, f, sign)
- num_digits + 1;
BasicFormatter::FormatDecimal(p, abs_value, num_digits);
break;
}
case 'x': case 'X': {
UnsignedType n = abs_value;
bool print_prefix = f.hash_flag();
if (print_prefix) size += 2;
do {
++size;
} while ((n >>= 4) != 0);
char *p = PrepareFilledBuffer(size, f, sign);
n = abs_value;
const char *digits = f.type() == 'x' ?
"0123456789abcdef" : "0123456789ABCDEF";
do {
*p-- = digits[n & 0xf];
} while ((n >>= 4) != 0);
if (print_prefix) {
*p-- = f.type();
*p = '0';
}
break;
}
case 'o': {
UnsignedType n = abs_value;
bool print_prefix = f.hash_flag();
if (print_prefix) ++size;
do {
++size;
} while ((n >>= 3) != 0);
char *p = PrepareFilledBuffer(size, f, sign);
n = abs_value;
do {
*p-- = '0' + (n & 7);
} while ((n >>= 3) != 0);
if (print_prefix)
*p = '0';
break;
}
default:
BasicFormatter::ReportUnknownType(f.type(), "integer");
break;
}
return *this;
}
/** /**
\rst \rst
The :cpp:class:`format::Formatter` class provides string formatting The :cpp:class:`format::Formatter` class provides string formatting
...@@ -382,7 +601,6 @@ class Formatter : public BasicFormatter { ...@@ -382,7 +601,6 @@ class Formatter : public BasicFormatter {
int next_arg_index_; int next_arg_index_;
friend class internal::ArgInserter; friend class internal::ArgInserter;
friend class ArgFormatter;
void Add(const Arg &arg) { void Add(const Arg &arg) {
args_.push_back(&arg); args_.push_back(&arg);
...@@ -522,34 +740,18 @@ const char *c_str(ArgInserter::Proxy p); ...@@ -522,34 +740,18 @@ const char *c_str(ArgInserter::Proxy p);
using format::internal::str; using format::internal::str;
using format::internal::c_str; using format::internal::c_str;
// ArgFormatter provides access to the format buffer within custom
// Format functions. It is not desirable to pass Formatter to these
// functions because Formatter::operator() is not reentrant and
// therefore can't be used for argument formatting.
class ArgFormatter {
private:
Formatter &formatter_;
public:
explicit ArgFormatter(Formatter &f) : formatter_(f) {}
void Write(const std::string &s, const FormatSpec &spec) {
formatter_.FormatString(s.data(), s.size(), spec);
}
};
// The default formatting function. // The default formatting function.
template <typename T> template <typename T>
void Format(ArgFormatter &af, const FormatSpec &spec, const T &value) { void Format(BasicFormatter &f, const FormatSpec &spec, const T &value) {
std::ostringstream os; std::ostringstream os;
os << value; os << value;
af.Write(os.str(), spec); f.Write(os.str(), spec);
} }
template <typename T> template <typename T>
void Formatter::FormatCustomArg(const void *arg, const FormatSpec &spec) { void Formatter::FormatCustomArg(const void *arg, const FormatSpec &spec) {
ArgFormatter af(*this); BasicFormatter &f = *this;
Format(af, spec, *static_cast<const T*>(arg)); Format(f, spec, *static_cast<const T*>(arg));
} }
inline internal::ArgInserter Formatter::operator()(StringRef format) { inline internal::ArgInserter Formatter::operator()(StringRef format) {
......
...@@ -45,10 +45,15 @@ using std::size_t; ...@@ -45,10 +45,15 @@ using std::size_t;
using std::sprintf; using std::sprintf;
using fmt::internal::Array; using fmt::internal::Array;
using fmt::BasicFormatter;
using fmt::Formatter; using fmt::Formatter;
using fmt::Format; using fmt::Format;
using fmt::FormatError; using fmt::FormatError;
using fmt::StringRef; using fmt::StringRef;
using fmt::hex;
using fmt::hexu;
using fmt::oct;
using fmt::pad;
#define FORMAT_TEST_THROW_(statement, expected_exception, message, fail) \ #define FORMAT_TEST_THROW_(statement, expected_exception, message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
...@@ -844,21 +849,6 @@ TEST(FormatterTest, FormatString) { ...@@ -844,21 +849,6 @@ TEST(FormatterTest, FormatString) {
EXPECT_EQ("test", str(Format("{0}") << std::string("test"))); EXPECT_EQ("test", str(Format("{0}") << std::string("test")));
} }
TEST(ArgFormatterTest, Write) {
Formatter formatter;
fmt::ArgFormatter format(formatter);
fmt::FormatSpec spec;
spec.width = 2;
format.Write("12", spec);
EXPECT_EQ("12", formatter.str());
spec.width = 4;
format.Write("34", spec);
EXPECT_EQ("1234 ", formatter.str());
spec.width = 0;
format.Write("56", spec);
EXPECT_EQ("1234 56", formatter.str());
}
class Date { class Date {
int year_, month_, day_; int year_, month_, day_;
public: public:
...@@ -880,8 +870,8 @@ TEST(FormatterTest, FormatUsingIOStreams) { ...@@ -880,8 +870,8 @@ TEST(FormatterTest, FormatUsingIOStreams) {
class Answer {}; class Answer {};
void Format(fmt::ArgFormatter &af, const fmt::FormatSpec &spec, Answer) { void Format(fmt::BasicFormatter &f, const fmt::FormatSpec &spec, Answer) {
af.Write("42", spec); f.Write("42", spec);
} }
TEST(FormatterTest, CustomFormat) { TEST(FormatterTest, CustomFormat) {
...@@ -1063,6 +1053,33 @@ TEST(TempFormatterTest, Examples) { ...@@ -1063,6 +1053,33 @@ TEST(TempFormatterTest, Examples) {
ReportError("File not found: {0}") << path; ReportError("File not found: {0}") << path;
} }
TEST(StrTest, oct) {
BasicFormatter f;
f << oct(042);
EXPECT_EQ("42", f.str());
}
TEST(StrTest, hex) {
BasicFormatter f;
f << hex(0xbeef);
EXPECT_EQ("beef", f.str());
}
TEST(StrTest, hexu) {
BasicFormatter f;
f << hexu(0xbabe);
EXPECT_EQ("BABE", f.str());
}
TEST(StrTest, pad) {
BasicFormatter f;
f << pad(hex(0xbeef), 8);
EXPECT_EQ(" beef", f.str());
f.Clear();
f << pad(42, 5, '0');
EXPECT_EQ("00042", f.str());
}
template <typename T> template <typename T>
std::string str(const T &value) { std::string str(const T &value) {
return fmt::str(fmt::Format("{0}") << value); return fmt::str(fmt::Format("{0}") << value);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment