Merge branch 'master' of github.com:andreagen0r/fmt

This commit is contained in:
André Agenor 2022-09-05 22:51:57 -03:00
commit 2d415bb497
11 changed files with 237 additions and 134 deletions

View File

@ -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
@ -332,7 +330,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
@ -486,6 +484,18 @@ template <typename Char> class basic_string_view {
size_ -= n;
}
FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(
basic_string_view<Char> sv) const noexcept {
return size_ >= sv.size_ &&
std::char_traits<Char>::compare(data_, sv.data_, sv.size_) == 0;
}
FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept {
return size_ >= 1 && std::char_traits<Char>::eq(*data_, c);
}
FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const {
return starts_with(basic_string_view<Char>(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_;

View File

@ -29,8 +29,6 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
template <typename Locale> typename Locale::id num_format_facet<Locale>::id;
namespace detail {
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
@ -118,22 +116,40 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
}
#endif
FMT_FUNC auto write_int(appender out, loc_value value,
FMT_FUNC auto write_loc(appender out, basic_format_arg<format_context> value,
const format_specs& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding.
if (!std::has_facet<num_format_facet<std::locale>>(locale)) return {};
std::use_facet<num_format_facet<std::locale>>(locale).put(out, value, specs,
locale);
return true;
using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale))
return std::use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
#endif
return false;
}
} // namespace detail
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
}
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, basic_format_arg<format_context> val,
const format_specs& specs) const -> bool {
return visit_format_arg(
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_},
val);
}
#endif
#if !FMT_MSC_VERSION
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
#endif

View File

