Skip to content

Commit

Permalink
Merge branch 'fmtlib:master' into zig-pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
kassane authored Sep 20, 2023
2 parents 3b8ec2e + a3a74fa commit 403427f
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 76 deletions.
132 changes: 68 additions & 64 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ with the same format specifiers. The ``format_as`` function should take an
object of your type and return an object of a formattable type. It should be
defined in the same namespace as your type.

Example (https://godbolt.org/z/r7vvGE1v7)::
Example (https://godbolt.org/z/nvME4arz8)::

#include <fmt/format.h>

Expand All @@ -123,67 +123,13 @@ Example (https://godbolt.org/z/r7vvGE1v7)::
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
}

Using the specialization API is more complex but gives you full control over
parsing and formatting. To use this method specialize the ``formatter`` struct
template for your type and implement ``parse`` and ``format`` methods.
For example::

#include <fmt/core.h>
Using specialization is more complex but gives you full control over parsing and
formatting. To use this method specialize the ``formatter`` struct template for
your type and implement ``parse`` and ``format`` methods.

struct point {
double x, y;
};

template <> struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f';

// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator {
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Please also note that this character range may be empty, in case of
// the "{}" format string, so therefore you should check ctx.begin()
// for equality with ctx.end().

// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;

// Check if reached the end of the range:
if (it != end && *it != '}') throw_format_error("invalid format");

// Return an iterator past the end of the parsed range:
return it;
}

// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
auto format(const point& p, format_context& ctx) const -> format_context::iterator {
// ctx.out() is an output iterator to write to.
return presentation == 'f'
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
: fmt::format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
}
};

Then you can pass objects of type ``point`` to any formatting function::

point p = {1, 2};
std::string s = fmt::format("{:f}", p);
// s == "(1.0, 2.0)"

You can also reuse existing formatters via inheritance or composition, for
example::
The recommended way of defining a formatter is by reusing an existing one via
inheritance or composition. This way you can support standard format specifiers
without implementing them yourself. For example::

// color.h:
#include <fmt/core.h>
Expand Down Expand Up @@ -211,16 +157,74 @@ example::
}

Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so
it has to be included in the source file.
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
all string format specifications, for example
it has to be included in the source file. Since ``parse`` is inherited from
``formatter<string_view>`` it will recognize all string format specifications,
for example

.. code-block:: c++

fmt::format("{:>10}", color::blue)

will return ``" blue"``.

The experimental ``nested_formatter`` provides an easy way applying a formatter
to one or more subobjects.

For example::

#include <fmt/format.h>

struct point {
double x, y;
};

template <>
struct fmt::formatter<point> : nested_formatter<double> {
auto format(point p, format_context& ctx) const {
return write_padded(ctx, [=](auto out) {
return format_to(out, "({}, {})", nested(p.x), nested(p.y));
});
}
};

int main() {
fmt::print("[{:>20.2f}]", point{1, 2});
}

prints::

[ (1.00, 2.00)]

Notice that fill, align and width are applied to the whole object which is the
recommended behavior while the remaining specifiers apply to elements.

In general the formatter has the following form::

template <> struct fmt::formatter<T> {
// Parses format specifiers and stores them in the formatter.
//
// [ctx.begin(), ctx.end()) is a, possibly empty, character range that
// contains a part of the format string starting from the format
// specifications to be parsed, e.g. in
//
// fmt::format("{:f} continued", ...);
//
// the range will contain "f} continued". The formatter should parse
// specifiers until '}' or the end of the range. In this example the
// formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
constexpr auto parse(format_parse_context& ctx)
-> format_parse_context::iterator;

// Formats value using the parsed format specification stored in this
// formatter and writes the output to ctx.out().
auto format(const T& value, format_context& ctx) const
-> format_context::iterator;
};

It is recommended to at least support fill, align and width that apply to the
whole object and have the same semantics as in standard formatters.

You can also write a formatter for a hierarchy of classes::

