Refactor the format API

This commit is contained in:
Victor Zverovich 2021-05-17 15:40:59 -07:00
parent 813ac49543
commit 8d70c0edab
3 changed files with 65 additions and 203 deletions

View File

@ -2439,26 +2439,17 @@ FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) {
}
}
template <typename ErrorHandler>
class char_specs_checker : public ErrorHandler {
private:
char type_;
public:
FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh)
: ErrorHandler(eh), type_(type) {}
FMT_CONSTEXPR void on_int() { check_int_type_spec(type_, *this); }
FMT_CONSTEXPR void on_char() {}
};
template <typename Char, typename Handler>
FMT_CONSTEXPR void handle_char_specs(const basic_format_specs<Char>& specs,
Handler&& handler) {
if (specs.type && specs.type != 'c') return handler.on_int();
// Checks char specs and returns true if the type spec is char (and not int).
template <typename Char, typename ErrorHandler>
FMT_CONSTEXPR bool check_char_specs(const basic_format_specs<Char>& specs,
ErrorHandler&& eh) {
if (specs.type && specs.type != 'c') {
check_int_type_spec(specs.type, eh);
return false;
}
if (specs.align == align::numeric || specs.sign != sign::none || specs.alt)
handler.on_error("invalid format specifier for char");
handler.on_char();
eh.on_error("invalid format specifier for char");
return true;
}
// A floating-point presentation format.
@ -2789,8 +2780,7 @@ struct formatter<T, Char,
detail::check_int_type_spec(specs_.type, eh);
break;
case detail::type::char_type:
detail::handle_char_specs(
specs_, detail::char_specs_checker<decltype(eh)>(specs_.type, eh));
detail::check_char_specs(specs_, eh);
break;
case detail::type::float_type:
if (detail::const_check(FMT_USE_FLOAT))
@ -2919,10 +2909,13 @@ FMT_INLINE auto vformat(
}
#if FMT_COMPILE_TIME_CHECKS
template <typename... Args> struct format_string {
string_view str;
template <typename Char, typename... Args> class basic_format_string {
private:
basic_string_view<Char> str;
template <size_t N> consteval format_string(const char (&s)[N]) : str(s) {
public:
template <size_t N>
consteval basic_format_string(const char (&s)[N]) : str(s) {
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<char, detail::error_handler,
remove_cvref_t<Args>...>;
@ -2932,13 +2925,17 @@ template <typename... Args> struct format_string {
template <typename T,
FMT_ENABLE_IF(std::is_constructible_v<string_view, const T&>)>
format_string(const T& s) : str(s) {}
basic_format_string(const T& s) : str(s) {}
operator basic_string_view<Char>() const { return str; }
};
template <typename... Args>
FMT_INLINE std::string format(
format_string<std::type_identity_t<Args>...> format_str, Args&&... args) {
return detail::vformat(format_str.str, make_format_args(args...));
using format_string = basic_format_string<char, std::type_identity_t<Args>...>;
template <typename... T>
FMT_INLINE auto format(format_string<T...> str, T&&... args) -> std::string {
return detail::vformat(str, make_format_args(args...));
}
#endif

View File

@ -346,6 +346,8 @@ template <typename T> using checked_ptr = T*;
template <typename T> inline T* make_checked(T* p, size_t) { return p; }
#endif
// Attempts to reserve space for n extra characters in the output range.
// Returns a pointer to the reserved range or a reference to it.
template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
__attribute__((no_sanitize("undefined")))
@ -1304,7 +1306,7 @@ template <typename Char, typename OutputIt>
FMT_CONSTEXPR OutputIt write(OutputIt out, Char value,
const basic_format_specs<Char>& specs,
locale_ref loc = {}) {
return !specs.type || specs.type == 'c'
return check_char_specs(specs, error_handler())
? write_char(out, value, specs)
: write(out, static_cast<int>(value), specs, loc);
}
@ -1807,7 +1809,8 @@ inline OutputIt write(OutputIt out, T value) {
}
template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, monostate) {
OutputIt write(OutputIt out, monostate, basic_format_specs<Char> = {},
locale_ref = {}) {
FMT_ASSERT(false, "");
return out;
}
@ -1927,152 +1930,32 @@ template <typename OutputIt, typename Char> struct default_arg_formatter {
basic_format_args<context> args;
locale_ref loc;
template <typename T> OutputIt operator()(T value) {
template <typename T> auto operator()(T value) -> OutputIt {
return write<Char>(out, value);
}
OutputIt operator()(typename basic_format_arg<context>::handle handle) {
auto operator()(typename basic_format_arg<context>::handle h) -> OutputIt {
basic_format_parse_context<Char> parse_ctx({});
basic_format_context<OutputIt, Char> format_ctx(out, args, loc);
handle.format(parse_ctx, format_ctx);
h.format(parse_ctx, format_ctx);
return format_ctx.out();
}
};
template <typename OutputIt, typename Char,
typename ErrorHandler = error_handler>
class arg_formatter_base {
public:
using iterator = OutputIt;
using char_type = Char;
using format_specs = basic_format_specs<Char>;
template <typename OutputIt, typename Char> struct arg_formatter {
using context = basic_format_context<OutputIt, Char>;
private:
iterator out_;
const format_specs& specs_;
locale_ref locale_;
OutputIt out;
const basic_format_specs<Char>& specs;
locale_ref locale;
// Attempts to reserve space for n extra characters in the output range.
// Returns a pointer to the reserved range or a reference to out_.
auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) {
return detail::reserve(out_, n);
template <typename T>
FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> OutputIt {
return detail::write(out, value, specs, locale);
}
void write(char value) {
auto&& it = reserve(1);
*it++ = value;
}
template <typename Ch, FMT_ENABLE_IF(std::is_same<Ch, Char>::value)>
void write(Ch value) {
out_ = detail::write<Char>(out_, value);
}
void write(string_view value) {
auto&& it = reserve(value.size());
it = copy_str<Char>(value.begin(), value.end(), it);
}
void write(wstring_view value) {
static_assert(std::is_same<Char, wchar_t>::value, "");
auto&& it = reserve(value.size());
it = copy_str<Char>(value.begin(), value.end(), it);
}
template <typename Ch>
void write(const Ch* s, size_t size, const format_specs& specs) {
auto width =
specs.width != 0 ? compute_width(basic_string_view<Ch>(s, size)) : 0;
out_ = write_padded(out_, specs, size, width,
[=](reserve_iterator<OutputIt> it) {
return copy_str<Char>(s, s + size, it);
});
}
template <typename Ch>
FMT_CONSTEXPR void write(basic_string_view<Ch> s,
const format_specs& specs = {}) {
out_ = detail::write(out_, s, specs);
}
struct char_spec_handler : ErrorHandler {
arg_formatter_base& formatter;
Char value;
constexpr char_spec_handler(arg_formatter_base& f, Char val)
: formatter(f), value(val) {}
FMT_CONSTEXPR void on_int() {
// char is only formatted as int if there are specs.
formatter.out_ = detail::write(formatter.out_, static_cast<int>(value),
formatter.specs_, formatter.locale_);
}
FMT_CONSTEXPR void on_char() {
formatter.out_ =
detail::write<Char>(formatter.out_, value, formatter.specs_);
}
};
protected:
iterator out() { return out_; }
const format_specs& specs() { return specs_; }
FMT_CONSTEXPR void write(bool value) {
write(string_view(value ? "true" : "false"), specs_);
}
void write(const Char* value) {
if (value)
write(basic_string_view<char_type>(value), specs_);
else
FMT_THROW(format_error("string pointer is null"));
}
public:
constexpr arg_formatter_base(OutputIt out, const format_specs& s,
locale_ref loc)
: out_(out), specs_(s), locale_(loc) {}
iterator operator()(monostate) {
FMT_ASSERT(false, "invalid argument type");
return out_;
}
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
FMT_CONSTEXPR FMT_INLINE iterator operator()(T value) {
return out_ = detail::write(out_, value, specs_, locale_);
}
FMT_CONSTEXPR iterator operator()(Char value) {
handle_char_specs(specs_,
char_spec_handler(*this, static_cast<Char>(value)));
return out_;
}
FMT_CONSTEXPR iterator operator()(bool value) {
if (specs_.type && specs_.type != 's') return (*this)(value ? 1 : 0);
write(value != 0);
return out_;
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
iterator operator()(T value) {
if (const_check(is_supported_floating_point(value)))
out_ = detail::write(out_, value, specs_, locale_);
else
FMT_ASSERT(false, "unsupported float argument type");
return out_;
}
iterator operator()(const Char* value) {
return out_ = detail::write(out_, value, specs_, locale_);
}
FMT_CONSTEXPR iterator operator()(basic_string_view<Char> value) {
return out_ = detail::write(out_, value, specs_, locale_);
}
iterator operator()(const void* value) {
return out_ = detail::write(out_, value, specs_, locale_);
auto operator()(typename basic_format_arg<context>::handle) -> OutputIt {
// User-defined types are handled separately because they require access
// to the parse context.
return out;
}
};
@ -2247,25 +2130,9 @@ struct format_handler : detail::error_handler {
arg.type());
begin = parse_format_specs(begin, end, handler);
if (begin == end || *begin != '}') on_error("missing '}' in format string");
using arg_formatter_base = detail::arg_formatter_base<OutputIt, Char>;
struct arg_formatter : arg_formatter_base {
Context& ctx_;
FMT_CONSTEXPR explicit arg_formatter(
Context& ctx, const typename arg_formatter_base::format_specs& specs)
: arg_formatter_base(ctx.out(), specs, ctx.locale()), ctx_(ctx) {}
using arg_formatter_base::operator();
auto operator()(typename basic_format_arg<Context>::handle) ->
typename arg_formatter_base::iterator {
// User-defined types are handled separately because they require access
// to the parse context.
return ctx_.out();
}
};
context.advance_to(visit_format_arg(arg_formatter(context, specs), arg));
auto f = detail::arg_formatter<OutputIt, Char>{context.out(), specs,
context.locale()};
context.advance_to(visit_format_arg(f, arg));
return begin;
}
};
@ -2631,8 +2498,8 @@ struct formatter<arg_join<It, Sentinel, Char>, Char> {
};
/**
Returns an object that formats the iterator range `[begin, end)` with elements
separated by `sep`.
Returns an object that formats the iterator range `[begin, end)` with
elements separated by `sep`.
*/
template <typename It, typename Sentinel>
arg_join<It, Sentinel, char> join(It begin, Sentinel end, string_view sep) {
@ -2692,8 +2559,8 @@ inline std::string to_string(const T& value) {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline std::string to_string(T value) {
// The buffer should be large enough to store the number including the sign or
// "false" for bool.
// The buffer should be large enough to store the number including the sign
// or "false" for bool.
constexpr int max_size = detail::digits10<T>() + 2;
char buffer[max_size > 5 ? static_cast<unsigned>(max_size) : 5];
char* begin = buffer;

View File

@ -191,33 +191,32 @@ template <typename Char> class printf_width_handler {
// The ``printf`` argument formatter.
template <typename OutputIt, typename Char>
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
class printf_arg_formatter : public arg_formatter<OutputIt, Char> {
private:
using base = detail::arg_formatter_base<OutputIt, Char>;
using base = arg_formatter<OutputIt, Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using format_specs = typename base::format_specs;
using format_specs = basic_format_specs<Char>;
context_type& context_;
OutputIt write_null_pointer(bool is_string = false) {
auto s = this->specs();
auto s = this->specs;
s.type = 0;
return detail::write(this->out(),
string_view(is_string ? "(null)" : "(nil)"), s);
return write(this->out, string_view(is_string ? "(null)" : "(nil)"), s);
}
public:
printf_arg_formatter(OutputIt iter, format_specs& specs, context_type& ctx)
: base(iter, specs, detail::locale_ref()), context_(ctx) {}
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
: base{iter, s, locale_ref()}, context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); }
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
OutputIt operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs();
format_specs fmt_specs = this->specs;
if (fmt_specs.type && fmt_specs.type != 'c')
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
@ -227,8 +226,7 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
return detail::write<Char>(this->out(), static_cast<Char>(value),
fmt_specs);
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
return base::operator()(value);
}
@ -241,13 +239,13 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
/** Formats a null-terminated C string. */
OutputIt operator()(const char* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs().type != 'p');
return write_null_pointer(this->specs.type != 'p');
}
/** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs().type != 'p');
return write_null_pointer(this->specs.type != 'p');
}
OutputIt operator()(basic_string_view<Char> value) {
@ -262,7 +260,7 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
/** Formats an argument of a custom (user-defined) type. */
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
handle.format(context_.parse_context(), context_);
return this->out();
return this->out;
}
};