refactor: core-fmt and core-ostream modules merged with core

This commit is contained in:
Mateusz Pusz
2024-01-06 08:51:01 +01:00
parent 614d82dece
commit fb8552b1d9
28 changed files with 80 additions and 198 deletions
+14
View File
@@ -42,6 +42,7 @@ add_units_module(
include/mp-units/bits/core_gmf.h
include/mp-units/bits/dimension_concepts.h
include/mp-units/bits/expression_template.h
include/mp-units/bits/fmt.h
include/mp-units/bits/get_associated_quantity.h
include/mp-units/bits/get_common_base.h
include/mp-units/bits/magnitude.h
@@ -63,7 +64,9 @@ add_units_module(
include/mp-units/core.h
include/mp-units/customization_points.h
include/mp-units/dimension.h
include/mp-units/format.h
include/mp-units/math.h
include/mp-units/ostream.h
include/mp-units/quantity.h
include/mp-units/quantity_point.h
include/mp-units/quantity_spec.h
@@ -74,6 +77,17 @@ add_units_module(
MODULE_INTERFACE_UNIT mp-units-core.cpp
)
target_compile_definitions(
mp-units-core ${${projectPrefix}TARGET_SCOPE} ${projectPrefix}USE_LIBFMT=$<BOOL:${${projectPrefix}USE_LIBFMT}>
)
if(${projectPrefix}USE_LIBFMT)
if(NOT TARGET fmt::fmt)
find_package(fmt CONFIG REQUIRED)
endif()
target_link_libraries(mp-units-core ${${projectPrefix}TARGET_SCOPE} fmt::fmt)
endif()
if(${projectPrefix}BUILD_CXX_MODULES)
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18)
+47
View File
@@ -99,3 +99,50 @@
#define MP_UNITS_CONSTRAINED_NTTP_WORKAROUND(X) X
#endif
#ifndef MP_UNITS_USE_LIBFMT
#define MP_UNITS_USE_LIBFMT 1
#endif
#if MP_UNITS_USE_LIBFMT
MP_UNITS_DIAGNOSTIC_PUSH
MP_UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE
MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW
#include <fmt/format.h>
MP_UNITS_DIAGNOSTIC_POP
#define MP_UNITS_STD_FMT fmt
#define MP_UNITS_FMT_LOCALE(loc) (loc).template get<std::locale>()
#define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast<int>(arg)
#define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast<size_t>(arg)
// This re-uses code from fmt;
#if FMT_EXCEPTIONS
#if FMT_MSC_VERSION || defined(__NVCC__)
#define MP_UNITS_THROW(x) ::fmt::detail::do_throw(x)
#else
#define MP_UNITS_THROW(x) throw x
#endif
#else
#define MP_UNITS_THROW(x) \
do { \
FMT_ASSERT(false, (x).what()); \
} while (false)
#endif
#else
#ifndef __cpp_lib_format
#error "std::formatting facility not supported"
#endif
#include <format>
#define MP_UNITS_STD_FMT std
#define MP_UNITS_FMT_LOCALE(loc) loc
#define MP_UNITS_FMT_TO_ARG_ID(arg) arg
#define MP_UNITS_FMT_FROM_ARG_ID(arg) arg
#define MP_UNITS_THROW(arg) throw arg
#endif
+471
View File
@@ -0,0 +1,471 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// Formatting library for C++ - the core API for char/UTF-8
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#pragma once
#include <gsl/gsl-lite.hpp>
#include <mp-units/bits/external/hacks.h>
#include <concepts>
#include <limits>
#include <string_view>
// most of the below code is based on/copied from libfmt
namespace mp_units::detail {
struct auto_id {};
enum class fmt_align { none, left, right, center };
enum class fmt_sign { none, minus, plus, space };
enum class arg_id_kind { none, index, name };
template<typename Char>
struct fill_t {
private:
static constexpr size_t max_size = 4 / sizeof(Char);
// At most one codepoint (so one char32_t or four utf-8 char8_t)
Char data_[max_size] = {Char{' '}};
unsigned char size_ = 1;
public:
constexpr fill_t& operator=(std::basic_string_view<Char> str)
{
auto size = str.size();
if (size > max_size) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill"));
for (size_t i = 0; i < size; ++i) data_[i] = str[i];
size_ = static_cast<unsigned char>(size);
return *this;
}
[[nodiscard]] constexpr size_t size() const { return size_; }
[[nodiscard]] constexpr const Char* data() const { return data_; }
[[nodiscard]] constexpr Char& operator[](size_t index) { return data_[index]; }
[[nodiscard]] constexpr const Char& operator[](size_t index) const { return data_[index]; }
};
template<typename T>
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;
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.
template<std::integral Char>
[[nodiscard]] constexpr Char to_ascii(Char value)
{
return value;
}
template<typename Char>
requires std::is_enum_v<Char>
[[nodiscard]] constexpr auto to_ascii(Char value) -> std::underlying_type_t<Char>
{
return value;
}
struct width_checker {
template<typename T>
[[nodiscard]] constexpr unsigned long long operator()(T value) const
{
if constexpr (is_integer<T>) {
if constexpr (std::numeric_limits<T>::is_signed) {
if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width"));
}
return static_cast<unsigned long long>(value);
}
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer"));
return 0; // should never happen
}
};
struct precision_checker {
template<typename T>
[[nodiscard]] constexpr unsigned long long operator()(T value) const
{
if constexpr (is_integer<T>) {
if constexpr (std::numeric_limits<T>::is_signed) {
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"));
}
return static_cast<int>(value);
}
// Parses the range [begin, end) as an unsigned integer. This function assumes
// that the range is non-empty and the first character is a digit.
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');
constexpr auto max_int = static_cast<unsigned>(std::numeric_limits<int>::max());
constexpr auto big_int = max_int / 10u;
value = 0;
do {
if (value > big_int) {
value = max_int + 1;
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>
[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, int& value)
{
size_t val_unsigned = 0;
begin = parse_nonnegative_int(begin, end, val_unsigned);
// Never invalid because parse_nonnegative_integer throws an error for values that don't fit in signed integers
value = static_cast<int>(val_unsigned);
return begin;
}
template<std::input_iterator It, std::sentinel_for<It> S, typename IDHandler>
[[nodiscard]] constexpr It do_parse_arg_id(It begin, S end, IDHandler&& handler)
{
gsl_Expects(begin != end);
auto c = *begin;
if (c >= '0' && c <= '9') {
size_t index = 0;
if (c != '0')
begin = parse_nonnegative_int(begin, end, index);
else
++begin;
if (begin == end || (*begin != '}' && *begin != ':'))
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
else
handler(index);
return begin;
}
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;
}
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
[[nodiscard]] constexpr It parse_sign(It begin, S end, Handler&& handler)
{
gsl_Expects(begin != end);
switch (to_ascii(*begin)) {
case '+':
handler.on_sign(fmt_sign::plus);
++begin;
break;
case '-':
handler.on_sign(fmt_sign::minus);
++begin;
break;
case ' ':
handler.on_sign(fmt_sign::space);
++begin;
break;
default:
break;
}
return begin;
}
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
[[nodiscard]] constexpr It parse_width(It begin, S end, Handler&& handler)
{
struct width_adapter {
Handler& handler;
constexpr void operator()() { handler.on_dynamic_width(auto_id{}); }
constexpr void operator()(size_t id) { handler.on_dynamic_width(id); }
};
gsl_Expects(begin != end);
if ('0' <= *begin && *begin <= '9') {
int width = 0;
begin = parse_nonnegative_int(begin, end, width);
if (width != -1)
handler.on_width(width);
else
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
} else if (*begin == '{') {
++begin;
if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler});
if (begin == end || *begin != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
++begin;
}
return begin;
}
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;
}
template<std::input_iterator It>
constexpr int code_point_length(It begin)
{
if constexpr (sizeof(std::iter_value_t<It>) != 1) return 1;
constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
int len = lengths[static_cast<unsigned char>(*begin) >> 3];
// Compute the pointer to the next character early so that the next
// iteration can start working on the next character. Neither Clang
// nor GCC figure out this reordering on their own.
return len + !len;
}
// Parses fill and alignment.
template<std::input_iterator It, std::sentinel_for<It> S, typename Handler>
[[nodiscard]] constexpr It parse_align(It begin, S end, Handler&& handler)
{
gsl_Expects(begin != end);
auto align = fmt_align::none;
auto p = begin + code_point_length(begin);
if (p >= end) p = begin;
for (;;) {
switch (to_ascii(*p)) {
case '<':
align = fmt_align::left;
break;
case '>':
align = fmt_align::right;
break;
case '^':
align = fmt_align::center;
break;
default:
break;
}
if (align != fmt_align::none) {
if (p != begin) {
auto c = *begin;
if (c == '{') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'"));
handler.on_fill(std::basic_string_view<std::iter_value_t<It>>(&*begin, static_cast<size_t>(p - begin)));
begin = p + 1;
} else
++begin;
handler.on_align(align);
break;
} else if (p == begin) {
break;
}
p = 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
+2
View File
@@ -27,7 +27,9 @@
#include <mp-units/concepts.h>
#include <mp-units/customization_points.h>
#include <mp-units/dimension.h>
#include <mp-units/format.h>
#include <mp-units/math.h>
#include <mp-units/ostream.h>
#include <mp-units/quantity.h>
#include <mp-units/quantity_point.h>
#include <mp-units/quantity_spec.h>
+469
View File
@@ -0,0 +1,469 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <mp-units/bits/external/algorithm.h>
#include <mp-units/bits/fmt.h>
#include <mp-units/customization_points.h>
#include <mp-units/quantity.h>
#include <mp-units/unit.h>
#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 {
// Holds specs about the whole object
template<typename CharT>
struct quantity_global_format_specs {
fill_t<CharT> fill;
fmt_align align = fmt_align::none;
int width = 0;
int dynamic_width_index = -1;
};
// Holds specs about the representation (%[specs]Q)
struct quantity_rep_format_specs {
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());
if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end)
throw MP_UNITS_STD_FMT::format_error("only one of '" + std::string(modifiers) +
"' unit modifiers may be used in the format spec");
return it;
}
} // namespace mp_units::detail
template<auto Reference, typename Rep, typename CharT>
struct MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, CharT> {
private:
using quantity = mp_units::quantity<Reference, Rep>;
using iterator = MP_UNITS_TYPENAME MP_UNITS_STD_FMT::basic_format_parse_context<CharT>::iterator;
bool quantity_value = false;
bool quantity_unit = false;
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; }
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; }
constexpr void on_hash() { f.specs.rep.alt = true; }
constexpr void on_precision(int precision) { f.specs.rep.precision = precision; }
constexpr void on_localized() { f.specs.rep.localized = true; }
constexpr void on_type(char type)
{
constexpr auto valid_rep_types = std::string_view{"aAbBdeEfFgGoxX"};
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");
f.specs.unit.separator = mp_units::unit_symbol_separator::half_high_dot;
}
}
f.quantity_unit = true;
}
};
[[nodiscard]] constexpr std::pair<iterator, iterator> do_parse(
MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx)
{
auto begin = ctx.begin();
auto end = ctx.end();
if (begin == end || *begin == '}') return {begin, begin};
// handler to assign parsed data to formatter data members
spec_handler handler{*this, ctx};
// parse alignment
begin = mp_units::detail::parse_align(begin, end, handler);
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};
}
template<typename OutputIt, typename FormatContext>
OutputIt format_quantity_content(OutputIt out, const quantity& q, FormatContext& ctx)
{
auto begin = format_str.begin();
auto end = format_str.end();
if (begin == end || *begin == '}') {
// default format should print value followed by the unit separated with 1 space
out = mp_units::detail::format_units_quantity_value<CharT>(out, q.numerical_value_ref_in(q.unit), specs.rep,
ctx.locale());
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 {
// user provided format
mp_units::detail::quantity_formatter f(out, q, specs, ctx.locale());
mp_units::detail::parse_units_format(begin, end, f);
}
return out;
}
public:
[[nodiscard]] constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<CharT>& ctx)
{
auto range = do_parse(ctx);
if (range.first != range.second)
format_str = std::basic_string_view<CharT>(&*range.first, static_cast<size_t>(range.second - range.first));
return range.second;
}
template<typename FormatContext>
auto format(const quantity& q, FormatContext& ctx)
{
// process dynamic width and precision
if (specs.global.dynamic_width_index >= 0)
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) {
// Avoid extra copying if width is not specified
return format_quantity_content(ctx.out(), q, ctx);
} else {
// In `quantity_buffer` we will have the representation and the unit formatted according to their
// specification, ignoring global specifiers
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "1.2_m"
std::basic_string<CharT> quantity_buffer;
// deal with quantity content
format_quantity_content(std::back_inserter(quantity_buffer), q, ctx);
// 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")
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
MP_UNITS_STD_FMT::make_format_args(quantity_buffer));
}
}
};
+68
View File
@@ -0,0 +1,68 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <mp-units/quantity.h>
#include <mp-units/unit.h>
#include <sstream>
namespace mp_units {
namespace detail {
template<typename CharT, class Traits, auto R, typename Rep>
void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
{
if constexpr (is_same_v<Rep, std::uint8_t> || is_same_v<Rep, std::int8_t>)
// promote the value to int
os << +q.numerical_value_ref_in(q.unit);
else
os << q.numerical_value_ref_in(q.unit);
if constexpr (has_unit_symbol(get_unit(R))) {
if constexpr (space_before_unit_symbol<get_unit(R)>) os << " ";
unit_symbol_to<CharT>(std::ostream_iterator<CharT>(os), get_unit(R));
}
}
} // namespace detail
template<typename CharT, typename Traits, auto R, typename Rep>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
requires requires { os << q.numerical_value_ref_in(q.unit); }
{
if (os.width()) {
// std::setw() applies to the whole quantity output so it has to be first put into std::string
std::basic_ostringstream<CharT, Traits> oss;
oss.flags(os.flags());
oss.imbue(os.getloc());
oss.precision(os.precision());
detail::to_stream(oss, q);
return os << std::move(oss).str();
}
detail::to_stream(os, q);
return os;
}
} // namespace mp_units