From fec5515c55f85143077806bf01650c23b08d3751 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 2 Sep 2022 17:27:19 -0700 Subject: [PATCH 01/11] num_format_facet -> format_facet --- include/fmt/core.h | 2 +- include/fmt/format-inl.h | 10 +++++----- include/fmt/format.h | 35 +++++++++++++---------------------- test/format-test.cc | 31 +++++++++++++++++++++---------- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 9dfcd2d6..a4b200d7 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -332,7 +332,7 @@ struct monostate { #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif FMT_BEGIN_DETAIL_NAMESPACE diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 6fbdb52a..0591e5af 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -29,7 +29,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE -template typename Locale::id num_format_facet::id; +template typename Locale::id format_facet::id; namespace detail { @@ -118,15 +118,15 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { } #endif -FMT_FUNC auto write_int(appender out, loc_value value, +FMT_FUNC auto write_int(appender out, basic_format_arg value, const format_specs& specs, locale_ref loc) -> bool { #ifndef FMT_STATIC_THOUSANDS_SEPARATOR auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. - if (!std::has_facet>(locale)) return {}; - std::use_facet>(locale).put(out, value, specs, - locale); + if (!std::has_facet>(locale)) return {}; + std::use_facet>(locale).put(out, value, specs, + locale); return true; #endif return false; diff --git a/include/fmt/format.h b/include/fmt/format.h index a1b98c7f..dc5108f9 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -985,27 +985,20 @@ constexpr auto compile_string_to_view(detail::std_string_view s) } } // namespace detail_exported -// A value to localize. -struct loc_value { - union { - unsigned long long ulong_long_value; - }; -}; - -// A locale facet that formats numeric values in UTF-8. -// It is parameterized on the locale to avoid heavy include. -template class num_format_facet : public Locale::facet { +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { public: static FMT_API typename Locale::id id; - void put(appender out, loc_value val, const format_specs& specs, - Locale& loc) const { + void put(appender out, basic_format_arg val, + const format_specs& specs, Locale& loc) const { do_put(out, val, specs, loc); } protected: - virtual void do_put(appender out, loc_value val, const format_specs& specs, - Locale& loc) const = 0; + virtual void do_put(appender out, basic_format_arg val, + const format_specs& specs, Locale& loc) const = 0; }; FMT_BEGIN_DETAIL_NAMESPACE @@ -2037,23 +2030,21 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, }); } -FMT_API auto write_int(appender out, loc_value value, const format_specs& specs, - locale_ref loc) -> bool; +FMT_API auto write_int(appender out, basic_format_arg value, + const format_specs& specs, locale_ref loc) -> bool; template -inline auto write_int(OutputIt, loc_value, const basic_format_specs&, - locale_ref) -> bool { +inline auto write_int(OutputIt, basic_format_arg>, + const basic_format_specs&, locale_ref) -> bool { return false; } template auto write_int(OutputIt& out, UInt value, unsigned prefix, const basic_format_specs& specs, locale_ref loc) -> bool { - auto result = false; auto buf = memory_buffer(); - if (sizeof(value) <= sizeof(unsigned long long)) - result = write_int(appender(buf), {static_cast(value)}, + auto written = write_int(appender(buf), make_arg>(value), specs, loc); - if (!result) { + if (!written) { auto grouping = digit_grouping(loc); out = write_int(out, value, prefix, specs, grouping); return true; diff --git a/test/format-test.cc b/test/format-test.cc index b028aefd..08dadfe3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2312,25 +2312,36 @@ TEST(format_int_test, format_int) { EXPECT_EQ(os.str(), fmt::format_int(max_value()).str()); } -#ifdef FMT_STATIC_THOUSANDS_SEPARATOR +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR # include -class num_format : public fmt::num_format_facet { +class format_facet : public fmt::format_facet { protected: - void do_put(fmt::appender out, fmt::loc_value, const fmt::format_specs&, - std::locale&) const override; + struct int_formatter { + fmt::appender out; + + template ::value)> + void operator()(T value) { + fmt::format_to(out, "[{}]", value); + } + template ::value)> + void operator()(T) {} + }; + + void do_put(fmt::appender out, fmt::basic_format_arg arg, + const fmt::format_specs&, std::locale&) const override; }; -void num_format::do_put(fmt::appender out, fmt::loc_value value, - const fmt::format_specs&, std::locale&) const { - fmt::format_to(out, "[{}]", value.ulong_long_value); +void format_facet::do_put(fmt::appender out, + fmt::basic_format_arg arg, + const fmt::format_specs&, std::locale&) const { + visit_format_arg(int_formatter{out}, arg); } -TEST(format_test, num_format) { - auto loc = std::locale(std::locale(), new num_format()); +TEST(format_test, format_facet) { + auto loc = std::locale(std::locale(), new format_facet()); EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]"); - EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]"); } #endif // FMT_STATIC_THOUSANDS_SEPARATOR From 29c6000137dd94151b61e34fc9f658304878d71e Mon Sep 17 00:00:00 2001 From: NewbieOrange Date: Sat, 3 Sep 2022 12:08:07 +0800 Subject: [PATCH 02/11] Simplify is_variant_like_ check, fix compile error before GCC 11 (#3072) Co-authored-by: Vladislav Shchapov Co-authored-by: Vladislav Shchapov --- include/fmt/std.h | 10 ++++------ test/std-test.cc | 3 +++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index c8b042a4..f1d1d120 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -101,14 +101,12 @@ template using variant_index_sequence = std::make_index_sequence::value>; -// variant_size and variant_alternative check. -template +template struct is_variant_like_ : std::false_type {}; -template -struct is_variant_like_::value)>> - : std::true_type {}; +template +struct is_variant_like_> : std::true_type {}; -// formattable element check +// formattable element check. template class is_variant_formattable_ { template static std::conjunction< diff --git a/test/std-test.cc b/test/std-test.cc index b9c15e02..77cf7934 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -75,6 +75,9 @@ TEST(std_test, variant) { EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)"); EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")"); + + volatile int i = 42; // Test compile error before GCC 11 described in #3068. + EXPECT_EQ(fmt::format("{}", i), "42"); #endif } From aec3bb5d0a0f01a8bdf97b6a68fc2458732a9b0d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 3 Sep 2022 06:35:55 -0700 Subject: [PATCH 03/11] Workaround C complex.h idiocy --- include/fmt/ranges.h | 14 +++++++------- include/fmt/std.h | 9 ++++----- test/core-test.cc | 2 -- test/enforce-checks-test.cc | 2 ++ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index dea7d60d..2105a668 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -211,16 +211,16 @@ class is_tuple_formattable_ { static constexpr const bool value = false; }; template class is_tuple_formattable_ { - template - static std::true_type check2(index_sequence, - integer_sequence); + template + static std::true_type check2(index_sequence, + integer_sequence); static std::false_type check2(...); - template + template static decltype(check2( - index_sequence{}, + index_sequence{}, integer_sequence< - bool, (is_formattable::type, - C>::value)...>{})) check(index_sequence); + bool, (is_formattable::type, + C>::value)...>{})) check(index_sequence); public: static constexpr const bool value = diff --git a/include/fmt/std.h b/include/fmt/std.h index f1d1d120..63dbebe3 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -101,17 +101,16 @@ template using variant_index_sequence = std::make_index_sequence::value>; -template -struct is_variant_like_ : std::false_type {}; +template struct is_variant_like_ : std::false_type {}; template struct is_variant_like_> : std::true_type {}; // formattable element check. template class is_variant_formattable_ { - template + template static std::conjunction< - is_formattable, C>...> - check(std::index_sequence); + is_formattable, C>...> + check(std::index_sequence); public: static constexpr const bool value = diff --git a/test/core-test.cc b/test/core-test.cc index c76dc161..96cde1ed 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -9,9 +9,7 @@ #include "test-assert.h" // clang-format on -#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I #include "fmt/core.h" -#undef I #include // std::copy_n #include // INT_MAX diff --git a/test/enforce-checks-test.cc b/test/enforce-checks-test.cc index c77cb142..960a7fcd 100644 --- a/test/enforce-checks-test.cc +++ b/test/enforce-checks-test.cc @@ -8,12 +8,14 @@ #include #include +#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I #include "fmt/chrono.h" #include "fmt/color.h" #include "fmt/format.h" #include "fmt/ostream.h" #include "fmt/ranges.h" #include "fmt/xchar.h" +#undef I // Exercise the API to verify that everything we expect to can compile. void test_format_api() { From 91ecb38a34e5323193417265c74f0fe8d4f8b4f6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 3 Sep 2022 07:01:11 -0700 Subject: [PATCH 04/11] Localize negative integers --- include/fmt/format.h | 23 ++++++----------------- test/format-test.cc | 1 + 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index dc5108f9..32ae14dc 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2041,23 +2041,8 @@ inline auto write_int(OutputIt, basic_format_arg>, template auto write_int(OutputIt& out, UInt value, unsigned prefix, const basic_format_specs& specs, locale_ref loc) -> bool { - auto buf = memory_buffer(); - auto written = write_int(appender(buf), make_arg>(value), - specs, loc); - if (!written) { - auto grouping = digit_grouping(loc); - out = write_int(out, value, prefix, specs, grouping); - return true; - } - size_t size = to_unsigned((prefix != 0 ? 1 : 0) + buf.size()); - out = write_padded( - out, specs, size, size, [&](reserve_iterator it) { - if (prefix != 0) { - char sign = static_cast(prefix); - *it++ = static_cast(sign); - } - return copy_str(buf.data(), buf.data() + buf.size(), it); - }); + auto grouping = digit_grouping(loc); + out = write_int(out, value, prefix, specs, grouping); return true; } @@ -4168,6 +4153,10 @@ void vformat_to(buffer& buf, basic_string_view fmt, begin = parse_format_specs(begin, end, handler); if (begin == end || *begin != '}') on_error("missing '}' in format string"); + if (specs.localized && arg.is_integral() && + write_int(context.out(), arg, specs, context.locale())) { + return begin; + } auto f = arg_formatter{context.out(), specs, context.locale()}; context.advance_to(visit_format_arg(f, arg)); return begin; diff --git a/test/format-test.cc b/test/format-test.cc index 08dadfe3..ee82bd9e 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2342,6 +2342,7 @@ void format_facet::do_put(fmt::appender out, TEST(format_test, format_facet) { auto loc = std::locale(std::locale(), new format_facet()); EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]"); + EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]"); } #endif // FMT_STATIC_THOUSANDS_SEPARATOR From 768d79a8397cd86e60f3e739a7f6f810f2505391 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 3 Sep 2022 08:10:59 -0700 Subject: [PATCH 05/11] Implement format_facet --- include/fmt/format-inl.h | 28 +++++++++++++++++++++-- include/fmt/format.h | 48 ++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 0591e5af..9ce96b1d 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -29,8 +29,6 @@ #include "format.h" FMT_BEGIN_NAMESPACE -template typename Locale::id format_facet::id; - namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { @@ -132,8 +130,34 @@ FMT_FUNC auto write_int(appender out, basic_format_arg value, return false; } +struct localize_int { + appender out; + const format_specs& specs; + locale_ref loc; + + template ::value)> + void operator()(T value) { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(loc)); + } + template ::value)> + void operator()(T) {} +}; } // namespace detail +template typename Locale::id format_facet::id; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template <> +FMT_API FMT_FUNC void format_facet::do_put( + appender out, basic_format_arg val, + const format_specs& specs, std::locale& loc) const { + visit_format_arg(detail::localize_int{out, specs, detail::locale_ref(loc)}, + val); +} +#endif + #if !FMT_MSC_VERSION FMT_API FMT_FUNC format_error::~format_error() noexcept = default; #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index 32ae14dc..f03a59f9 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -988,17 +988,23 @@ constexpr auto compile_string_to_view(detail::std_string_view s) // A locale facet that formats values in UTF-8. // It is parameterized on the locale to avoid the heavy include. template class format_facet : public Locale::facet { + private: + std::string separator_; + + protected: + virtual void do_put(appender out, basic_format_arg val, + const format_specs& specs, Locale& loc) const; + public: static FMT_API typename Locale::id id; + explicit format_facet(string_view sep = ",") + : separator_(sep.data(), sep.size()) {} + void put(appender out, basic_format_arg val, const format_specs& specs, Locale& loc) const { do_put(out, val, specs, loc); } - - protected: - virtual void do_put(appender out, basic_format_arg val, - const format_specs& specs, Locale& loc) const = 0; }; FMT_BEGIN_DETAIL_NAMESPACE @@ -1949,19 +1955,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: - thousands_sep_result sep_; + std::string grouping_; + std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + next_state initial_state() const { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; @@ -1970,14 +1976,16 @@ template class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep(loc); - else - sep_.thousands_sep = Char(); + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + explicit digit_grouping(thousands_sep_result sep) + : grouping_(sep.grouping), + thousands_sep_(sep.thousands_sep ? 1 : 0, sep.thousands_sep) {} - Char separator() const { return sep_.thousands_sep; } + bool has_separator() const { return !thousands_sep_.empty(); } int count_separators(int num_digits) const { int count = 0; @@ -2000,7 +2008,9 @@ template class digit_grouping { for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - *out++ = separator(); + out = + copy_str(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); @@ -2307,7 +2317,7 @@ template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } @@ -2369,7 +2379,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } @@ -2492,7 +2502,7 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr Char separator() const { return Char(); } + constexpr bool has_separator() const { return false; } constexpr int count_separators(int) const { return 0; } From 1b94271ff69f1159abb0907dd5a18700531b2922 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 3 Sep 2022 10:35:57 -0700 Subject: [PATCH 06/11] Add support for UTF-8 digit separators --- include/fmt/format-inl.h | 12 +++++------- include/fmt/format.h | 13 ++++++------- test/format-test.cc | 14 ++++++++++++-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 9ce96b1d..d77e5c82 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -123,8 +123,7 @@ FMT_FUNC auto write_int(appender out, basic_format_arg value, // We cannot use the num_put facet because it may produce output in // a wrong encoding. if (!std::has_facet>(locale)) return {}; - std::use_facet>(locale).put(out, value, specs, - locale); + std::use_facet>(locale).put(out, value, specs); return true; #endif return false; @@ -133,13 +132,13 @@ FMT_FUNC auto write_int(appender out, basic_format_arg value, struct localize_int { appender out; const format_specs& specs; - locale_ref loc; + std::string sep; template ::value)> void operator()(T value) { auto arg = make_write_int_arg(value, specs.sign); write_int(out, static_cast>(arg.abs_value), arg.prefix, - specs, digit_grouping(loc)); + specs, digit_grouping("\3", sep)); } template ::value)> void operator()(T) {} @@ -152,9 +151,8 @@ template typename Locale::id format_facet::id; template <> FMT_API FMT_FUNC void format_facet::do_put( appender out, basic_format_arg val, - const format_specs& specs, std::locale& loc) const { - visit_format_arg(detail::localize_int{out, specs, detail::locale_ref(loc)}, - val); + const format_specs& specs) const { + visit_format_arg(detail::localize_int{out, specs, separator_}, val); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index f03a59f9..ab98dbd5 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -993,7 +993,7 @@ template class format_facet : public Locale::facet { protected: virtual void do_put(appender out, basic_format_arg val, - const format_specs& specs, Locale& loc) const; + const format_specs& specs) const; public: static FMT_API typename Locale::id id; @@ -1002,8 +1002,8 @@ template class format_facet : public Locale::facet { : separator_(sep.data(), sep.size()) {} void put(appender out, basic_format_arg val, - const format_specs& specs, Locale& loc) const { - do_put(out, val, specs, loc); + const format_specs& specs) const { + do_put(out, val, specs); } }; @@ -1981,9 +1981,8 @@ template class digit_grouping { grouping_ = sep.grouping; if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result sep) - : grouping_(sep.grouping), - thousands_sep_(sep.thousands_sep ? 1 : 0, sep.thousands_sep) {} + digit_grouping(std::string grouping, std::string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} bool has_separator() const { return !thousands_sep_.empty(); } @@ -3950,7 +3949,7 @@ template struct formatter> : formatter { specs_.precision, specs_.precision_ref, ctx); return detail::write_int( ctx.out(), static_cast>(t.value), 0, specs_, - detail::digit_grouping({"\3", ','})); + detail::digit_grouping("\3", ",")); } }; diff --git a/test/format-test.cc b/test/format-test.cc index ee82bd9e..6fb38cb9 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2330,12 +2330,12 @@ class format_facet : public fmt::format_facet { }; void do_put(fmt::appender out, fmt::basic_format_arg arg, - const fmt::format_specs&, std::locale&) const override; + const fmt::format_specs&) const override; }; void format_facet::do_put(fmt::appender out, fmt::basic_format_arg arg, - const fmt::format_specs&, std::locale&) const { + const fmt::format_specs&) const { visit_format_arg(int_formatter{out}, arg); } @@ -2345,4 +2345,14 @@ TEST(format_test, format_facet) { EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]"); } +TEST(format_test, format_facet_separator) { + // U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH + // locale. + auto loc = std::locale(std::locale(), + new fmt::format_facet("\xe2\x80\x99")); + EXPECT_EQ(fmt::format(loc, "{:L}", 1000), + "1\xe2\x80\x99" + "000"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR From 58a5563a9ff7050fbd50dedd5f1de59cb7509e56 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 4 Sep 2022 09:01:26 -0700 Subject: [PATCH 07/11] Implement grouping --- include/fmt/format-inl.h | 6 ++++-- include/fmt/format.h | 21 ++++++++++++--------- test/format-test.cc | 10 ++++++++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index d77e5c82..75fa163b 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -133,12 +133,13 @@ struct localize_int { appender out; const format_specs& specs; std::string sep; + std::string grouping; template ::value)> void operator()(T value) { auto arg = make_write_int_arg(value, specs.sign); write_int(out, static_cast>(arg.abs_value), arg.prefix, - specs, digit_grouping("\3", sep)); + specs, digit_grouping(grouping, sep)); } template ::value)> void operator()(T) {} @@ -152,7 +153,8 @@ template <> FMT_API FMT_FUNC void format_facet::do_put( appender out, basic_format_arg val, const format_specs& specs) const { - visit_format_arg(detail::localize_int{out, specs, separator_}, val); + visit_format_arg(detail::localize_int{out, specs, separator_, grouping_}, + val); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index ab98dbd5..5e17a0a1 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -33,13 +33,14 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error +#include // std::signbit +#include // uint32_t +#include // std::memcpy +#include // std::initializer_list +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error #ifdef __cpp_lib_bit_cast # include // std::bitcast @@ -990,6 +991,7 @@ constexpr auto compile_string_to_view(detail::std_string_view s) template class format_facet : public Locale::facet { private: std::string separator_; + std::string grouping_; protected: virtual void do_put(appender out, basic_format_arg val, @@ -998,8 +1000,9 @@ template class format_facet : public Locale::facet { public: static FMT_API typename Locale::id id; - explicit format_facet(string_view sep = ",") - : separator_(sep.data(), sep.size()) {} + explicit format_facet(string_view sep = ",", + std::initializer_list g = {3}) + : separator_(sep.data(), sep.size()), grouping_(g.begin(), g.end()) {} void put(appender out, basic_format_arg val, const format_specs& specs) const { diff --git a/test/format-test.cc b/test/format-test.cc index 6fb38cb9..85f1af49 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2348,11 +2348,17 @@ TEST(format_test, format_facet) { TEST(format_test, format_facet_separator) { // U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH // locale. - auto loc = std::locale(std::locale(), - new fmt::format_facet("\xe2\x80\x99")); + auto loc = + std::locale({}, new fmt::format_facet("\xe2\x80\x99")); EXPECT_EQ(fmt::format(loc, "{:L}", 1000), "1\xe2\x80\x99" "000"); } +TEST(format_test, format_facet_grouping) { + auto loc = + std::locale({}, new fmt::format_facet(",", {1, 2, 3})); + EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR From d59b89e9cd9a130d45b62a434eb9c105908310e1 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 4 Sep 2022 10:44:05 -0700 Subject: [PATCH 08/11] More locale --- include/fmt/format-inl.h | 39 +++++++++++++++++++++++++++------------ include/fmt/format.h | 30 ++++++++++++++++++------------ test/format-test.cc | 18 +++++++++++------- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 75fa163b..465dd6b0 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -116,45 +116,60 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { } #endif -FMT_FUNC auto write_int(appender out, basic_format_arg value, +FMT_FUNC auto write_loc(appender out, basic_format_arg value, const format_specs& specs, locale_ref loc) -> bool { #ifndef FMT_STATIC_THOUSANDS_SEPARATOR auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. - if (!std::has_facet>(locale)) return {}; - std::use_facet>(locale).put(out, value, specs); - return true; + using facet = format_facet; + if (std::has_facet(locale)) + return std::use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); #endif return false; } -struct localize_int { +struct localizer { appender out; const format_specs& specs; std::string sep; std::string grouping; + std::string decimal_point; template ::value)> - void operator()(T value) { + auto operator()(T value) -> bool { auto arg = make_write_int_arg(value, specs.sign); write_int(out, static_cast>(arg.abs_value), arg.prefix, specs, digit_grouping(grouping, sep)); + return true; } - template ::value)> - void operator()(T) {} + + template ::value)> + auto operator()(T) -> bool { + return false; + } + + auto operator()(...) -> bool { return false; } }; } // namespace detail template typename Locale::id format_facet::id; #ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template format_facet::format_facet(Locale& loc) { + auto& numpunct = std::use_facet>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); +} + template <> -FMT_API FMT_FUNC void format_facet::do_put( +FMT_API FMT_FUNC auto format_facet::do_put( appender out, basic_format_arg val, - const format_specs& specs) const { - visit_format_arg(detail::localize_int{out, specs, separator_, grouping_}, - val); + const format_specs& specs) const -> bool { + return visit_format_arg( + detail::localizer{out, specs, separator_, grouping_, decimal_point_}, + val); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index 5e17a0a1..c2bff6c4 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -992,21 +992,26 @@ template class format_facet : public Locale::facet { private: std::string separator_; std::string grouping_; + std::string decimal_point_; protected: - virtual void do_put(appender out, basic_format_arg val, - const format_specs& specs) const; + virtual auto do_put(appender out, basic_format_arg val, + const format_specs& specs) const -> bool; public: static FMT_API typename Locale::id id; - explicit format_facet(string_view sep = ",", - std::initializer_list g = {3}) - : separator_(sep.data(), sep.size()), grouping_(g.begin(), g.end()) {} + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} - void put(appender out, basic_format_arg val, - const format_specs& specs) const { - do_put(out, val, specs); + auto put(appender out, basic_format_arg val, + const format_specs& specs) const -> bool { + return do_put(out, val, specs); } }; @@ -2042,10 +2047,11 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, }); } -FMT_API auto write_int(appender out, basic_format_arg value, +// Writes value with localization. +FMT_API auto write_loc(appender out, basic_format_arg value, const format_specs& specs, locale_ref loc) -> bool; template -inline auto write_int(OutputIt, basic_format_arg>, +inline auto write_loc(OutputIt, basic_format_arg>, const basic_format_specs&, locale_ref) -> bool { return false; } @@ -4165,8 +4171,8 @@ void vformat_to(buffer& buf, basic_string_view fmt, begin = parse_format_specs(begin, end, handler); if (begin == end || *begin != '}') on_error("missing '}' in format string"); - if (specs.localized && arg.is_integral() && - write_int(context.out(), arg, specs, context.locale())) { + if (specs.localized && + write_loc(context.out(), arg, specs, context.locale())) { return begin; } auto f = arg_formatter{context.out(), specs, context.locale()}; diff --git a/test/format-test.cc b/test/format-test.cc index 85f1af49..ee722ab3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2322,21 +2322,25 @@ class format_facet : public fmt::format_facet { fmt::appender out; template ::value)> - void operator()(T value) { + auto operator()(T value) -> bool { fmt::format_to(out, "[{}]", value); + return true; } + template ::value)> - void operator()(T) {} + auto operator()(T) -> bool { + return false; + } }; - void do_put(fmt::appender out, fmt::basic_format_arg arg, - const fmt::format_specs&) const override; + auto do_put(fmt::appender out, fmt::basic_format_arg arg, + const fmt::format_specs&) const -> bool override; }; -void format_facet::do_put(fmt::appender out, +auto format_facet::do_put(fmt::appender out, fmt::basic_format_arg arg, - const fmt::format_specs&) const { - visit_format_arg(int_formatter{out}, arg); + const fmt::format_specs&) const -> bool { + return visit_format_arg(int_formatter{out}, arg); } TEST(format_test, format_facet) { From bac53951b827ac71abfe6a213be9d1e1a89d50aa Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 4 Sep 2022 23:41:16 +0500 Subject: [PATCH 09/11] Add starts_with to basic_string_view. (#3080) Signed-off-by: Vladislav Shchapov Signed-off-by: Vladislav Shchapov --- include/fmt/core.h | 12 ++++++++++++ test/core-test.cc | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index a4b200d7..bc87b2f5 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -486,6 +486,18 @@ template class basic_string_view { size_ -= n; } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( + basic_string_view sv) const noexcept { + return size_ >= sv.size_ && + std::char_traits::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { + return size_ >= 1 && std::char_traits::eq(*data_, c); + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + return starts_with(basic_string_view(s)); + } + // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; diff --git a/test/core-test.cc b/test/core-test.cc index 96cde1ed..6d1309fe 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -70,6 +70,16 @@ TEST(string_view_test, compare) { EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); EXPECT_GT(string_view("foo").compare(string_view("fo")), 0); EXPECT_LT(string_view("fo").compare(string_view("foo")), 0); + + EXPECT_TRUE(string_view("foo").starts_with('f')); + EXPECT_FALSE(string_view("foo").starts_with('o')); + EXPECT_FALSE(string_view().starts_with('o')); + + EXPECT_TRUE(string_view("foo").starts_with("fo")); + EXPECT_TRUE(string_view("foo").starts_with("foo")); + EXPECT_FALSE(string_view("foo").starts_with("fooo")); + EXPECT_FALSE(string_view().starts_with("fooo")); + check_op(); check_op(); check_op(); From b98ffb7dbdb7d97e14ea65fad757cb5bb853d98b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 4 Sep 2022 20:20:59 -0700 Subject: [PATCH 10/11] Improve locale handling --- include/fmt/format-inl.h | 25 +----------- include/fmt/format.h | 84 ++++++++++++++++++++++++---------------- include/fmt/xchar.h | 21 ++++++++++ test/xchar-test.cc | 10 ++--- 4 files changed, 77 insertions(+), 63 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 465dd6b0..8721888d 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -129,29 +129,6 @@ FMT_FUNC auto write_loc(appender out, basic_format_arg value, #endif return false; } - -struct localizer { - appender out; - const format_specs& specs; - std::string sep; - std::string grouping; - std::string decimal_point; - - template ::value)> - auto operator()(T value) -> bool { - auto arg = make_write_int_arg(value, specs.sign); - write_int(out, static_cast>(arg.abs_value), arg.prefix, - specs, digit_grouping(grouping, sep)); - return true; - } - - template ::value)> - auto operator()(T) -> bool { - return false; - } - - auto operator()(...) -> bool { return false; } -}; } // namespace detail template typename Locale::id format_facet::id; @@ -168,7 +145,7 @@ FMT_API FMT_FUNC auto format_facet::do_put( appender out, basic_format_arg val, const format_specs& specs) const -> bool { return visit_format_arg( - detail::localizer{out, specs, separator_, grouping_, decimal_point_}, + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}, val); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index c2bff6c4..43dc215a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -739,6 +739,21 @@ inline auto code_point_index(basic_string_view s, size_t n) string_view(reinterpret_cast(s.data()), s.size()), n); } +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + #ifndef FMT_USE_FLOAT128 # ifdef __SIZEOF_FLOAT128__ # define FMT_USE_FLOAT128 1 @@ -1017,15 +1032,6 @@ template class format_facet : public Locale::facet { FMT_BEGIN_DETAIL_NAMESPACE -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; - // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> @@ -1989,7 +1995,7 @@ template class digit_grouping { grouping_ = sep.grouping; if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - digit_grouping(std::string grouping, std::string sep) + digit_grouping(std::string grouping, std::basic_string sep) : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} bool has_separator() const { return !thousands_sep_.empty(); } @@ -2047,23 +2053,16 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, }); } -// Writes value with localization. +// Writes localized value. FMT_API auto write_loc(appender out, basic_format_arg value, const format_specs& specs, locale_ref loc) -> bool; + template inline auto write_loc(OutputIt, basic_format_arg>, const basic_format_specs&, locale_ref) -> bool { return false; } -template -auto write_int(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) -> bool { - auto grouping = digit_grouping(loc); - out = write_int(out, value, prefix, specs, grouping); - return true; -} - FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix |= prefix != 0 ? value << 8 : value; prefix += (1u + (value > 0xff ? 1 : 0)) << 24; @@ -2090,20 +2089,39 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } +template struct loc_writer { + buffer_appender out; + const basic_format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } + + auto operator()(...) -> bool { return false; } +}; + template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const basic_format_specs& specs, - locale_ref loc) -> OutputIt { + locale_ref) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { case presentation_type::none: case presentation_type::dec: { - if (specs.localized && - write_int(out, static_cast>(abs_value), prefix, - specs, loc)) - return out; auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { @@ -2163,6 +2181,10 @@ template & specs, locale_ref loc) -> OutputIt { + if (specs.localized && + write_loc(out, make_arg>(value), specs, loc)) { + return out; + } return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2174,6 +2196,10 @@ template & specs, locale_ref loc) -> OutputIt { + if (specs.localized && + write_loc(out, make_arg>(value), specs, loc)) { + return out; + } return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -3455,12 +3481,6 @@ template struct custom_formatter { template void operator()(T) const {} }; -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} @@ -4171,10 +4191,6 @@ void vformat_to(buffer& buf, basic_string_view fmt, begin = parse_format_specs(begin, end, handler); if (begin == end || *begin != '}') on_error("missing '}' in format string"); - if (specs.localized && - write_loc(context.out(), arg, specs, context.locale())) { - return begin; - } auto f = arg_formatter{context.out(), specs, context.locale()}; context.advance_to(visit_format_arg(f, arg)); return begin; diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 3b5bc15c..5fb5c815 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -12,11 +12,32 @@ #include "format.h" +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +# include +#endif + FMT_BEGIN_NAMESPACE namespace detail { + template using is_exotic_char = bool_constant::value>; + +template +auto write_loc(OutputIt out, basic_format_arg> val, + const basic_format_specs& specs, locale_ref loc) + -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return visit_format_arg( + loc_writer{out, specs, separator, grouping, {}}, val); +#endif + return false; } +} // namespace detail FMT_MODULE_EXPORT_BEGIN diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 3eec7384..dd45826d 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -448,11 +448,11 @@ TEST(locale_test, int_formatter) { auto f = fmt::formatter(); auto parse_ctx = fmt::format_parse_context("L"); f.parse(parse_ctx); - char buf[10] = {}; - fmt::basic_format_context format_ctx( - buf, {}, fmt::detail::locale_ref(loc)); - *f.format(12345, format_ctx) = 0; - EXPECT_STREQ("12,345", buf); + auto buf = fmt::memory_buffer(); + fmt::basic_format_context format_ctx( + fmt::appender(buf), {}, fmt::detail::locale_ref(loc)); + f.format(12345, format_ctx); + EXPECT_EQ(fmt::to_string(buf), "12,345"); } FMT_BEGIN_NAMESPACE From 1feb430faaac6bd8094e996861d6025f9903d34e Mon Sep 17 00:00:00 2001 From: VinaCC Date: Mon, 5 Sep 2022 20:50:06 +0200 Subject: [PATCH 11/11] Fix intellisense on Windows (#3082) __INTELLISENSE__ is 1 on vs2022 and clang, causing FMT_HAS_INCLUDE, FMT_USE_FCNTL, etc to be 0. That results in VS and VSCode having a lot of linter errors while code compiles just fine. --- include/fmt/core.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index bc87b2f5..42835c0e 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -69,9 +69,7 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ - FMT_MSC_VERSION > 1900) && \ - !defined(__INTELLISENSE__) +#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0