Skip to content

Commit

Permalink
Add variable-width fill support (#1109)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Jan 19, 2020
1 parent 75765bf commit bb98c07
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 19 deletions.
2 changes: 1 addition & 1 deletion include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
}

void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
void on_fill(Char fill) { f.specs.fill[0] = fill; }
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
void on_align(align_t align) { f.specs.align = align; }
void on_width(int width) { f.specs.width = width; }
void on_precision(int _precision) { f.precision = _precision; }
Expand Down
71 changes: 54 additions & 17 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -962,9 +962,24 @@ template <typename T = void> struct null {};
// Workaround an array initialization issue in gcc 4.8.
template <typename Char> struct fill_t {
private:
Char data_[6];
enum { max_size = 5 };
Char data_[max_size + 1];

public:
void operator=(basic_string_view<Char> s) {
if (s.size() > max_size) throw format_error("invalid fill");
std::memcpy(&data_, s.data(), s.size() * sizeof(Char));
data_[s.size()] = Char();
}

size_t size() const {
size_t i = 1;
while (data_[i] && i <= max_size) ++i;
return i;
}

const Char* data() const { return data_; }

FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; }
FMT_CONSTEXPR const Char& operator[](size_t index) const {
return data_[index];
Expand Down Expand Up @@ -1352,6 +1367,16 @@ template <typename Char> struct nonfinite_writer {
}
};

template <typename OutputIt, typename Char>
OutputIt fill(OutputIt it, size_t n, const fill_t<Char>& fill) {
auto fill_size = fill.size();
if (fill_size == 1)
return std::fill_n(it, n, fill[0]);
for (size_t i = 0; i < n; ++i)
it = std::copy_n(fill.data(), fill_size, it);
return it;
}

// This template provides operations for formatting and writing data into a
// character range.
template <typename Range> class basic_writer {
Expand Down Expand Up @@ -1614,20 +1639,20 @@ template <typename Range> class basic_writer {
size_t size = f.size(); // The number of code units.
size_t num_code_points = width != 0 ? f.width() : size;
if (width <= num_code_points) return f(reserve(size));
auto&& it = reserve(width + (size - num_code_points));
char_type fill = specs.fill[0];
std::size_t padding = width - num_code_points;
size_t padding = width - num_code_points;
size_t fill_size = specs.fill.size();
auto&& it = reserve(size + padding * fill_size);
if (specs.align == align::right) {
it = std::fill_n(it, padding, fill);
it = fill(it, padding, specs.fill);
f(it);
} else if (specs.align == align::center) {
std::size_t left_padding = padding / 2;
it = std::fill_n(it, left_padding, fill);
it = fill(it, left_padding, specs.fill);
f(it);
it = std::fill_n(it, padding - left_padding, fill);
it = fill(it, padding - left_padding, specs.fill);
} else {
f(it);
it = std::fill_n(it, padding, fill);
it = fill(it, padding, specs.fill);
}
}

Expand Down Expand Up @@ -2008,7 +2033,9 @@ template <typename Char> class specs_setter {
: specs_(other.specs_) {}

FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; }
FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill[0] = fill; }
FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
specs_.fill = fill;
}
FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; }
FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; }
FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; }
Expand Down Expand Up @@ -2326,10 +2353,17 @@ FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end,
Handler&& handler) {
FMT_ASSERT(begin != end, "");
auto align = align::none;
int i = 0;
if (begin + 1 != end) ++i;
do {
switch (static_cast<char>(begin[i])) {
auto next_code_point = [](const Char* begin, const Char* end) {
if (sizeof(Char) != 1 || (*begin & 0x80) == 0) return begin + 1;
do {
++begin;
} while (begin != end && (*begin & 0xc0) == 0x80);
return begin;
};
auto p = next_code_point(begin, end);
if (p == end) p = begin;
for (;;) {
switch (static_cast<char>(*p)) {
case '<':
align = align::left;
break;
Expand All @@ -2346,18 +2380,21 @@ FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end,
break;
}
if (align != align::none) {
if (i > 0) {
if (p != begin) {
auto c = *begin;
if (c == '{')
return handler.on_error("invalid fill character '{'"), begin;
begin += 2;
handler.on_fill(c);
handler.on_fill(basic_string_view<Char>(begin, p - begin));
begin = p + 1;
} else
++begin;
handler.on_align(align);
break;
} else if (p == begin) {
break;
}
} while (i-- > 0);
p = begin;
}
return begin;
}

Expand Down
3 changes: 2 additions & 1 deletion test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ TEST(FormatterTest, Fill) {
EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast<void*>(0xface)));
EXPECT_EQ("foo=", format("{:}=", "foo"));
EXPECT_EQ(std::string("\0\0\0*", 4), format(string_view("{:\0>4}", 6), '*'));
EXPECT_EQ("жж42", format("{0:ж>4}", 42));
}

TEST(FormatterTest, PlusSign) {
Expand Down Expand Up @@ -2173,7 +2174,7 @@ struct test_format_specs_handler {
type(other.type) {}

FMT_CONSTEXPR void on_align(fmt::align_t a) { align = a; }
FMT_CONSTEXPR void on_fill(char f) { fill = f; }
FMT_CONSTEXPR void on_fill(fmt::string_view f) { fill = f[0]; }
FMT_CONSTEXPR void on_plus() { res = PLUS; }
FMT_CONSTEXPR void on_minus() { res = MINUS; }
FMT_CONSTEXPR void on_space() { res = SPACE; }
Expand Down

0 comments on commit bb98c07

Please sign in to comment.