@ -33,13 +33,14 @@
#ifndef FMT_FORMAT_H_
#define FMT_FORMAT_H_
#include <cmath> // std::signbit
#include <cstdint> // uint32_t
#include <cstring> // std::memcpy
#include <limits> // std::numeric_limits
#include <memory> // std::uninitialized_copy
#include <stdexcept> // std::runtime_error
#include <system_error> // std::system_error
#include <cmath> // std::signbit
#include <cstdint> // uint32_t
#include <cstring> // std::memcpy
#include <initializer_list> // std::initializer_list
#include <limits> // std::numeric_limits
#include <memory> // std::uninitialized_copy
#include <stdexcept> // std::runtime_error
#include <system_error> // std::system_error
#ifdef __cpp_lib_bit_cast
# include <bit> // std::bitcast
@ -738,6 +739,21 @@ inline auto code_point_index(basic_string_view<char8_type> s, size_t n)
string_view(reinterpret_cast<const char*>(s.data()), s.size()), n);
}
template <typename T> struct is_integral : std::is_integral<T> {};
template <> struct is_integral<int128_opt> : std::true_type {};
template <> struct is_integral<uint128_t> : std::true_type {};
template <typename T>
using is_signed =
std::integral_constant<bool, std::numeric_limits<T>::is_signed ||
std::is_same<T, int128_opt>::value>;
template <typename T>
using is_integer =
bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
!std::is_same<T, char>::value &&
!std::is_same<T, wchar_t>::value>;
#ifndef FMT_USE_FLOAT128
# ifdef __SIZEOF_FLOAT128__
# define FMT_USE_FLOAT128 1
@ -985,40 +1001,37 @@ constexpr auto compile_string_to_view(detail::std_string_view<Char> s)
}
} // namespace detail_exported
// A value to localize.
struct loc_value {
union {
unsigned long long ulong_long_value;
};
};
// A locale facet that formats values in UTF-8.
// It is parameterized on the locale to avoid the heavy <locale> include.
template <typename Locale> class format_facet : public Locale::facet {
private:
std::string separator_;
std::string grouping_;
std::string decimal_point_;
protected:
virtual auto do_put(appender out, basic_format_arg<format_context> val,
const format_specs& specs) const -> bool;
// A locale facet that formats numeric values in UTF-8.
// It is parameterized on the locale to avoid heavy <locale> include.
template <typename Locale> class num_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 {
do_put(out, val, specs, loc);
}
explicit format_facet(Locale& loc);
explicit format_facet(string_view sep = "",
std::initializer_list<unsigned char> g = {3},
std::string decimal_point = ".")
: separator_(sep.data(), sep.size()),
grouping_(g.begin(), g.end()),
decimal_point_(decimal_point) {}
protected:
virtual void do_put(appender out, loc_value val, const format_specs& specs,
Locale& loc) const = 0;
auto put(appender out, basic_format_arg<format_context> val,
const format_specs& specs) const -> bool {
return do_put(out, val, specs);
}
};
FMT_BEGIN_DETAIL_NAMESPACE
template <typename T> struct is_integral : std::is_integral<T> {};
template <> struct is_integral<int128_opt> : std::true_type {};
template <> struct is_integral<uint128_t> : std::true_type {};
template <typename T>
using is_signed =
std::integral_constant<bool, std::numeric_limits<T>::is_signed ||
std::is_same<T, int128_opt>::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 <typename T, FMT_ENABLE_IF(is_signed<T>::value)>
@ -1956,19 +1969,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits,
template <typename Char> class digit_grouping {
private:
thousands_sep_result<Char> sep_;
std::string grouping_;
std::basic_string<Char> 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<int>();
if (state.group == sep_.grouping.end())
return state.pos += sep_.grouping.back();
if (thousands_sep_.empty()) return max_value<int>();
if (state.group == grouping_.end()) return state.pos += grouping_.back();
if (*state.group <= 0 || *state.group == max_value<char>())
return max_value<int>();
state.pos += *state.group++;
@ -1977,14 +1990,15 @@ template <typename Char> class digit_grouping {
public:
explicit digit_grouping(locale_ref loc, bool localized = true) {
if (localized)
sep_ = thousands_sep<Char>(loc);
else
sep_.thousands_sep = Char();
if (!localized) return;
auto sep = thousands_sep<Char>(loc);
grouping_ = sep.grouping;
if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep);
}
explicit digit_grouping(thousands_sep_result<Char> sep) : sep_(sep) {}
digit_grouping(std::string grouping, std::basic_string<Char> sep)
: grouping_(std::move(grouping)), thousands_sep_(std::move(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;
@ -2007,7 +2021,9 @@ template <typename Char> class digit_grouping {
for (int i = 0, sep_index = static_cast<int>(separators.size() - 1);
i < num_digits; ++i) {
if (num_digits - i == separators[sep_index]) {
*out++ = separator();
out =
copy_str<Char>(thousands_sep_.data(),
thousands_sep_.data() + thousands_sep_.size(), out);
--sep_index;
}
*out++ = static_cast<Char>(digits[to_unsigned(i)]);
@ -2037,37 +2053,14 @@ 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;
template <typename OutputIt, typename Char>
inline auto write_int(OutputIt, loc_value, const basic_format_specs<Char>&,
locale_ref) -> bool {
return false;
}
// Writes localized value.
FMT_API auto write_loc(appender out, basic_format_arg<format_context> value,
const format_specs& specs, locale_ref loc) -> bool;
template <typename OutputIt, typename UInt, typename Char>
auto write_int(OutputIt& out, UInt value, unsigned prefix,
const basic_format_specs<Char>& 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<unsigned long long>(value)},
specs, loc);
if (!result) {
auto grouping = digit_grouping<Char>(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<align::right>(
out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
if (prefix != 0) {
char sign = static_cast<char>(prefix);
*it++ = static_cast<Char>(sign);
}
return copy_str<Char>(buf.data(), buf.data() + buf.size(), it);
});
return true;
template <typename OutputIt, typename Char>
inline auto write_loc(OutputIt, basic_format_arg<buffer_context<Char>>,
const basic_format_specs<Char>&, locale_ref) -> bool {
return false;
}
FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
@ -2096,20 +2089,39 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign)
return {abs_value, prefix};
}
template <typename Char = char> struct loc_writer {
buffer_appender<Char> out;
const basic_format_specs<Char>& specs;
std::basic_string<Char> sep;
std::string grouping;
std::basic_string<Char> decimal_point;
template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
auto operator()(T value) -> bool {
auto arg = make_write_int_arg(value, specs.sign);
write_int(out, static_cast<uint64_or_128_t<T>>(arg.abs_value), arg.prefix,
specs, digit_grouping<Char>(grouping, sep));
return true;
}
template <typename T, FMT_ENABLE_IF(is_floating_point<T>::value)>
auto operator()(T) -> bool {
return false;
}
auto operator()(...) -> bool { return false; }
};
template <typename Char, typename OutputIt, typename T>
FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
const basic_format_specs<Char>& specs,
locale_ref loc) -> OutputIt {
locale_ref) -> OutputIt {
static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::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<uint64_or_128_t<T>>(abs_value), prefix,
specs, loc))
return out;
auto num_digits = count_digits(abs_value);
return write_int(
out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
@ -2169,6 +2181,10 @@ template <typename Char, typename OutputIt, typename T,
FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
const basic_format_specs<Char>& specs,
locale_ref loc) -> OutputIt {
if (specs.localized &&
write_loc(out, make_arg<buffer_context<Char>>(value), specs, loc)) {
return out;
}
return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs,
loc);
}
@ -2180,6 +2196,10 @@ template <typename Char, typename OutputIt, typename T,
FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
const basic_format_specs<Char>& specs,
locale_ref loc) -> OutputIt {
if (specs.localized &&
write_loc(out, make_arg<buffer_context<Char>>(value), specs, loc)) {
return out;
}
return write_int(out, make_write_int_arg(value, specs.sign), specs, loc);
}
@ -2331,7 +2351,7 @@ template <typename Char, typename OutputIt, typename T, typename Grouping>
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<Char>(out, significand, significand_size);
return detail::fill_n(out, exponent, static_cast<Char>('0'));
}
@ -2393,7 +2413,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);
}
@ -2515,7 +2535,7 @@ template <typename Char> 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; }
@ -3460,12 +3480,6 @@ template <typename Char> struct custom_formatter {
template <typename T> void operator()(T) const {}
};
template <typename T>
using is_integer =
bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
!std::is_same<T, char>::value &&
!std::is_same<T, wchar_t>::value>;
template <typename ErrorHandler> class width_checker {
public:
explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {}
@ -3963,7 +3977,7 @@ template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
specs_.precision, specs_.precision_ref, ctx);
return detail::write_int(
ctx.out(), static_cast<detail::uint64_or_128_t<T>>(t.value), 0, specs_,
detail::digit_grouping<char>({"\3", ','}));
detail::digit_grouping<char>("\3", ","));
}
};