// demo.h:
Expand Down
17 changes: 13 additions & 4 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -2302,9 +2302,12 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(
dynamic_format_specs<Char>& specs;
type arg_type;

FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* {
if (!in(arg_type, set)) throw_format_error("invalid format specifier");
specs.type = type;
FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* {
if (!in(arg_type, set)) {
if (arg_type == type::none_type) return begin;
throw_format_error("invalid format specifier");
}
specs.type = pres_type;
return begin + 1;
}
} parse_presentation_type{begin, specs, arg_type};
Expand All @@ -2321,6 +2324,7 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(
case '+':
case '-':
case ' ':
if (arg_type == type::none_type) return begin;
enter_state(state::sign, in(arg_type, sint_set | float_set));
switch (c) {
case '+':
Expand All @@ -2336,14 +2340,17 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(
++begin;
break;
case '#':
if (arg_type == type::none_type) return begin;
enter_state(state::hash, is_arithmetic_type(arg_type));
specs.alt = true;
++begin;
break;
case '0':
enter_state(state::zero);
if (!is_arithmetic_type(arg_type))
if (!is_arithmetic_type(arg_type)) {
if (arg_type == type::none_type) return begin;
throw_format_error("format specifier requires numeric argument");
}
if (specs.align == align::none) {
// Ignore 0 if align is specified for compatibility with std::format.
specs.align = align::numeric;
Expand All @@ -2365,12 +2372,14 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(
begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx);
break;
case '.':
if (arg_type == type::none_type) return begin;
enter_state(state::precision,
in(arg_type, float_set | string_set | cstring_set));
begin = parse_precision(begin, end, specs.precision, specs.precision_ref,
ctx);
break;
case 'L':
if (arg_type == type::none_type) return begin;
enter_state(state::locale, is_arithmetic_type(arg_type));
specs.localized = true;
++begin;
Expand Down
26 changes: 24 additions & 2 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -3033,7 +3033,7 @@ class bigint {
bigits_.resize(to_unsigned(num_bigits + exp_difference));
for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
bigits_[j] = bigits_[i];
std::uninitialized_fill_n(bigits_.data(), exp_difference, 0);
std::uninitialized_fill_n(bigits_.data(), exp_difference, 0u);
exp_ -= exp_difference;
}

Expand Down Expand Up @@ -4222,13 +4222,35 @@ struct formatter<nested_view<T>> {
template <typename T>
struct nested_formatter {
private:
int width_;
detail::fill_t<char> fill_;
align_t align_ : 4;
formatter<T> formatter_;

public:
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> const char* {
auto specs = detail::dynamic_format_specs<char>();
auto it = parse_format_specs(
ctx.begin(), ctx.end(), specs, ctx, detail::type::none_type);
width_ = specs.width;
fill_ = specs.fill;
align_ = specs.align;
ctx.advance_to(it);
return formatter_.parse(ctx);
}

template <typename F>
auto write_padded(format_context& ctx, F write) const -> decltype(ctx.out()) {
if (width_ == 0) return write(ctx.out());
auto buf = memory_buffer();
write(std::back_inserter(buf));
auto specs = format_specs<>();
specs.width = width_;
specs.fill = fill_;
specs.align = align_;
return detail::write(ctx.out(), string_view(buf.data(), buf.size()), specs);
}

auto nested(const T& value) const -> nested_view<T> {
return nested_view<T>{&formatter_, &value};
}
Expand Down
2 changes: 1 addition & 1 deletion include/fmt/ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ struct formatter<detail::streamed_view<T>, Char>
\endrst
*/
template <typename T>
auto streamed(const T& value) -> detail::streamed_view<T> {
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}

Expand Down
11 changes: 7 additions & 4 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1779,22 +1779,25 @@ TEST(format_test, group_digits_view) {
EXPECT_EQ(fmt::format("{:8}", fmt::group_digits(1000)), " 1,000");
}

#ifdef __cpp_generic_lambdas
struct point {
double x, y;
};

FMT_BEGIN_NAMESPACE
template <>
struct formatter<point> : nested_formatter<double> {
template <> struct formatter<point> : nested_formatter<double> {
auto format(point p, format_context& ctx) const -> decltype(ctx.out()) {
return format_to(ctx.out(), "({}, {})", nested(p.x), nested(p.y));
return write_padded(ctx, [this, p](auto out) -> decltype(out) {
return format_to(out, "({}, {})", nested(p.x), nested(p.y));
});
}
};
FMT_END_NAMESPACE

TEST(format_test, nested_formatter) {
EXPECT_EQ(fmt::format("{:.2f}", point{1, 2}), "(1.00, 2.00)");
EXPECT_EQ(fmt::format("{:>16.2f}", point{1, 2}), " (1.00, 2.00)");
}
#endif // __cpp_generic_lambdas

enum test_enum { foo, bar };
auto format_as(test_enum e) -> int { return e; }
Expand Down
62 changes: 61 additions & 1 deletion test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,71 @@ struct custom_char {
template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {}

operator char() const {
constexpr operator char() const {
return value <= 0xff ? static_cast<char>(value) : '\0';
}
constexpr bool operator<(custom_char c) const { return value < c.value; }
};

namespace std {

template <> struct char_traits<custom_char> {
using char_type = custom_char;
using int_type = int;
using off_type = streamoff;
using pos_type = streampos;
using state_type = mbstate_t;

static constexpr void assign(char_type& r, const char_type& a) { r = a; }
static constexpr bool eq(char_type a, char_type b) { return a == b; }
static constexpr bool lt(char_type a, char_type b) { return a < b; }
static FMT_CONSTEXPR int compare(const char_type* s1, const char_type* s2,
size_t count) {
for (; count; count--, s1++, s2++) {
if (lt(*s1, *s2)) return -1;
if (lt(*s2, *s1)) return 1;
}
return 0;
}
static FMT_CONSTEXPR size_t length(const char_type* s) {
size_t count = 0;
while (!eq(*s++, custom_char(0))) count++;
return count;
}
static const char_type* find(const char_type*, size_t, const char_type&);
static FMT_CONSTEXPR char_type* move(char_type* dest, const char_type* src,
size_t count) {
if (count == 0) return dest;
char_type* ret = dest;
if (src < dest) {
dest += count;
src += count;
for (; count; count--) assign(*--dest, *--src);
} else if (src > dest)
copy(dest, src, count);
return ret;
}
static FMT_CONSTEXPR char_type* copy(char_type* dest, const char_type* src,
size_t count) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, *src++);
return ret;
}
static FMT_CONSTEXPR char_type* assign(char_type* dest, std::size_t count,
char_type a) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, a);
return ret;
}
static int_type not_eof(int_type);
static char_type to_char_type(int_type);
static int_type to_int_type(char_type);
static bool eq_int_type(int_type, int_type);
static int_type eof();
};

} // namespace std

auto to_ascii(custom_char c) -> char { return c; }

FMT_BEGIN_NAMESPACE
Expand Down

0 comments on commit 403427f

Please sign in to comment.