mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-05 05:04:27 +02:00
feat: formatting grammar improved and units formatting support added
This commit is contained in:
@@ -35,15 +35,36 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
// most of the below code is based on/copied from libfmt
|
// most of the below code is based on/copied from fmtlib
|
||||||
|
|
||||||
namespace mp_units::detail {
|
namespace mp_units::detail {
|
||||||
|
|
||||||
struct auto_id {};
|
enum class fmt_align { none, left, right, center, numeric };
|
||||||
|
enum class fmt_arg_id_kind { none, index, name };
|
||||||
|
|
||||||
enum class fmt_align { none, left, right, center };
|
template<typename Char>
|
||||||
enum class fmt_sign { none, minus, plus, space };
|
struct fmt_arg_ref {
|
||||||
enum class arg_id_kind { none, index, name };
|
fmt_arg_id_kind kind = fmt_arg_id_kind::none;
|
||||||
|
union value {
|
||||||
|
int index = 0;
|
||||||
|
std::basic_string_view<Char> name;
|
||||||
|
|
||||||
|
value() = default;
|
||||||
|
constexpr value(int idx) : index(idx) {}
|
||||||
|
constexpr value(std::basic_string_view<Char> n) : name(n) {}
|
||||||
|
} val{};
|
||||||
|
|
||||||
|
fmt_arg_ref() = default;
|
||||||
|
constexpr explicit fmt_arg_ref(int index) : kind(fmt_arg_id_kind::index), val(index) {}
|
||||||
|
constexpr explicit fmt_arg_ref(std::basic_string_view<Char> name) : kind(fmt_arg_id_kind::name), val(name) {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr fmt_arg_ref& operator=(int idx)
|
||||||
|
{
|
||||||
|
kind = fmt_arg_id_kind::index;
|
||||||
|
val.index = idx;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename Char>
|
template<typename Char>
|
||||||
struct fill_t {
|
struct fill_t {
|
||||||
@@ -74,12 +95,6 @@ template<typename T>
|
|||||||
inline constexpr bool is_integer = std::is_integral<T>::value && !std::is_same<T, bool>::value &&
|
inline constexpr bool is_integer = std::is_integral<T>::value && !std::is_same<T, bool>::value &&
|
||||||
!std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value;
|
!std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value;
|
||||||
|
|
||||||
template<typename Char>
|
|
||||||
[[nodiscard]] constexpr bool is_ascii_letter(Char c)
|
|
||||||
{
|
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a character to ASCII. Returns a number > 127 on conversion failure.
|
// Converts a character to ASCII. Returns a number > 127 on conversion failure.
|
||||||
template<std::integral Char>
|
template<std::integral Char>
|
||||||
[[nodiscard]] constexpr Char to_ascii(Char value)
|
[[nodiscard]] constexpr Char to_ascii(Char value)
|
||||||
@@ -89,232 +104,198 @@ template<std::integral Char>
|
|||||||
|
|
||||||
template<typename Char>
|
template<typename Char>
|
||||||
requires std::is_enum_v<Char>
|
requires std::is_enum_v<Char>
|
||||||
[[nodiscard]] constexpr auto to_ascii(Char value) -> std::underlying_type_t<Char>
|
[[nodiscard]] constexpr std::underlying_type_t<Char> to_ascii(Char value)
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Casts a nonnegative integer to unsigned.
|
||||||
|
template<typename Int>
|
||||||
|
[[nodiscard]] constexpr std::make_unsigned_t<Int> to_unsigned(Int value)
|
||||||
|
{
|
||||||
|
gsl_Expects(std::is_unsigned_v<Int> || value >= 0);
|
||||||
|
return static_cast<std::make_unsigned_t<Int>>(value);
|
||||||
|
}
|
||||||
|
|
||||||
struct width_checker {
|
struct width_checker {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
[[nodiscard]] constexpr unsigned long long operator()(T value) const
|
[[nodiscard]] constexpr unsigned long long operator()(T value) const
|
||||||
{
|
{
|
||||||
if constexpr (is_integer<T>) {
|
if constexpr (is_integer<T>) {
|
||||||
if constexpr (std::numeric_limits<T>::is_signed) {
|
if constexpr (std::numeric_limits<T>::is_signed)
|
||||||
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width"));
|
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width"));
|
||||||
}
|
|
||||||
return static_cast<unsigned long long>(value);
|
return static_cast<unsigned long long>(value);
|
||||||
}
|
}
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer"));
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer"));
|
||||||
return 0; // should never happen
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct precision_checker {
|
template<class Handler, typename FormatArg>
|
||||||
template<typename T>
|
[[nodiscard]] constexpr int get_dynamic_spec(FormatArg arg)
|
||||||
[[nodiscard]] constexpr unsigned long long operator()(T value) const
|
|
||||||
{
|
{
|
||||||
if constexpr (is_integer<T>) {
|
const unsigned long long value = MP_UNITS_STD_FMT::visit_format_arg(Handler{}, arg);
|
||||||
if constexpr (std::numeric_limits<T>::is_signed) {
|
if (value > ::mp_units::detail::to_unsigned(std::numeric_limits<int>::max())) {
|
||||||
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative precision"));
|
|
||||||
}
|
|
||||||
return static_cast<unsigned long long>(value);
|
|
||||||
}
|
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("precision is not integer"));
|
|
||||||
return 0; // should never happen
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format specifiers for built-in and string types.
|
|
||||||
template<typename Char>
|
|
||||||
struct basic_format_specs {
|
|
||||||
int width = 0;
|
|
||||||
int precision = -1;
|
|
||||||
char type = '\0';
|
|
||||||
fmt_align align : 4 = fmt_align::none;
|
|
||||||
fmt_sign sign : 3 = fmt_sign::none;
|
|
||||||
bool alt : 1 = false; // Alternate form ('#').
|
|
||||||
bool localized : 1 = false;
|
|
||||||
fill_t<Char> fill;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format specifiers with width and precision resolved at formatting rather
|
|
||||||
// than parsing time to allow re-using the same parsed specifiers with
|
|
||||||
// different sets of arguments (precompilation of format strings).
|
|
||||||
template<typename Char>
|
|
||||||
struct dynamic_format_specs : basic_format_specs<Char> {
|
|
||||||
int dynamic_width_index = -1;
|
|
||||||
int dynamic_precision_index = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr int verify_dynamic_arg_index_in_range(size_t idx)
|
|
||||||
{
|
|
||||||
if (idx > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Dynamic width or precision index too large."));
|
|
||||||
}
|
|
||||||
return static_cast<int>(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename CharT>
|
|
||||||
[[nodiscard]] constexpr int on_dynamic_arg(size_t arg_id, MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context)
|
|
||||||
{
|
|
||||||
context.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(arg_id));
|
|
||||||
return verify_dynamic_arg_index_in_range(arg_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename CharT>
|
|
||||||
[[nodiscard]] constexpr int on_dynamic_arg(auto_id, MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context)
|
|
||||||
{
|
|
||||||
return verify_dynamic_arg_index_in_range(MP_UNITS_FMT_FROM_ARG_ID(context.next_arg_id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Handler, typename FormatContext>
|
|
||||||
[[nodiscard]] constexpr int get_dynamic_spec(int index, FormatContext& ctx)
|
|
||||||
{
|
|
||||||
const unsigned long long value =
|
|
||||||
MP_UNITS_STD_FMT::visit_format_arg(Handler{}, ctx.arg(MP_UNITS_FMT_TO_ARG_ID(static_cast<size_t>(index))));
|
|
||||||
if (value > static_cast<unsigned long long>(std::numeric_limits<int>::max())) {
|
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
||||||
}
|
}
|
||||||
return static_cast<int>(value);
|
return static_cast<int>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses the range [begin, end) as an unsigned integer. This function assumes
|
template<typename Context, typename ID>
|
||||||
// that the range is non-empty and the first character is a digit.
|
[[nodiscard]] constexpr auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id))
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, size_t& value)
|
|
||||||
{
|
{
|
||||||
gsl_Expects(begin != end && '0' <= *begin && *begin <= '9');
|
auto arg = ctx.arg(id);
|
||||||
constexpr auto max_int = static_cast<unsigned>(std::numeric_limits<int>::max());
|
if (!arg) ctx.on_error("argument not found");
|
||||||
constexpr auto big_int = max_int / 10u;
|
return arg;
|
||||||
value = 0;
|
}
|
||||||
|
|
||||||
do {
|
template<class Handler, typename Context>
|
||||||
if (value > big_int) {
|
constexpr void handle_dynamic_spec(int& value, fmt_arg_ref<typename Context::char_type> ref, Context& ctx)
|
||||||
value = max_int + 1;
|
{
|
||||||
|
switch (ref.kind) {
|
||||||
|
case fmt_arg_id_kind::none:
|
||||||
|
break;
|
||||||
|
case fmt_arg_id_kind::index:
|
||||||
|
value = ::mp_units::detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index));
|
||||||
|
break;
|
||||||
|
case fmt_arg_id_kind::name:
|
||||||
|
value = ::mp_units::detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value = value * 10 + static_cast<unsigned int>(*begin - '0');
|
|
||||||
++begin;
|
|
||||||
} while (begin != end && '0' <= *begin && *begin <= '9');
|
|
||||||
|
|
||||||
if (value > max_int) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Number is too big"));
|
|
||||||
|
|
||||||
return begin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
// Parses the range [begin, end) as an unsigned integer. This function assumes
|
||||||
[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, int& value)
|
// that the range is non-empty and the first character is a digit.
|
||||||
|
template<typename Char>
|
||||||
|
[[nodiscard]] constexpr int parse_nonnegative_int(const Char*& begin, const Char* end, int error_value)
|
||||||
{
|
{
|
||||||
size_t val_unsigned = 0;
|
gsl_Expects(begin != end && '0' <= *begin && *begin <= '9');
|
||||||
begin = parse_nonnegative_int(begin, end, val_unsigned);
|
unsigned value = 0, prev = 0;
|
||||||
// Never invalid because parse_nonnegative_integer throws an error for values that don't fit in signed integers
|
auto p = begin;
|
||||||
value = static_cast<int>(val_unsigned);
|
do {
|
||||||
return begin;
|
prev = value;
|
||||||
|
value = value * 10 + unsigned(*p - '0');
|
||||||
|
++p;
|
||||||
|
} while (p != end && '0' <= *p && *p <= '9');
|
||||||
|
auto num_digits = p - begin;
|
||||||
|
begin = p;
|
||||||
|
if (num_digits <= std::numeric_limits<int>::digits10) return static_cast<int>(value);
|
||||||
|
// Check for overflow.
|
||||||
|
const unsigned max = ::mp_units::detail::to_unsigned((std::numeric_limits<int>::max)());
|
||||||
|
return num_digits == std::numeric_limits<int>::digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max
|
||||||
|
? static_cast<int>(value)
|
||||||
|
: error_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename IDHandler>
|
template<typename Char>
|
||||||
[[nodiscard]] constexpr It do_parse_arg_id(It begin, S end, IDHandler&& handler)
|
[[nodiscard]] constexpr bool is_name_start(Char c)
|
||||||
{
|
{
|
||||||
gsl_Expects(begin != end);
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_';
|
||||||
auto c = *begin;
|
}
|
||||||
|
|
||||||
|
template<typename Char, typename Handler>
|
||||||
|
[[nodiscard]] constexpr const Char* do_parse_arg_id(const Char* begin, const Char* end, Handler&& handler)
|
||||||
|
{
|
||||||
|
Char c = *begin;
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
size_t index = 0;
|
int index = 0;
|
||||||
|
constexpr int max = (std::numeric_limits<int>::max)();
|
||||||
if (c != '0')
|
if (c != '0')
|
||||||
begin = parse_nonnegative_int(begin, end, index);
|
index = ::mp_units::detail::parse_nonnegative_int(begin, end, max);
|
||||||
else
|
else
|
||||||
++begin;
|
++begin;
|
||||||
if (begin == end || (*begin != '}' && *begin != ':'))
|
if (begin == end || (*begin != '}' && *begin != ':'))
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
||||||
else
|
else
|
||||||
handler(index);
|
handler.on_index(index);
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
if (c == '%') return begin; // mp-units extension
|
||||||
|
if (!::mp_units::detail::is_name_start(c)) {
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
||||||
return begin; // should never happen
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename IDHandler>
|
|
||||||
[[nodiscard]] constexpr It parse_arg_id(It begin, S end, IDHandler&& handler)
|
|
||||||
{
|
|
||||||
auto c = *begin;
|
|
||||||
if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler);
|
|
||||||
handler();
|
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
auto it = begin;
|
||||||
|
do {
|
||||||
|
++it;
|
||||||
|
} while (it != end && (::mp_units::detail::is_name_start(*it) || ('0' <= *it && *it <= '9')));
|
||||||
|
handler.on_name({begin, ::mp_units::detail::to_unsigned(it - begin)});
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
template<typename Char, typename Handler>
|
||||||
[[nodiscard]] constexpr It parse_sign(It begin, S end, Handler&& handler)
|
[[nodiscard]] constexpr const Char* parse_arg_id(const Char* begin, const Char* end, Handler&& handler)
|
||||||
{
|
{
|
||||||
gsl_Expects(begin != end);
|
gsl_Expects(begin != end);
|
||||||
switch (to_ascii(*begin)) {
|
Char c = *begin;
|
||||||
case '+':
|
if (c != '}' && c != ':') return ::mp_units::detail::do_parse_arg_id(begin, end, handler);
|
||||||
handler.on_sign(fmt_sign::plus);
|
handler.on_auto();
|
||||||
++begin;
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
handler.on_sign(fmt_sign::minus);
|
|
||||||
++begin;
|
|
||||||
break;
|
|
||||||
case ' ':
|
|
||||||
handler.on_sign(fmt_sign::space);
|
|
||||||
++begin;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
template<typename Char, typename Handler>
|
||||||
[[nodiscard]] constexpr It parse_width(It begin, S end, Handler&& handler)
|
[[nodiscard]] constexpr const Char* parse_subentity_replacement_field(const Char* begin, const Char* end,
|
||||||
|
Handler&& handler)
|
||||||
{
|
{
|
||||||
struct width_adapter {
|
if (end - begin++ < 4) return MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")), end;
|
||||||
Handler& handler;
|
if (*begin++ != '%') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format"));
|
||||||
constexpr void operator()() { handler.on_dynamic_width(auto_id{}); }
|
if (*begin == '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format"));
|
||||||
constexpr void operator()(size_t id) { handler.on_dynamic_width(id); }
|
auto it = begin;
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
if (*it == '{' || *it == '%') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format"));
|
||||||
|
if (*it == '}' || *it == ':') break;
|
||||||
|
}
|
||||||
|
if (it == end) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format"));
|
||||||
|
std::string_view id{begin, it};
|
||||||
|
if (*it == ':') ++it;
|
||||||
|
it = handler.on_replacement_field(id, it);
|
||||||
|
if (it == end || *it != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format"));
|
||||||
|
return ++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Char>
|
||||||
|
struct dynamic_spec_id_handler {
|
||||||
|
MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx;
|
||||||
|
fmt_arg_ref<Char>& ref;
|
||||||
|
|
||||||
|
constexpr void on_auto()
|
||||||
|
{
|
||||||
|
int id = ctx.next_arg_id();
|
||||||
|
ref = fmt_arg_ref<Char>(id);
|
||||||
|
ctx.check_dynamic_spec(id);
|
||||||
|
}
|
||||||
|
constexpr void on_index(int id)
|
||||||
|
{
|
||||||
|
ref = fmt_arg_ref<Char>(id);
|
||||||
|
ctx.check_arg_id(id);
|
||||||
|
ctx.check_dynamic_spec(id);
|
||||||
|
}
|
||||||
|
constexpr void on_name(std::basic_string_view<Char> id)
|
||||||
|
{
|
||||||
|
ref = fmt_arg_ref<Char>(id);
|
||||||
|
ctx.check_arg_id(id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename Char>
|
||||||
|
[[nodiscard]] constexpr const Char* parse_dynamic_spec(const Char* begin, const Char* end, int& value,
|
||||||
|
fmt_arg_ref<Char>& ref,
|
||||||
|
MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx)
|
||||||
|
{
|
||||||
gsl_Expects(begin != end);
|
gsl_Expects(begin != end);
|
||||||
if ('0' <= *begin && *begin <= '9') {
|
if ('0' <= *begin && *begin <= '9') {
|
||||||
int width = 0;
|
int val = ::mp_units::detail::parse_nonnegative_int(begin, end, -1);
|
||||||
begin = parse_nonnegative_int(begin, end, width);
|
if (val != -1)
|
||||||
if (width != -1)
|
value = val;
|
||||||
handler.on_width(width);
|
|
||||||
else
|
else
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
||||||
} else if (*begin == '{') {
|
} else if (*begin == '{') {
|
||||||
++begin;
|
++begin;
|
||||||
if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler});
|
if (*begin == '%') return begin - 1; // mp-units extension
|
||||||
if (begin == end || *begin != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
auto handler = dynamic_spec_id_handler<Char>{ctx, ref};
|
||||||
++begin;
|
if (begin != end) begin = ::mp_units::detail::parse_arg_id(begin, end, handler);
|
||||||
}
|
if (begin != end && *begin == '}') return ++begin;
|
||||||
return begin;
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
|
||||||
[[nodiscard]] constexpr It parse_precision(It begin, S end, Handler&& handler)
|
|
||||||
{
|
|
||||||
struct precision_adapter {
|
|
||||||
Handler& handler;
|
|
||||||
constexpr void operator()() { handler.on_dynamic_precision(auto_id{}); }
|
|
||||||
constexpr void operator()(size_t id) { handler.on_dynamic_precision(id); }
|
|
||||||
};
|
|
||||||
|
|
||||||
++begin;
|
|
||||||
auto c = begin != end ? *begin : std::iter_value_t<It>();
|
|
||||||
if ('0' <= c && c <= '9') {
|
|
||||||
auto precision = 0;
|
|
||||||
begin = parse_nonnegative_int(begin, end, precision);
|
|
||||||
if (precision != -1)
|
|
||||||
handler.on_precision(precision);
|
|
||||||
else
|
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
|
||||||
} else if (c == '{') {
|
|
||||||
++begin;
|
|
||||||
if (begin != end) begin = parse_arg_id(begin, end, precision_adapter{handler});
|
|
||||||
if (begin == end || *begin++ != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
|
||||||
} else {
|
|
||||||
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("missing precision specifier"));
|
|
||||||
}
|
}
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
@@ -334,13 +315,13 @@ constexpr int code_point_length(It begin)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parses fill and alignment.
|
// Parses fill and alignment.
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
template<typename Char, typename Specs>
|
||||||
[[nodiscard]] constexpr It parse_align(It begin, S end, Handler&& handler)
|
[[nodiscard]] constexpr const Char* parse_align(const Char* begin, const Char* end, Specs& specs)
|
||||||
{
|
{
|
||||||
gsl_Expects(begin != end);
|
gsl_Expects(begin != end);
|
||||||
auto align = fmt_align::none;
|
auto align = fmt_align::none;
|
||||||
auto p = begin + code_point_length(begin);
|
auto p = begin + code_point_length(begin);
|
||||||
if (p >= end) p = begin;
|
if (end - p <= 0) p = begin;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
switch (to_ascii(*p)) {
|
switch (to_ascii(*p)) {
|
||||||
case '<':
|
case '<':
|
||||||
@@ -352,120 +333,28 @@ template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
|||||||
case '^':
|
case '^':
|
||||||
align = fmt_align::center;
|
align = fmt_align::center;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (align != fmt_align::none) {
|
if (align != fmt_align::none) {
|
||||||
if (p != begin) {
|
if (p != begin) {
|
||||||
auto c = *begin;
|
auto c = *begin;
|
||||||
if (c == '{') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'"));
|
if (c == '}') return begin;
|
||||||
handler.on_fill(std::basic_string_view<std::iter_value_t<It>>(&*begin, static_cast<size_t>(p - begin)));
|
if (c == '{') {
|
||||||
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'"));
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
specs.fill = {begin, to_unsigned(p - begin)};
|
||||||
begin = p + 1;
|
begin = p + 1;
|
||||||
} else
|
} else {
|
||||||
++begin;
|
++begin;
|
||||||
handler.on_align(align);
|
}
|
||||||
break;
|
break;
|
||||||
} else if (p == begin) {
|
} else if (p == begin) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
p = begin;
|
p = begin;
|
||||||
}
|
}
|
||||||
|
specs.align = align;
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses standard format specifiers and sends notifications about parsed
|
|
||||||
// components to handler.
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename SpecHandler>
|
|
||||||
[[nodiscard]] constexpr It parse_format_specs(It begin, S end, SpecHandler&& handler)
|
|
||||||
{
|
|
||||||
if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') {
|
|
||||||
handler.on_type(*begin++);
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (begin == end) return begin;
|
|
||||||
|
|
||||||
begin = ::mp_units::detail::parse_align(begin, end, handler);
|
|
||||||
if (begin == end) return begin;
|
|
||||||
|
|
||||||
// Parse sign.
|
|
||||||
begin = ::mp_units::detail::parse_sign(begin, end, handler);
|
|
||||||
if (begin == end) return begin;
|
|
||||||
|
|
||||||
if (*begin == '#') {
|
|
||||||
handler.on_hash();
|
|
||||||
if (++begin == end) return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse zero flag.
|
|
||||||
if (*begin == '0') {
|
|
||||||
handler.on_zero();
|
|
||||||
if (++begin == end) return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
begin = ::mp_units::detail::parse_width(begin, end, handler);
|
|
||||||
if (begin == end) return begin;
|
|
||||||
|
|
||||||
// Parse precision.
|
|
||||||
if (*begin == '.') {
|
|
||||||
begin = ::mp_units::detail::parse_precision(begin, end, handler);
|
|
||||||
if (begin == end) return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*begin == 'L') {
|
|
||||||
handler.on_localized();
|
|
||||||
++begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse type.
|
|
||||||
if (begin != end && *begin != '}') handler.on_type(*begin++);
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A format specifier handler that sets fields in basic_format_specs.
|
|
||||||
template<typename Char>
|
|
||||||
class specs_setter {
|
|
||||||
protected:
|
|
||||||
basic_format_specs<Char>& specs_;
|
|
||||||
public:
|
|
||||||
constexpr explicit specs_setter(basic_format_specs<Char>& specs) : specs_(specs) {}
|
|
||||||
constexpr void on_align(fmt_align align) { specs_.align = align; }
|
|
||||||
constexpr void on_fill(std::basic_string_view<Char> fill) { specs_.fill = fill; }
|
|
||||||
constexpr void on_sign(fmt_sign s) { specs_.sign = s; }
|
|
||||||
constexpr void on_hash() { specs_.alt = true; }
|
|
||||||
constexpr void on_localized() { specs_.localized = true; }
|
|
||||||
constexpr void on_zero() { specs_.fill[0] = Char('0'); }
|
|
||||||
constexpr void on_width(int width) { specs_.width = width; }
|
|
||||||
constexpr void on_precision(int precision) { specs_.precision = precision; }
|
|
||||||
constexpr void on_type(Char type) { specs_.type = static_cast<char>(type); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format spec handler that saves references to arguments representing dynamic
|
|
||||||
// width and precision to be resolved at formatting time.
|
|
||||||
template<typename ParseContext>
|
|
||||||
class dynamic_specs_handler : public specs_setter<typename ParseContext::char_type> {
|
|
||||||
public:
|
|
||||||
using char_type = MP_UNITS_TYPENAME ParseContext::char_type;
|
|
||||||
|
|
||||||
constexpr dynamic_specs_handler(dynamic_format_specs<char_type>& specs, ParseContext& ctx) :
|
|
||||||
specs_setter<char_type>(specs), specs_(specs), context_(ctx)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr void on_dynamic_width(T t)
|
|
||||||
{
|
|
||||||
specs_.dynamic_width_index = on_dynamic_arg(t, context_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr void on_dynamic_precision(T t)
|
|
||||||
{
|
|
||||||
specs_.dynamic_precision_index = on_dynamic_arg(t, context_);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
dynamic_format_specs<char_type>& specs_;
|
|
||||||
ParseContext& context_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace mp_units::detail
|
} // namespace mp_units::detail
|
||||||
|
@@ -29,241 +29,19 @@
|
|||||||
#include <mp-units/unit.h>
|
#include <mp-units/unit.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
// Grammar
|
|
||||||
//
|
|
||||||
// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs]
|
|
||||||
// quantity-specs ::= conversion-spec
|
|
||||||
// quantity-specs conversion-spec
|
|
||||||
// quantity-specs literal-char
|
|
||||||
// literal-char ::= any character other than '{' or '}'
|
|
||||||
// conversion-spec ::= '%' type
|
|
||||||
// type ::= [rep-modifier] 'Q'
|
|
||||||
// [unit-modifier] 'q'
|
|
||||||
// rep-modifier ::= [sign] [#] [precision] [L] [rep-type]
|
|
||||||
// rep-type ::= one of
|
|
||||||
// a A b B d e E f F g G o x X
|
|
||||||
// unit-modifier ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator]
|
|
||||||
// [text-encoding] [unit-symbol-separator] [unit-symbol-solidus]
|
|
||||||
// [unit-symbol-solidus] [text-encoding] [unit-symbol-separator]
|
|
||||||
// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding]
|
|
||||||
// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus]
|
|
||||||
// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding]
|
|
||||||
// text-encoding ::= one of
|
|
||||||
// U A
|
|
||||||
// unit-symbol-solidus ::= one of
|
|
||||||
// o a n
|
|
||||||
// unit-symbol-separator ::= one of
|
|
||||||
// s d
|
|
||||||
|
|
||||||
|
|
||||||
// TODO Should the below be allowed? Is it even possible to implement with `format()` being const?
|
|
||||||
// std::cout << std::format("{:%Q %q %Q %q}\n", 123s);
|
|
||||||
|
|
||||||
namespace mp_units::detail {
|
namespace mp_units::detail {
|
||||||
|
|
||||||
// Holds specs about the whole object
|
template<typename Char>
|
||||||
template<typename CharT>
|
struct fill_align_width_format_specs {
|
||||||
struct quantity_global_format_specs {
|
fill_t<Char> fill;
|
||||||
fill_t<CharT> fill;
|
fmt_align align : 4 = fmt_align::none;
|
||||||
fmt_align align = fmt_align::none;
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int dynamic_width_index = -1;
|
fmt_arg_ref<Char> width_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Holds specs about the representation (%[specs]Q)
|
template<typename Char>
|
||||||
struct quantity_rep_format_specs {
|
[[nodiscard]] constexpr const Char* at_most_one_of(const Char* begin, const Char* end, std::string_view modifiers)
|
||||||
fmt_sign sign = fmt_sign::none;
|
|
||||||
int precision = -1;
|
|
||||||
int dynamic_precision_index = -1;
|
|
||||||
char type = '\0';
|
|
||||||
bool alt = false;
|
|
||||||
bool localized = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Holds specs about the unit (%[specs]q)
|
|
||||||
struct quantity_unit_format_specs : unit_symbol_formatting {};
|
|
||||||
|
|
||||||
template<typename CharT>
|
|
||||||
struct quantity_format_specs {
|
|
||||||
quantity_global_format_specs<CharT> global;
|
|
||||||
quantity_rep_format_specs rep;
|
|
||||||
quantity_unit_format_specs unit;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse a `units-rep-modifier`
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
|
||||||
constexpr It parse_units_rep(It begin, S end, Handler&& handler, bool treat_as_floating_point)
|
|
||||||
{
|
|
||||||
// parse sign
|
|
||||||
begin = parse_sign(begin, end, handler);
|
|
||||||
if (begin == end) return begin;
|
|
||||||
|
|
||||||
// parse #
|
|
||||||
if (*begin == '#') {
|
|
||||||
handler.on_hash();
|
|
||||||
if (++begin == end) return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse precision if a floating point
|
|
||||||
if (*begin == '.') {
|
|
||||||
if (treat_as_floating_point) {
|
|
||||||
begin = parse_precision(begin, end, handler);
|
|
||||||
} else
|
|
||||||
throw MP_UNITS_STD_FMT::format_error("precision not allowed for integral quantity representation");
|
|
||||||
if (begin == end) return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse L to enable the locale-specific form
|
|
||||||
if (*begin == 'L') {
|
|
||||||
handler.on_localized();
|
|
||||||
++begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (begin != end && *begin != '}' && *begin != '%') {
|
|
||||||
handler.on_type(*begin++);
|
|
||||||
}
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse units-specs
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
|
|
||||||
constexpr It parse_units_format(It begin, S end, Handler&& handler)
|
|
||||||
{
|
|
||||||
auto ptr = begin;
|
|
||||||
while (ptr != end) {
|
|
||||||
auto c = *ptr;
|
|
||||||
if (c == '}') break;
|
|
||||||
if (c != '%') {
|
|
||||||
++ptr;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (begin != ptr) handler.on_text(begin, ptr);
|
|
||||||
begin = ++ptr; // consume '%'
|
|
||||||
if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid format");
|
|
||||||
c = *ptr++;
|
|
||||||
|
|
||||||
constexpr auto units_types = std::string_view{"Qq"};
|
|
||||||
const auto new_end = find_first_of(begin, end, units_types.begin(), units_types.end());
|
|
||||||
if (new_end == end) throw MP_UNITS_STD_FMT::format_error("invalid format");
|
|
||||||
if (*new_end == 'Q') {
|
|
||||||
handler.on_quantity_value(begin, new_end); // Edit `on_quantity_value` to add rep modifiers
|
|
||||||
} else {
|
|
||||||
handler.on_quantity_unit(begin, new_end); // Edit `on_quantity_unit` to add an unit modifier
|
|
||||||
}
|
|
||||||
ptr = new_end + 1;
|
|
||||||
begin = ptr;
|
|
||||||
}
|
|
||||||
if (begin != ptr) handler.on_text(begin, ptr);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the 'representation' as requested in the format string, applying only units-rep-modifiers
|
|
||||||
template<typename CharT, typename Rep, typename OutputIt, typename Locale>
|
|
||||||
[[nodiscard]] OutputIt format_units_quantity_value(OutputIt out, const Rep& val,
|
|
||||||
const quantity_rep_format_specs& rep_specs, const Locale& loc)
|
|
||||||
{
|
|
||||||
std::basic_string<CharT> buffer;
|
|
||||||
auto to_buffer = std::back_inserter(buffer);
|
|
||||||
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "{{:");
|
|
||||||
switch (rep_specs.sign) {
|
|
||||||
case fmt_sign::none:
|
|
||||||
break;
|
|
||||||
case fmt_sign::plus:
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "+");
|
|
||||||
break;
|
|
||||||
case fmt_sign::minus:
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "-");
|
|
||||||
break;
|
|
||||||
case fmt_sign::space:
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, " ");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rep_specs.alt) {
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "#");
|
|
||||||
}
|
|
||||||
auto type = rep_specs.type;
|
|
||||||
if (auto precision = rep_specs.precision; precision >= 0) {
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type);
|
|
||||||
} else if constexpr (treat_as_floating_point<Rep>) {
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type == '\0' ? 'g' : type);
|
|
||||||
} else {
|
|
||||||
if (type != '\0') {
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rep_specs.localized) {
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "L");
|
|
||||||
}
|
|
||||||
|
|
||||||
MP_UNITS_STD_FMT::format_to(to_buffer, "}}");
|
|
||||||
if (rep_specs.localized) {
|
|
||||||
return MP_UNITS_STD_FMT::vformat_to(out, MP_UNITS_FMT_LOCALE(loc), buffer, MP_UNITS_STD_FMT::make_format_args(val));
|
|
||||||
}
|
|
||||||
return MP_UNITS_STD_FMT::vformat_to(out, buffer, MP_UNITS_STD_FMT::make_format_args(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a global format string
|
|
||||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
|
|
||||||
template<typename CharT, typename OutputIt>
|
|
||||||
OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs<CharT>& specs)
|
|
||||||
{
|
|
||||||
MP_UNITS_STD_FMT::format_to(out, "{{:");
|
|
||||||
if (specs.fill.size() != 1 || specs.fill[0] != ' ') {
|
|
||||||
MP_UNITS_STD_FMT::format_to(out, "{}", specs.fill.data());
|
|
||||||
}
|
|
||||||
switch (specs.align) {
|
|
||||||
case fmt_align::left:
|
|
||||||
MP_UNITS_STD_FMT::format_to(out, "<");
|
|
||||||
break;
|
|
||||||
case fmt_align::right:
|
|
||||||
MP_UNITS_STD_FMT::format_to(out, ">");
|
|
||||||
break;
|
|
||||||
case fmt_align::center:
|
|
||||||
MP_UNITS_STD_FMT::format_to(out, "^");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (specs.width >= 1) MP_UNITS_STD_FMT::format_to(out, "{}", specs.width);
|
|
||||||
return MP_UNITS_STD_FMT::format_to(out, "}}");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<auto Reference, typename Rep, typename Locale, typename CharT, typename OutputIt>
|
|
||||||
struct quantity_formatter {
|
|
||||||
OutputIt out;
|
|
||||||
Rep val;
|
|
||||||
const quantity_format_specs<CharT>& specs;
|
|
||||||
Locale loc;
|
|
||||||
|
|
||||||
explicit quantity_formatter(OutputIt o, const quantity<Reference, Rep>& q, const quantity_format_specs<CharT>& fspecs,
|
|
||||||
Locale lc) :
|
|
||||||
out(o), val(q.numerical_value_ref_in(q.unit)), specs(fspecs), loc(std::move(lc))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
void on_text(It begin, S end)
|
|
||||||
{
|
|
||||||
std::copy(begin, end, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
void on_quantity_value([[maybe_unused]] It, [[maybe_unused]] S)
|
|
||||||
{
|
|
||||||
out = format_units_quantity_value<CharT>(out, val, specs.rep, loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
void on_quantity_unit(It, S)
|
|
||||||
{
|
|
||||||
out = unit_symbol_to<CharT>(out, get_unit(Reference), specs.unit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
[[nodiscard]] constexpr It at_most_one_of(It begin, S end, std::string_view modifiers)
|
|
||||||
{
|
{
|
||||||
auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end());
|
auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end());
|
||||||
if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end)
|
if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end)
|
||||||
@@ -274,194 +52,350 @@ template<std::input_iterator It, std::sentinel_for<It> S>
|
|||||||
|
|
||||||
} // namespace mp_units::detail
|
} // namespace mp_units::detail
|
||||||
|
|
||||||
template<auto Reference, typename Rep, typename CharT>
|
//
|
||||||
struct MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, CharT> {
|
// Grammar
|
||||||
private:
|
//
|
||||||
using quantity = mp_units::quantity<Reference, Rep>;
|
// dimension-format-spec ::= [fill-and-align] [width] [dimension-spec]
|
||||||
using iterator = MP_UNITS_TYPENAME MP_UNITS_STD_FMT::basic_format_parse_context<CharT>::iterator;
|
// dimension-spec ::= [text-encoding]
|
||||||
|
|
||||||
bool quantity_value = false;
|
// template<typename Char>
|
||||||
bool quantity_unit = false;
|
// struct dimension_format_specs : fill_align_width_format_specs<Char>, dimension_symbol_formatting {};
|
||||||
mp_units::detail::quantity_format_specs<CharT> specs;
|
|
||||||
std::basic_string_view<CharT> format_str;
|
|
||||||
|
|
||||||
struct spec_handler {
|
|
||||||
formatter& f;
|
|
||||||
MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& context;
|
|
||||||
|
|
||||||
constexpr void on_fill(std::basic_string_view<CharT> fill) { f.specs.global.fill = fill; }
|
//
|
||||||
constexpr void on_align(mp_units::detail::fmt_align align) { f.specs.global.align = align; }
|
// Grammar
|
||||||
constexpr void on_width(int width) { f.specs.global.width = width; }
|
//
|
||||||
constexpr void on_sign(mp_units::detail::fmt_sign sign) { f.specs.rep.sign = sign; }
|
// unit-format-spec ::= [fill-and-align] [width] [unit-spec]
|
||||||
constexpr void on_hash() { f.specs.rep.alt = true; }
|
// unit-spec ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] [L]
|
||||||
constexpr void on_precision(int precision) { f.specs.rep.precision = precision; }
|
// [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] [L]
|
||||||
constexpr void on_localized() { f.specs.rep.localized = true; }
|
// [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] [L]
|
||||||
|
// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] [L]
|
||||||
|
// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] [L]
|
||||||
|
// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] [L]
|
||||||
|
// text-encoding ::= one of
|
||||||
|
// U A
|
||||||
|
// unit-symbol-solidus ::= one of
|
||||||
|
// o a n
|
||||||
|
// unit-symbol-separator ::= one of
|
||||||
|
// s d
|
||||||
|
template<mp_units::Unit U, typename Char>
|
||||||
|
class MP_UNITS_STD_FMT::formatter<U, Char> {
|
||||||
|
struct format_specs : mp_units::detail::fill_align_width_format_specs<Char>, mp_units::unit_symbol_formatting {};
|
||||||
|
|
||||||
constexpr void on_type(char type)
|
std::basic_string_view<Char> fill_align_width_format_str_;
|
||||||
|
std::basic_string_view<Char> modifiers_format_str_;
|
||||||
|
format_specs specs_{};
|
||||||
|
|
||||||
|
struct format_checker {
|
||||||
|
using enum mp_units::text_encoding;
|
||||||
|
|
||||||
|
mp_units::text_encoding encoding = unicode;
|
||||||
|
|
||||||
|
constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; }
|
||||||
|
constexpr void on_unit_symbol_solidus(Char) const {}
|
||||||
|
constexpr void on_unit_symbol_separator(Char val) const
|
||||||
{
|
{
|
||||||
constexpr auto valid_rep_types = std::string_view{"aAbBdeEfFgGoxX"};
|
if (val == 'd' && encoding == ascii)
|
||||||
if (valid_rep_types.find(type) != std::string_view::npos) {
|
|
||||||
f.specs.rep.type = type;
|
|
||||||
} else {
|
|
||||||
throw MP_UNITS_STD_FMT::format_error("invalid quantity type specifier");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr void on_dynamic_width(T t)
|
|
||||||
{
|
|
||||||
f.specs.global.dynamic_width_index = mp_units::detail::on_dynamic_arg(t, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
constexpr void on_dynamic_precision(T t)
|
|
||||||
{
|
|
||||||
f.specs.rep.dynamic_precision_index = mp_units::detail::on_dynamic_arg(t, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
constexpr void on_text(It, S)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
constexpr void on_quantity_value(It begin, S end)
|
|
||||||
{
|
|
||||||
if (begin != end) mp_units::detail::parse_units_rep(begin, end, *this, mp_units::treat_as_floating_point<Rep>);
|
|
||||||
f.quantity_value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::input_iterator It, std::sentinel_for<It> S>
|
|
||||||
constexpr void on_quantity_unit(It begin, S end)
|
|
||||||
{
|
|
||||||
if (begin == end) return;
|
|
||||||
|
|
||||||
constexpr auto valid_modifiers = std::string_view{"UAoansd"};
|
|
||||||
for (auto it = begin; it != end; ++it) {
|
|
||||||
if (valid_modifiers.find(*it) == std::string_view::npos)
|
|
||||||
throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) {
|
|
||||||
if (*it == 'U')
|
|
||||||
f.specs.unit.encoding = mp_units::text_encoding::unicode;
|
|
||||||
else
|
|
||||||
f.specs.unit.encoding = mp_units::text_encoding::ascii;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto it = mp_units::detail::at_most_one_of(begin, end, "oan"); it != end) {
|
|
||||||
if (*it == 'o')
|
|
||||||
f.specs.unit.solidus = mp_units::unit_symbol_solidus::one_denominator;
|
|
||||||
else if (*it == 'a')
|
|
||||||
f.specs.unit.solidus = mp_units::unit_symbol_solidus::always;
|
|
||||||
else
|
|
||||||
f.specs.unit.solidus = mp_units::unit_symbol_solidus::never;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) {
|
|
||||||
if (*it == 's')
|
|
||||||
f.specs.unit.separator = mp_units::unit_symbol_separator::space;
|
|
||||||
else {
|
|
||||||
if (f.specs.unit.encoding == mp_units::text_encoding::ascii)
|
|
||||||
throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding");
|
throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding");
|
||||||
f.specs.unit.separator = mp_units::unit_symbol_separator::half_high_dot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.quantity_unit = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::pair<iterator, iterator> do_parse(
|
struct unit_formatter {
|
||||||
MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx)
|
format_specs specs;
|
||||||
|
|
||||||
|
using enum mp_units::text_encoding;
|
||||||
|
using enum mp_units::unit_symbol_solidus;
|
||||||
|
using enum mp_units::unit_symbol_separator;
|
||||||
|
|
||||||
|
constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; }
|
||||||
|
constexpr void on_unit_symbol_solidus(Char val)
|
||||||
{
|
{
|
||||||
auto begin = ctx.begin();
|
switch (val) {
|
||||||
auto end = ctx.end();
|
case 'o':
|
||||||
|
specs.solidus = one_denominator;
|
||||||
if (begin == end || *begin == '}') return {begin, begin};
|
break;
|
||||||
|
case 'a':
|
||||||
// handler to assign parsed data to formatter data members
|
specs.solidus = always;
|
||||||
spec_handler handler{*this, ctx};
|
break;
|
||||||
|
case 'n':
|
||||||
// parse alignment
|
specs.solidus = never;
|
||||||
begin = mp_units::detail::parse_align(begin, end, handler);
|
break;
|
||||||
if (begin == end) return {begin, begin};
|
|
||||||
|
|
||||||
// parse width
|
|
||||||
begin = mp_units::detail::parse_width(begin, end, handler);
|
|
||||||
if (begin == end) return {begin, begin};
|
|
||||||
|
|
||||||
// parse units-specific specification
|
|
||||||
end = mp_units::detail::parse_units_format(begin, end, handler);
|
|
||||||
|
|
||||||
if (specs.global.align == mp_units::detail::fmt_align::none && (!quantity_unit || quantity_value))
|
|
||||||
// quantity values should behave like numbers (by default aligned to right)
|
|
||||||
specs.global.align = mp_units::detail::fmt_align::right;
|
|
||||||
|
|
||||||
return {begin, end};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
constexpr void on_unit_symbol_separator(Char val) { specs.separator = (val == 's') ? space : half_high_dot; }
|
||||||
|
};
|
||||||
|
|
||||||
template<typename OutputIt, typename FormatContext>
|
template<typename Handler>
|
||||||
OutputIt format_quantity_content(OutputIt out, const quantity& q, FormatContext& ctx)
|
constexpr const Char* parse_unit_specs(const Char* begin, const Char* end, Handler&& handler) const
|
||||||
{
|
{
|
||||||
auto begin = format_str.begin();
|
auto it = begin;
|
||||||
auto end = format_str.end();
|
if (it == end || *it == '}') return begin;
|
||||||
|
|
||||||
if (begin == end || *begin == '}') {
|
constexpr auto valid_modifiers = std::string_view{"UAoansd"};
|
||||||
// default format should print value followed by the unit separated with 1 space
|
for (; it != end && *it != '}'; ++it) {
|
||||||
out = mp_units::detail::format_units_quantity_value<CharT>(out, q.numerical_value_ref_in(q.unit), specs.rep,
|
if (valid_modifiers.find(*it) == std::string_view::npos)
|
||||||
ctx.locale());
|
throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified");
|
||||||
if constexpr (mp_units::detail::has_unit_symbol(get_unit(Reference))) {
|
|
||||||
if constexpr (mp_units::space_before_unit_symbol<get_unit(Reference)>) *out++ = CharT(' ');
|
|
||||||
out = unit_symbol_to<CharT>(out, get_unit(Reference));
|
|
||||||
}
|
}
|
||||||
} else {
|
end = it;
|
||||||
// user provided format
|
|
||||||
mp_units::detail::quantity_formatter f(out, q, specs, ctx.locale());
|
if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it);
|
||||||
mp_units::detail::parse_units_format(begin, end, f);
|
if (it = mp_units::detail::at_most_one_of(begin, end, "oan"); it != end) handler.on_unit_symbol_solidus(*it);
|
||||||
}
|
if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) handler.on_unit_symbol_separator(*it);
|
||||||
return out;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx)
|
constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
|
||||||
{
|
{
|
||||||
auto range = do_parse(ctx);
|
const auto begin = ctx.begin();
|
||||||
if (range.first != range.second)
|
auto it = begin, end = ctx.end();
|
||||||
format_str = std::basic_string_view<CharT>(&*range.first, static_cast<size_t>(range.second - range.first));
|
if (it == end || *it == '}') return it;
|
||||||
return range.second;
|
|
||||||
|
it = mp_units::detail::parse_align(it, end, specs_);
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
it = mp_units::detail::parse_dynamic_spec(it, end, specs_.width, specs_.width_ref, ctx);
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
fill_align_width_format_str_ = {begin, it};
|
||||||
|
|
||||||
|
format_checker checker;
|
||||||
|
end = parse_unit_specs(it, end, checker);
|
||||||
|
modifiers_format_str_ = {it, end};
|
||||||
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatContext>
|
template<typename FormatContext>
|
||||||
auto format(const quantity& q, FormatContext& ctx)
|
auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out())
|
||||||
{
|
{
|
||||||
// process dynamic width and precision
|
auto specs = specs_;
|
||||||
if (specs.global.dynamic_width_index >= 0)
|
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
|
||||||
specs.global.width =
|
|
||||||
mp_units::detail::get_dynamic_spec<mp_units::detail::width_checker>(specs.global.dynamic_width_index, ctx);
|
|
||||||
if (specs.rep.dynamic_precision_index >= 0)
|
|
||||||
specs.rep.precision =
|
|
||||||
mp_units::detail::get_dynamic_spec<mp_units::detail::precision_checker>(specs.rep.dynamic_precision_index, ctx);
|
|
||||||
|
|
||||||
if (specs.global.width == 0) {
|
if (specs.width == 0) {
|
||||||
// Avoid extra copying if width is not specified
|
// Avoid extra copying if width is not specified
|
||||||
return format_quantity_content(ctx.out(), q, ctx);
|
return mp_units::unit_symbol_to<Char>(ctx.out(), u, specs);
|
||||||
} else {
|
} else {
|
||||||
// In `quantity_buffer` we will have the representation and the unit formatted according to their
|
std::basic_string<Char> unit_buffer;
|
||||||
// specification, ignoring global specifiers
|
mp_units::unit_symbol_to<Char>(std::back_inserter(unit_buffer), u, specs);
|
||||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "1.2_m"
|
|
||||||
std::basic_string<CharT> quantity_buffer;
|
|
||||||
|
|
||||||
// deal with quantity content
|
std::basic_string<Char> global_format_buffer = "{:" + std::basic_string<Char>{fill_align_width_format_str_} + "}";
|
||||||
format_quantity_content(std::back_inserter(quantity_buffer), q, ctx);
|
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
|
||||||
|
MP_UNITS_STD_FMT::make_format_args(unit_buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// In `global_format_buffer` we will create a global format string
|
|
||||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
|
|
||||||
std::basic_string<CharT> global_format_buffer;
|
|
||||||
mp_units::detail::format_global_buffer<CharT>(std::back_inserter(global_format_buffer), specs.global);
|
|
||||||
|
|
||||||
// Format the `quantity buffer` using specs from `global_format_buffer`
|
//
|
||||||
// In the example, equivalent to MP_UNITS_STD_FMT::format("{:*^10}", "1.2_m")
|
// Grammar
|
||||||
|
//
|
||||||
|
// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs]
|
||||||
|
// quantity-specs ::= conversion-spec
|
||||||
|
// quantity-specs conversion-spec
|
||||||
|
// quantity-specs literal-char
|
||||||
|
// literal-char ::= any character other than '{', '}', or '%'
|
||||||
|
// conversion-spec ::= placement-spec
|
||||||
|
// subentity-replacement-field
|
||||||
|
// placement-spec ::= '%' placement-type
|
||||||
|
// placement-type ::= one of
|
||||||
|
// N U D ? %
|
||||||
|
// subentity-replacement-field ::= { % subentity-id [format-specifier] }
|
||||||
|
// subentity-id ::= any character other than {, }, or %
|
||||||
|
// format-specifier ::= format-spec
|
||||||
|
// format-spec ::= as specified by the formatter for the argument type; cannot start with }
|
||||||
|
//
|
||||||
|
template<auto Reference, typename Rep, typename Char>
|
||||||
|
class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
|
||||||
|
static constexpr auto unit = get_unit(Reference);
|
||||||
|
static constexpr auto dimension = get_quantity_spec(Reference).dimension;
|
||||||
|
|
||||||
|
using quantity_t = mp_units::quantity<Reference, Rep>;
|
||||||
|
using unit_t = std::remove_const_t<decltype(unit)>;
|
||||||
|
using dimension_t = std::remove_const_t<decltype(dimension)>;
|
||||||
|
|
||||||
|
using format_specs = mp_units::detail::fill_align_width_format_specs<Char>;
|
||||||
|
|
||||||
|
std::basic_string_view<Char> fill_align_width_format_str_;
|
||||||
|
std::basic_string_view<Char> modifiers_format_str_;
|
||||||
|
std::vector<size_t> format_str_lengths_;
|
||||||
|
format_specs specs_{};
|
||||||
|
|
||||||
|
struct format_checker {
|
||||||
|
MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx;
|
||||||
|
std::vector<size_t>& format_str_lengths;
|
||||||
|
|
||||||
|
constexpr void on_number(std::basic_string_view<Char>) const {}
|
||||||
|
constexpr void on_maybe_space() const {}
|
||||||
|
constexpr void on_unit(std::basic_string_view<Char>) const {}
|
||||||
|
constexpr void on_dimension(std::basic_string_view<Char>) const {}
|
||||||
|
constexpr void on_text(const Char*, const Char*) const {}
|
||||||
|
|
||||||
|
constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)
|
||||||
|
{
|
||||||
|
if (id == "N")
|
||||||
|
return on_replacement_field<Rep>(begin);
|
||||||
|
else if (id == "U")
|
||||||
|
return on_replacement_field<unit_t>(begin);
|
||||||
|
else if (id == "D") {
|
||||||
|
return begin;
|
||||||
|
// on_replacement_field<dimension_t>(begin);
|
||||||
|
} else
|
||||||
|
throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
constexpr const Char* on_replacement_field(const Char* begin) const
|
||||||
|
{
|
||||||
|
MP_UNITS_STD_FMT::formatter<T> sf;
|
||||||
|
ctx.advance_to(begin);
|
||||||
|
auto ptr = sf.parse(ctx);
|
||||||
|
if (*ptr != '}') throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
format_str_lengths.push_back(mp_units::detail::to_unsigned(ptr - begin));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename OutputIt>
|
||||||
|
struct quantity_formatter {
|
||||||
|
OutputIt out;
|
||||||
|
const quantity_t& q;
|
||||||
|
std::vector<size_t>::const_iterator format_str_lengths_it;
|
||||||
|
std::locale locale;
|
||||||
|
|
||||||
|
void on_number(std::basic_string_view<Char> format_str)
|
||||||
|
{
|
||||||
|
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str,
|
||||||
|
MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit)));
|
||||||
|
}
|
||||||
|
void on_maybe_space()
|
||||||
|
{
|
||||||
|
if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ';
|
||||||
|
}
|
||||||
|
void on_unit(std::basic_string_view<Char> format_str)
|
||||||
|
{
|
||||||
|
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit));
|
||||||
|
}
|
||||||
|
void on_dimension(std::basic_string_view<Char>) {}
|
||||||
|
void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); }
|
||||||
|
|
||||||
|
constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)
|
||||||
|
{
|
||||||
|
auto format_str = [&] { return "{:" + std::string(begin, *format_str_lengths_it + 1); };
|
||||||
|
if (id == "N")
|
||||||
|
on_number(format_str());
|
||||||
|
else if (id == "U")
|
||||||
|
on_unit(format_str());
|
||||||
|
else if (id == "D")
|
||||||
|
on_dimension(format_str());
|
||||||
|
else
|
||||||
|
throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
return begin + *format_str_lengths_it++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Handler>
|
||||||
|
constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const
|
||||||
|
{
|
||||||
|
if (begin == end || *begin == '}') return begin;
|
||||||
|
if (*begin != '%' && *begin != '{') throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
auto ptr = begin;
|
||||||
|
while (ptr != end) {
|
||||||
|
auto c = *ptr;
|
||||||
|
if (c == '}') break;
|
||||||
|
if (c == '{') {
|
||||||
|
if (begin != ptr) handler.on_text(begin, ptr);
|
||||||
|
begin = ptr = mp_units::detail::parse_subentity_replacement_field(ptr, end, handler);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c != '%') {
|
||||||
|
++ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (begin != ptr) handler.on_text(begin, ptr);
|
||||||
|
++ptr; // consume '%'
|
||||||
|
if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
|
||||||
|
c = *ptr++;
|
||||||
|
switch (c) {
|
||||||
|
case 'N':
|
||||||
|
handler.on_number("{}");
|
||||||
|
break;
|
||||||
|
case 'U':
|
||||||
|
handler.on_unit("{}");
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
handler.on_dimension("{}");
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
handler.on_maybe_space();
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
handler.on_text(ptr - 1, ptr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw MP_UNITS_STD_FMT::format_error("invalid format");
|
||||||
|
}
|
||||||
|
begin = ptr;
|
||||||
|
}
|
||||||
|
if (begin != ptr) handler.on_text(begin, ptr);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename OutputIt, typename FormatContext>
|
||||||
|
OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const
|
||||||
|
{
|
||||||
|
std::locale locale = ctx.locale().template get<std::locale>();
|
||||||
|
if (modifiers_format_str_.empty()) {
|
||||||
|
// default format should print value followed by the unit separated with 1 space
|
||||||
|
out = MP_UNITS_STD_FMT::vformat_to(out, locale, "{}",
|
||||||
|
MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit)));
|
||||||
|
if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ';
|
||||||
|
return MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", MP_UNITS_STD_FMT::make_format_args(q.unit));
|
||||||
|
} else {
|
||||||
|
// user provided format
|
||||||
|
quantity_formatter f{out, q, format_str_lengths_.cbegin(), locale};
|
||||||
|
parse_quantity_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f);
|
||||||
|
return f.out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// parse quantity-format-specs
|
||||||
|
constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
|
||||||
|
{
|
||||||
|
const auto begin = ctx.begin();
|
||||||
|
auto it = begin, end = ctx.end();
|
||||||
|
if (it == end || *it == '}') return it;
|
||||||
|
|
||||||
|
it = mp_units::detail::parse_align(it, end, specs_);
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
it = mp_units::detail::parse_dynamic_spec(it, end, specs_.width, specs_.width_ref, ctx);
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
fill_align_width_format_str_ = {begin, it};
|
||||||
|
|
||||||
|
format_checker checker(ctx, format_str_lengths_);
|
||||||
|
end = parse_quantity_specs(it, end, checker);
|
||||||
|
modifiers_format_str_ = {it, end};
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatContext>
|
||||||
|
auto format(const quantity_t& q, FormatContext& ctx) const -> decltype(ctx.out())
|
||||||
|
{
|
||||||
|
auto specs = specs_;
|
||||||
|
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
|
||||||
|
|
||||||
|
if (specs.width == 0) {
|
||||||
|
// Avoid extra copying if width is not specified
|
||||||
|
return format_quantity(ctx.out(), q, ctx);
|
||||||
|
} else {
|
||||||
|
std::basic_string<Char> quantity_buffer;
|
||||||
|
format_quantity(std::back_inserter(quantity_buffer), q, ctx);
|
||||||
|
|
||||||
|
std::basic_string<Char> global_format_buffer = "{:" + std::basic_string<Char>{fill_align_width_format_str_} + "}";
|
||||||
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
|
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
|
||||||
MP_UNITS_STD_FMT::make_format_args(quantity_buffer));
|
MP_UNITS_STD_FMT::make_format_args(quantity_buffer));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user