View File

@ -211,16 +211,16 @@ class is_tuple_formattable_ {
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... I>
static std::true_type check2(index_sequence<I...>,
integer_sequence<bool, (I == I)...>);
template <std::size_t... Is>
static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...);
template <std::size_t... I>
template <std::size_t... Is>
static decltype(check2(
index_sequence<I...>{},
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<I, T>::type,
C>::value)...>{})) check(index_sequence<I...>);
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<Is...>);
public:
static constexpr const bool value =

View File

@ -101,19 +101,16 @@ template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
// variant_size and variant_alternative check.
template <typename T, typename U = void>
struct is_variant_like_ : std::false_type {};
template <typename T>
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
: std::true_type {};
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... I>
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<I, T>, C>...>
check(std::index_sequence<I...>);
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =

View File

@ -12,11 +12,32 @@
#include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
template <typename OutputIt>
auto write_loc(OutputIt out, basic_format_arg<buffer_context<wchar_t>> val,
const basic_format_specs<wchar_t>& specs, locale_ref loc)
-> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return visit_format_arg(
loc_writer<wchar_t>{out, specs, separator, grouping, {}}, val);
#endif
return false;
}
} // namespace detail
FMT_MODULE_EXPORT_BEGIN

View File

@ -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 <algorithm> // std::copy_n
#include <climits> // INT_MAX
@ -72,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<std::equal_to>();
check_op<std::not_equal_to>();
check_op<std::less>();

View File

@ -8,12 +8,14 @@
#include <iterator>
#include <vector>
#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() {

View File

@ -2312,25 +2312,57 @@ TEST(format_int_test, format_int) {
EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
}
#ifdef FMT_STATIC_THOUSANDS_SEPARATOR
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
class num_format : public fmt::num_format_facet<std::locale> {
class format_facet : public fmt::format_facet<std::locale> {
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 <typename T, FMT_ENABLE_IF(fmt::detail::is_integer<T>::value)>
auto operator()(T value) -> bool {
fmt::format_to(out, "[{}]", value);
return true;
}
template <typename T, FMT_ENABLE_IF(!fmt::detail::is_integer<T>::value)>
auto operator()(T) -> bool {
return false;
}
};
auto do_put(fmt::appender out, fmt::basic_format_arg<fmt::format_context> arg,
const fmt::format_specs&) const -> bool 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);
auto format_facet::do_put(fmt::appender out,
fmt::basic_format_arg<fmt::format_context> arg,
const fmt::format_specs&) const -> bool {
return 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]");
}
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({}, new fmt::format_facet<std::locale>("\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<std::locale>(",", {1, 2, 3}));
EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR

View File

@ -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
}

View File

@ -448,11 +448,11 @@ TEST(locale_test, int_formatter) {
auto f = fmt::formatter<int>();
auto parse_ctx = fmt::format_parse_context("L");
f.parse(parse_ctx);
char buf[10] = {};
fmt::basic_format_context<char*, char> 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<fmt::appender, char> 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