Skip to content

Commit

Permalink
Improve locale handling
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Sep 5, 2022
1 parent bac5395 commit b98ffb7
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 63 deletions.
25 changes: 1 addition & 24 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,29 +129,6 @@ FMT_FUNC auto write_loc(appender out, basic_format_arg<format_context> value,
#endif
return false;
}

struct localizer {
appender out;
const format_specs& specs;
std::string sep;
std::string grouping;
std::string decimal_point;

template <typename T, FMT_ENABLE_IF(detail::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(detail::is_floating_point<T>::value)>
auto operator()(T) -> bool {
return false;
}

auto operator()(...) -> bool { return false; }
};
} // namespace detail

template <typename Locale> typename Locale::id format_facet<Locale>::id;
Expand All @@ -168,7 +145,7 @@ 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::localizer{out, specs, separator_, grouping_, decimal_point_},
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_},
val);
}
#endif
Expand Down
84 changes: 50 additions & 34 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -739,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
Expand Down Expand Up @@ -1017,15 +1032,6 @@ template <typename Locale> class format_facet : public Locale::facet {

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)>
Expand Down Expand Up @@ -1989,7 +1995,7 @@ template <typename Char> 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<Char> sep)
: grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {}

bool has_separator() const { return !thousands_sep_.empty(); }
Expand Down Expand Up @@ -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<format_context> value,
const format_specs& specs, locale_ref loc) -> bool;

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

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 grouping = digit_grouping<Char>(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;
Expand All @@ -2090,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) {
Expand Down Expand Up @@ -2163,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);
}
Expand All @@ -2174,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);
}

Expand Down Expand Up @@ -3455,12 +3481,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) {}
Expand Down Expand Up @@ -4171,10 +4191,6 @@ void vformat_to(buffer<Char>& buf, basic_string_view<Char> 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<Char>{context.out(), specs, context.locale()};
context.advance_to(visit_format_arg(f, arg));
return begin;
Expand Down
21 changes: 21 additions & 0 deletions include/fmt/xchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b98ffb7

Please sign in to comment.