From 9a2aae37d4537b3de391ce29b905438cc84b4633 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 2 Sep 2024 09:32:10 -0700 Subject: [PATCH] Cleanup base API --- include/fmt/base.h | 566 ++++++++++++++++++++++----------------------- 1 file changed, 274 insertions(+), 292 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 2d7d3866..4e3fb930 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -439,6 +439,11 @@ FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { return static_cast>(value); } +template +using unsigned_char = typename conditional_t::value, + std::make_unsigned, + type_identity>::type; + // A heuristic to detect std::string and std::[experimental::]string_view. // It is mainly used to avoid dependency on <[experimental/]string_view>. template @@ -500,17 +505,14 @@ template inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> typename OutputIt::container_type& { struct accessor : OutputIt { - accessor(OutputIt base) : OutputIt(base) {} + FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} using OutputIt::container; }; return *accessor(it).container; } } // namespace detail -FMT_BEGIN_EXPORT - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; +FMT_BEGIN_EXPORT // Parsing-related public API and forward declarations. /** * An implementation of `std::basic_string_view` for pre-C++17. It provides a @@ -621,10 +623,16 @@ template <> struct is_char : std::true_type {}; template class basic_appender; using appender = basic_appender; +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + class context; template class generic_context; +template class parse_context; // Longer aliases for C++20 compatibility. +template using basic_format_parse_context = parse_context; +using format_parse_context = parse_context; template using basic_format_context = conditional_t::value, context, @@ -659,6 +667,252 @@ inline auto format_as(T b) -> unsigned char { } #endif +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +enum class align { none, left, right, center, numeric }; +enum class sign { none, minus, plus, space }; +enum class arg_id_kind { none, index, name }; + +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Data is arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + unsigned long data_ = 1 << fill_size_shift; + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(fmt::align a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> arg_id_kind { + return static_cast((data_ & width_mask) >> width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr bool dynamic() const { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(fmt::sign s) { + data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } +}; + +// Format specifiers for built-in and string types. +struct format_specs : basic_specs { + int width; + int precision; + + constexpr format_specs() : width(0), precision(-1) {} +}; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + */ +template class parse_context { + private: + basic_string_view format_str_; + int next_arg_id_; + + enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; + + FMT_CONSTEXPR void do_check_arg_id(int arg_id); + + public: + using char_type = Char; + using iterator = const Char*; + + explicit constexpr parse_context(basic_string_view format_str, + int next_arg_id = 0) + : format_str_(format_str), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { + return format_str_.begin(); + } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return format_str_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + FMT_END_EXPORT namespace detail { @@ -694,11 +948,6 @@ template ()))> using char_t = typename V::value_type; -template -using unsigned_char = typename conditional_t::value, - std::make_unsigned, - type_identity>::type; - enum class type { none_type, // Integer types should go first, @@ -1025,264 +1274,6 @@ template ; -} // namespace detail - -/// Reports a format error at compile time or, via a `format_error` exception, -/// at runtime. -// This function is intentionally not constexpr to give a compile-time error. -FMT_NORETURN FMT_API void report_error(const char* message); - -FMT_DEPRECATED FMT_NORETURN inline void throw_format_error( - const char* message) { - report_error(message); -} - -enum class presentation_type : unsigned char { - // Common specifiers: - none = 0, - debug = 1, // '?' - string = 2, // 's' (string, bool) - - // Integral, bool and character specifiers: - dec = 3, // 'd' - hex, // 'x' or 'X' - oct, // 'o' - bin, // 'b' or 'B' - chr, // 'c' - - // String and pointer specifiers: - pointer = 3, // 'p' - - // Floating-point specifiers: - exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) - fixed, // 'f' or 'F' - general, // 'g' or 'G' - hexfloat // 'a' or 'A' -}; - -enum class align { none, left, right, center, numeric }; -enum class sign { none, minus, plus, space }; -enum class arg_id_kind { none, index, name }; - -// Basic format specifiers for built-in and string types. -class basic_specs { - private: - // Data is arranged as follows: - // - // 0 1 2 3 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // |type |align| w | p | s |u|#|L| f | unused | - // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ - // - // w - dynamic width info - // p - dynamic precision info - // s - sign - // u - uppercase (e.g. 'X' for 'x') - // # - alternate form ('#') - // L - localized - // f - fill size - // - // Bitfields are not used because of compiler bugs such as gcc bug 61414. - enum : unsigned { - type_mask = 0x00007, - align_mask = 0x00038, - width_mask = 0x000C0, - precision_mask = 0x00300, - sign_mask = 0x00C00, - uppercase_mask = 0x01000, - alternate_mask = 0x02000, - localized_mask = 0x04000, - fill_size_mask = 0x38000, - - align_shift = 3, - width_shift = 6, - precision_shift = 8, - sign_shift = 10, - fill_size_shift = 15, - - max_fill_size = 4 - }; - - unsigned long data_ = 1 << fill_size_shift; - - // Character (code unit) type is erased to prevent template bloat. - char fill_data_[max_fill_size] = {' '}; - - FMT_CONSTEXPR void set_fill_size(size_t size) { - data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); - } - - public: - constexpr auto type() const -> presentation_type { - return static_cast(data_ & type_mask); - } - FMT_CONSTEXPR void set_type(presentation_type t) { - data_ = (data_ & ~type_mask) | static_cast(t); - } - - constexpr auto align() const -> align { - return static_cast((data_ & align_mask) >> align_shift); - } - FMT_CONSTEXPR void set_align(fmt::align a) { - data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); - } - - constexpr auto dynamic_width() const -> arg_id_kind { - return static_cast((data_ & width_mask) >> width_shift); - } - FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { - data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); - } - - FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { - return static_cast((data_ & precision_mask) >> - precision_shift); - } - FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { - data_ = (data_ & ~precision_mask) | - (static_cast(p) << precision_shift); - } - - constexpr bool dynamic() const { - return (data_ & (width_mask | precision_mask)) != 0; - } - - constexpr auto sign() const -> sign { - return static_cast((data_ & sign_mask) >> sign_shift); - } - FMT_CONSTEXPR void set_sign(fmt::sign s) { - data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); - } - - constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } - FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } - - constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } - FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } - FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } - - constexpr auto localized() const -> bool { - return (data_ & localized_mask) != 0; - } - FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } - - constexpr auto fill_size() const -> size_t { - return (data_ & fill_size_mask) >> fill_size_shift; - } - - template ::value)> - constexpr auto fill() const -> const Char* { - return fill_data_; - } - template ::value)> - constexpr auto fill() const -> const Char* { - return nullptr; - } - - template constexpr auto fill_unit() const -> Char { - using uchar = unsigned char; - return static_cast(static_cast(fill_data_[0]) | - (static_cast(fill_data_[1]) << 8)); - } - - FMT_CONSTEXPR void set_fill(char c) { - fill_data_[0] = c; - set_fill_size(1); - } - - template - FMT_CONSTEXPR void set_fill(basic_string_view s) { - auto size = s.size(); - set_fill_size(size); - if (size == 1) { - unsigned uchar = static_cast>(s[0]); - fill_data_[0] = static_cast(uchar); - fill_data_[1] = static_cast(uchar >> 8); - return; - } - FMT_ASSERT(size <= max_fill_size, "invalid fill"); - for (size_t i = 0; i < size; ++i) - fill_data_[i & 3] = static_cast(s[i]); - } -}; - -// Format specifiers for built-in and string types. -struct format_specs : basic_specs { - int width; - int precision; - - constexpr format_specs() : width(0), precision(-1) {} -}; - -/** - * Parsing context consisting of a format string range being parsed and an - * argument counter for automatic indexing. - * You can use the `format_parse_context` type alias for `char` instead. - */ -FMT_EXPORT -template class parse_context { - private: - basic_string_view format_str_; - int next_arg_id_; - - FMT_CONSTEXPR void do_check_arg_id(int id); - - public: - using char_type = Char; - using iterator = const Char*; - - explicit constexpr parse_context(basic_string_view format_str, - int next_arg_id = 0) - : format_str_(format_str), next_arg_id_(next_arg_id) {} - - /// Returns an iterator to the beginning of the format string range being - /// parsed. - constexpr auto begin() const noexcept -> iterator { - return format_str_.begin(); - } - - /// Returns an iterator past the end of the format string range being parsed. - constexpr auto end() const noexcept -> iterator { return format_str_.end(); } - - /// Advances the begin iterator to `it`. - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(detail::to_unsigned(it - begin())); - } - - /// Reports an error if using the manual argument indexing; otherwise returns - /// the next argument index and switches to the automatic indexing. - FMT_CONSTEXPR auto next_arg_id() -> int { - if (next_arg_id_ < 0) { - report_error("cannot switch from manual to automatic argument indexing"); - return 0; - } - int id = next_arg_id_++; - do_check_arg_id(id); - return id; - } - - /// Reports an error if using the automatic argument indexing; otherwise - /// switches to the manual indexing. - FMT_CONSTEXPR void check_arg_id(int id) { - if (next_arg_id_ > 0) { - report_error("cannot switch from automatic to manual argument indexing"); - return; - } - next_arg_id_ = -1; - do_check_arg_id(id); - } - FMT_CONSTEXPR void check_arg_id(basic_string_view) { - next_arg_id_ = -1; - } - FMT_CONSTEXPR void check_dynamic_spec(int arg_id); -}; - -FMT_EXPORT -template using basic_format_parse_context = parse_context; -using format_parse_context = parse_context; - -namespace detail { // A parse context with extra data used only in compile-time checks. template class compile_parse_context : public parse_context { @@ -2489,38 +2480,30 @@ inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} #endif } // namespace detail -FMT_BEGIN_EXPORT +FMT_BEGIN_EXPORT // Main public API: template -FMT_CONSTEXPR void parse_context::do_check_arg_id(int id) { - // Argument id is only checked at compile-time during parsing because +FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { + // Argument id is only checked at compile time during parsing because // formatting has its own validation. - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + if (detail::is_constant_evaluated() && use_constexpr_cast) { auto ctx = static_cast*>(this); - if (id >= ctx->num_args()) report_error("argument not found"); + if (arg_id >= ctx->num_args()) report_error("argument not found"); } } template FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - auto ctx = static_cast*>(this); - ctx->check_dynamic_spec(arg_id); - } + using detail::compile_parse_context; + if (detail::is_constant_evaluated() && use_constexpr_cast) + static_cast*>(this)->check_dynamic_spec(arg_id); } // An output iterator that appends to a buffer. It is used instead of // back_insert_iterator to reduce symbol sizes and avoid dependency. template class basic_appender { - private: - detail::buffer* buffer_; - - friend FMT_CONSTEXPR20 auto get_container(basic_appender app) - -> detail::buffer& { - return *app.buffer_; - } + protected: + detail::buffer* container; public: using iterator_category = int; @@ -2531,10 +2514,10 @@ template class basic_appender { using container_type = detail::buffer; FMT_UNCHECKED_ITERATOR(basic_appender); - FMT_CONSTEXPR basic_appender(detail::buffer& buf) : buffer_(&buf) {} + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { - buffer_->push_back(c); + container->push_back(c); return *this; } FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } @@ -2809,15 +2792,14 @@ template class basic_format_string { FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_format_string(const S& s) : str_(s) { using namespace detail; static_assert( - detail::count<(std::is_base_of>::value && - std::is_reference::value)...>() == 0, + count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(S) != 0, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING"); + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); #endif } template