forked from mpusz/mp-units
feat: std::format support for compliant compilers added
This commit is contained in:
@@ -22,7 +22,14 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
option(UNITS_USE_LIBFMT "Enables usage of libfmt instead of the one from 'std'" OFF)
|
||||
message(STATUS "UNITS_USE_LIBFMT: ${UNITS_USE_LIBFMT}")
|
||||
|
||||
# find dependencies
|
||||
find_package(fmt CONFIG REQUIRED)
|
||||
|
||||
add_units_module(core-fmt mp-units::core fmt::fmt)
|
||||
add_units_module(core-fmt mp-units::core)
|
||||
if(UNITS_USE_LIBFMT)
|
||||
target_link_libraries(mp-units-core-fmt INTERFACE fmt::fmt)
|
||||
target_compile_definitions(mp-units-core-fmt INTERFACE UNITS_USE_LIBFMT)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
// 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.
|
||||
|
||||
#include <gsl/gsl-lite.hpp>
|
||||
#include <units/bits/fmt_hacks.h>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
|
||||
// most of the below code is based on/copied from libfmt
|
||||
|
||||
namespace 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 void operator=(std::basic_string_view<Char> s)
|
||||
{
|
||||
auto size = s.size();
|
||||
if (size > max_size) return throw STD_FMT::format_error("invalid fill");
|
||||
for (size_t i = 0; i < size; ++i) data_[i] = s[i];
|
||||
size_ = static_cast<unsigned char>(size);
|
||||
}
|
||||
|
||||
[[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) throw STD_FMT::format_error("negative width");
|
||||
}
|
||||
return static_cast<unsigned long long>(value);
|
||||
} else {
|
||||
throw STD_FMT::format_error("width is not integer");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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) throw STD_FMT::format_error("negative precision");
|
||||
}
|
||||
return static_cast<unsigned long long>(value);
|
||||
} else {
|
||||
throw STD_FMT::format_error("precision is not integer");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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())) {
|
||||
throw 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, STD_FMT::basic_format_parse_context<CharT>& context)
|
||||
{
|
||||
context.check_arg_id(FMT_ARG_ID(arg_id));
|
||||
return verify_dynamic_arg_index_in_range(arg_id);
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
[[nodiscard]] constexpr int on_dynamic_arg(auto_id, STD_FMT::basic_format_parse_context<CharT>& context)
|
||||
{
|
||||
return verify_dynamic_arg_index_in_range(context.next_arg_id());
|
||||
}
|
||||
|
||||
template<class Handler, typename FormatContext>
|
||||
[[nodiscard]] constexpr int get_dynamic_spec(int index, FormatContext& ctx)
|
||||
{
|
||||
const unsigned long long value = STD_FMT::visit_format_arg(Handler{}, ctx.arg(static_cast<size_t>(index)));
|
||||
if (value > static_cast<unsigned long long>(std::numeric_limits<int>::max())) {
|
||||
throw 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::forward_iterator It, std::sentinel_for<It> S>
|
||||
[[nodiscard]] constexpr int parse_nonnegative_int(It& begin, S end, int error_value) noexcept
|
||||
{
|
||||
gsl_Expects(begin != end && '0' <= *begin && *begin <= '9');
|
||||
unsigned value = 0, prev = 0;
|
||||
auto p = begin;
|
||||
do {
|
||||
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 = static_cast<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::forward_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') {
|
||||
int index = 0;
|
||||
if (c != '0')
|
||||
index = parse_nonnegative_int(begin, end, std::numeric_limits<int>::max());
|
||||
else
|
||||
++begin;
|
||||
if (begin == end || (*begin != '}' && *begin != ':'))
|
||||
handler.on_error("invalid format string");
|
||||
else
|
||||
handler(index);
|
||||
return begin;
|
||||
}
|
||||
handler.on_error("invalid format string");
|
||||
return begin;
|
||||
}
|
||||
|
||||
template<std::forward_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::forward_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::forward_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()(int id) { handler.on_dynamic_width(id); }
|
||||
constexpr void on_error(const char* message)
|
||||
{
|
||||
if (message) handler.on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
gsl_Expects(begin != end);
|
||||
if ('0' <= *begin && *begin <= '9') {
|
||||
int width = parse_nonnegative_int(begin, end, -1);
|
||||
if (width != -1)
|
||||
handler.on_width(width);
|
||||
else
|
||||
handler.on_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 != '}') return handler.on_error("invalid format string"), begin;
|
||||
++begin;
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
template<std::forward_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()(int id) { handler.on_dynamic_precision(id); }
|
||||
constexpr void on_error(const char* message)
|
||||
{
|
||||
if (message) handler.on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
++begin;
|
||||
auto c = begin != end ? *begin : std::iter_value_t<It>();
|
||||
if ('0' <= c && c <= '9') {
|
||||
auto precision = parse_nonnegative_int(begin, end, -1);
|
||||
if (precision != -1)
|
||||
handler.on_precision(precision);
|
||||
else
|
||||
handler.on_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++ != '}') return handler.on_error("invalid format string"), begin;
|
||||
} else {
|
||||
return handler.on_error("missing precision specifier"), begin;
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
template<std::forward_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::forward_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 == '{') return handler.on_error("invalid fill character '{'"), begin;
|
||||
handler.on_fill(std::basic_string_view<std::iter_value_t<It>>(begin, p));
|
||||
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::forward_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 = ::units::detail::parse_align(begin, end, handler);
|
||||
if (begin == end) return begin;
|
||||
|
||||
// Parse sign.
|
||||
begin = ::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 = ::units::detail::parse_width(begin, end, handler);
|
||||
if (begin == end) return begin;
|
||||
|
||||
// Parse precision.
|
||||
if (*begin == '.') {
|
||||
begin = ::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 = 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_);
|
||||
}
|
||||
|
||||
constexpr void on_error(const char* message) { context_.on_error(message); }
|
||||
|
||||
private:
|
||||
dynamic_format_specs<char_type>& specs_;
|
||||
ParseContext& context_;
|
||||
};
|
||||
|
||||
} // namespace units::detail
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
#include <units/bits/external/hacks.h>
|
||||
|
||||
#if UNITS_USE_LIBFMT
|
||||
|
||||
UNITS_DIAGNOSTIC_PUSH
|
||||
UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE
|
||||
UNITS_DIAGNOSTIC_IGNORE_SHADOW
|
||||
#include <fmt/format.h>
|
||||
UNITS_DIAGNOSTIC_POP
|
||||
|
||||
#define STD_FMT fmt
|
||||
#define FMT_RUNTIME(arg) fmt::runtime(arg)
|
||||
#define FMT_LOCALE(loc) (loc).template get<std::locale>()
|
||||
#define FMT_ARG_ID(arg) static_cast<int>(arg)
|
||||
|
||||
#else
|
||||
|
||||
#ifndef __cpp_lib_format
|
||||
#error "std::formatting facility not supported"
|
||||
#endif
|
||||
|
||||
#include <format>
|
||||
|
||||
#define STD_FMT std
|
||||
#define FMT_RUNTIME(arg) arg
|
||||
#define FMT_LOCALE(loc) loc
|
||||
#define FMT_ARG_ID(arg) arg
|
||||
|
||||
#endif
|
||||
+309
-360
@@ -22,24 +22,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <units/bits/fmt.h>
|
||||
#include <units/customization_points.h>
|
||||
#include <units/quantity.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include <units/bits/unit_text.h>
|
||||
|
||||
UNITS_DIAGNOSTIC_PUSH
|
||||
UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE
|
||||
UNITS_DIAGNOSTIC_IGNORE_SHADOW
|
||||
#include <fmt/format.h>
|
||||
UNITS_DIAGNOSTIC_POP
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
// Grammar
|
||||
//
|
||||
//
|
||||
// units-format-spec ::= [fill-and-align] [width] [units-specs]
|
||||
// units-specs ::= conversion-spec
|
||||
// units-specs conversion-spec
|
||||
@@ -60,457 +54,412 @@ UNITS_DIAGNOSTIC_POP
|
||||
// - Add the new symbol in the `units_types` variable in the `parse_units_format` function
|
||||
// - Add a new case in the `if` following the format_error in `parse_units_format` function;
|
||||
// this should invoke `handler.on_[...]`
|
||||
// - Edit `fmt::formatter`:
|
||||
// - Edit `STD_FMT::formatter`:
|
||||
// - Add a new field for the flag/specs
|
||||
// - Add to the `fmt::formatter::spec_handler` a `on_[...]` function that set the flag/specs if needed
|
||||
// - Edit `units_formatter`:
|
||||
// - Add to the `STD_FMT::formatter::spec_handler` a `on_[...]` function that set the flag/specs if needed
|
||||
// - Edit `quantity_formatter`:
|
||||
// - Add a new field for the flag/specs
|
||||
// - write a `on_[...]` function that writes to the `out` iterator the correct output
|
||||
//
|
||||
//
|
||||
// If you want to add a new `units-rep-type`:
|
||||
// - Add the new symbol in the `valid_rep_types` variable (which is in the
|
||||
// fmt::formatter::spec_handler::on_type member function)
|
||||
// STD_FMT::formatter::spec_handler::on_type member function)
|
||||
// NB: currently this function forward the modifier to the value that must be formatted;
|
||||
// if the symbol has no meaning for fmt::formatter<Rep>, this behavior should be disabled manually
|
||||
// if the symbol has no meaning for STD_FMT::formatter<Rep>, this behavior should be disabled manually
|
||||
// (as is done for '\0')
|
||||
// - Implement the effect of the new flag in `format_units_quantity_value`
|
||||
//
|
||||
//
|
||||
// If you want to add a new `units-unit-modifier`:
|
||||
// - Add the new symbol in the `valid_modifiers` variable (which is in the
|
||||
// fmt::formatter::spec_handler::on_modifier member function)
|
||||
// - Implement the effect of the new flag in the `units_formatter::on_quantity_unit` member function
|
||||
// STD_FMT::formatter::spec_handler::on_unit_modifier member function)
|
||||
// - Implement the effect of the new flag in the `quantity_formatter::on_quantity_unit` member function
|
||||
|
||||
namespace units {
|
||||
namespace units::detail {
|
||||
|
||||
namespace 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 whole object
|
||||
template<typename CharT>
|
||||
struct global_format_specs
|
||||
{
|
||||
fmt::detail::fill_t<CharT> fill;
|
||||
fmt::align_t align = fmt::align_t::none;
|
||||
int width = 0;
|
||||
};
|
||||
// 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 representation (%[specs]Q)
|
||||
struct rep_format_specs
|
||||
{
|
||||
fmt::sign_t sign = fmt::sign_t::none;
|
||||
int precision = -1;
|
||||
char type = '\0';
|
||||
bool alt = false;
|
||||
bool use_locale = false;
|
||||
};
|
||||
// Holds specs about the unit (%[specs]q)
|
||||
struct quantity_unit_format_specs {
|
||||
bool ascii_only = false;
|
||||
};
|
||||
|
||||
// Holds specs about the unit (%[specs]q)
|
||||
struct unit_format_specs
|
||||
{
|
||||
char modifier = '\0';
|
||||
};
|
||||
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 <typename CharT, typename Handler>
|
||||
constexpr const CharT* parse_units_rep(const CharT* begin, const CharT* end, Handler&& handler, bool treat_as_floating_point)
|
||||
{
|
||||
// parse sign
|
||||
switch(static_cast<char>(*begin)) {
|
||||
case '+':
|
||||
handler.on_plus();
|
||||
++begin;
|
||||
break;
|
||||
case '-':
|
||||
handler.on_minus();
|
||||
++begin;
|
||||
break;
|
||||
case ' ':
|
||||
handler.on_space();
|
||||
++begin;
|
||||
break;
|
||||
}
|
||||
if(begin == end)
|
||||
return begin;
|
||||
// Parse a `units-rep-modifier`
|
||||
template<std::forward_iterator It, std::sentinel_for<It> S, typename Handler>
|
||||
constexpr const 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_alt();
|
||||
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 = fmt::detail::parse_precision(begin, end, handler);
|
||||
else
|
||||
handler.on_error("precision not allowed for integral quantity representation");
|
||||
}
|
||||
// parse precision if a floating point
|
||||
if (*begin == '.') {
|
||||
if (treat_as_floating_point) {
|
||||
begin = parse_precision(begin, end, handler);
|
||||
} else
|
||||
handler.on_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_locale();
|
||||
++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;
|
||||
if (begin != end && *begin != '}' && *begin != '%') {
|
||||
handler.on_type(*begin++);
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
// parse units-specs
|
||||
template<std::forward_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 STD_FMT::format_error("invalid format");
|
||||
c = *ptr++;
|
||||
|
||||
// parse units-specs
|
||||
template<typename CharT, typename Handler>
|
||||
constexpr const CharT* parse_units_format(const CharT* begin, const CharT* 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 fmt::format_error("invalid format");
|
||||
c = *ptr++;
|
||||
|
||||
switch(c) {
|
||||
// units-type
|
||||
case '%':
|
||||
handler.on_text(ptr - 1, ptr);
|
||||
break;
|
||||
case 'n': {
|
||||
const char newline[] = "\n";
|
||||
handler.on_text(newline, newline + 1);
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
const char tab[] = "\t";
|
||||
handler.on_text(tab, tab + 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
constexpr auto units_types = std::string_view{"Qq"};
|
||||
auto const new_end = std::find_first_of(begin, end, units_types.begin(), units_types.end());
|
||||
if (new_end == end) {
|
||||
throw 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); // 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 LocaleRef>
|
||||
inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, const rep_format_specs& rep_specs, LocaleRef loc)
|
||||
{
|
||||
fmt::basic_memory_buffer<CharT> buffer;
|
||||
auto to_buffer = std::back_inserter(buffer);
|
||||
|
||||
fmt::format_to(to_buffer, "{{:");
|
||||
switch(rep_specs.sign) {
|
||||
case fmt::sign::none:
|
||||
switch (c) {
|
||||
// units-type
|
||||
case '%':
|
||||
handler.on_text(ptr - 1, ptr);
|
||||
break;
|
||||
case fmt::sign::plus:
|
||||
fmt::format_to(to_buffer, "+");
|
||||
break;
|
||||
case fmt::sign::minus:
|
||||
fmt::format_to(to_buffer, "-");
|
||||
break;
|
||||
case fmt::sign::space:
|
||||
fmt::format_to(to_buffer, " ");
|
||||
case 'n': {
|
||||
const char newline[] = "\n";
|
||||
handler.on_text(newline, newline + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (rep_specs.alt) {
|
||||
fmt::format_to(to_buffer, "#");
|
||||
}
|
||||
auto type = rep_specs.type;
|
||||
if (auto precision = rep_specs.precision; precision >= 0) {
|
||||
fmt::format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type);
|
||||
} else if constexpr (treat_as_floating_point<Rep>) {
|
||||
fmt::format_to(to_buffer, "{}", type == '\0' ? 'g' : type);
|
||||
} else {
|
||||
if (type != '\0') {
|
||||
fmt::format_to(to_buffer, "{}", type);
|
||||
}
|
||||
}
|
||||
if (rep_specs.use_locale) {
|
||||
fmt::format_to(to_buffer, "L");
|
||||
}
|
||||
fmt::format_to(to_buffer, "}}");
|
||||
if (rep_specs.use_locale and static_cast<bool>(loc)) {
|
||||
return fmt::format_to(out, loc.template get<std::locale>(), fmt::runtime(std::string_view(buffer.data(), buffer.size())), val);
|
||||
}
|
||||
return fmt::format_to(out, fmt::runtime(std::string_view(buffer.data(), buffer.size())), val);
|
||||
}
|
||||
|
||||
// Creates a global format string
|
||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
|
||||
template<typename CharT, typename OutputIt>
|
||||
inline OutputIt format_global_buffer(OutputIt out, const global_format_specs<CharT>& specs)
|
||||
{
|
||||
fmt::format_to(out, "{{:");
|
||||
if (specs.fill.size() != 1 || specs.fill[0] != ' ') {
|
||||
fmt::format_to(out, "{}", specs.fill.data());
|
||||
}
|
||||
switch (specs.align) {
|
||||
case fmt::align_t::left:
|
||||
fmt::format_to(out, "<");
|
||||
break;
|
||||
case fmt::align_t::right:
|
||||
fmt::format_to(out, ">");
|
||||
break;
|
||||
case fmt::align_t::center:
|
||||
fmt::format_to(out, "^");
|
||||
case 't': {
|
||||
const char tab[] = "\t";
|
||||
handler.on_text(tab, tab + 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (specs.width >= 1) {
|
||||
fmt::format_to(out, "{}", specs.width);
|
||||
}
|
||||
return fmt::format_to(out, "}}");
|
||||
constexpr auto units_types = std::string_view{"Qq"};
|
||||
auto const new_end = std::find_first_of(begin, end, units_types.begin(), units_types.end());
|
||||
if (new_end == end) throw 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); // 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;
|
||||
}
|
||||
|
||||
template<typename OutputIt, typename Dimension, typename Unit, typename Rep, typename LocaleRef, typename CharT>
|
||||
struct units_formatter {
|
||||
OutputIt out;
|
||||
Rep val;
|
||||
global_format_specs<CharT> const & global_specs;
|
||||
rep_format_specs const & rep_specs;
|
||||
unit_format_specs const & unit_specs;
|
||||
LocaleRef loc;
|
||||
// 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);
|
||||
|
||||
explicit units_formatter(
|
||||
OutputIt o, quantity<Dimension, Unit, Rep> q,
|
||||
global_format_specs<CharT> const & gspecs,
|
||||
rep_format_specs const & rspecs, unit_format_specs const & uspecs,
|
||||
LocaleRef lc
|
||||
):
|
||||
out(o), val(q.number()), global_specs(gspecs), rep_specs(rspecs), unit_specs(uspecs), loc(lc)
|
||||
{
|
||||
}
|
||||
STD_FMT::format_to(to_buffer, "{{:");
|
||||
switch (rep_specs.sign) {
|
||||
case fmt_sign::none:
|
||||
break;
|
||||
case fmt_sign::plus:
|
||||
STD_FMT::format_to(to_buffer, "+");
|
||||
break;
|
||||
case fmt_sign::minus:
|
||||
STD_FMT::format_to(to_buffer, "-");
|
||||
break;
|
||||
case fmt_sign::space:
|
||||
STD_FMT::format_to(to_buffer, " ");
|
||||
break;
|
||||
}
|
||||
|
||||
template<typename CharT2>
|
||||
void on_text(const CharT2* begin, const CharT2* end)
|
||||
{
|
||||
std::copy(begin, end, out);
|
||||
}
|
||||
if (rep_specs.alt) {
|
||||
STD_FMT::format_to(to_buffer, "#");
|
||||
}
|
||||
auto type = rep_specs.type;
|
||||
if (auto precision = rep_specs.precision; precision >= 0) {
|
||||
STD_FMT::format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type);
|
||||
} else if constexpr (treat_as_floating_point<Rep>) {
|
||||
STD_FMT::format_to(to_buffer, "{}", type == '\0' ? 'g' : type);
|
||||
} else {
|
||||
if (type != '\0') {
|
||||
STD_FMT::format_to(to_buffer, "{}", type);
|
||||
}
|
||||
}
|
||||
if (rep_specs.localized) {
|
||||
STD_FMT::format_to(to_buffer, "L");
|
||||
}
|
||||
|
||||
void on_quantity_value([[maybe_unused]] const CharT*, [[maybe_unused]] const CharT*)
|
||||
{
|
||||
out = format_units_quantity_value<CharT>(out, val, rep_specs, loc);
|
||||
}
|
||||
STD_FMT::format_to(to_buffer, "}}");
|
||||
if (rep_specs.localized) {
|
||||
return STD_FMT::format_to(out, FMT_LOCALE(loc), FMT_RUNTIME(buffer), val);
|
||||
}
|
||||
return STD_FMT::format_to(out, FMT_RUNTIME(buffer), val);
|
||||
}
|
||||
|
||||
void on_quantity_unit([[maybe_unused]] const CharT)
|
||||
{
|
||||
auto txt = unit_text<Dimension, Unit>();
|
||||
if(unit_specs.modifier == 'A') {
|
||||
fmt::format_to(out, "{}", txt.ascii().c_str());
|
||||
}
|
||||
else {
|
||||
fmt::format_to(out, "{}", txt.standard().c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
// Creates a global format string
|
||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
|
||||
template<typename CharT, std::output_iterator<CharT> OutputIt>
|
||||
OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs<CharT>& specs)
|
||||
{
|
||||
STD_FMT::format_to(out, "{{:");
|
||||
if (specs.fill.size() != 1 || specs.fill[0] != ' ') {
|
||||
STD_FMT::format_to(out, "{}", specs.fill.data());
|
||||
}
|
||||
switch (specs.align) {
|
||||
case fmt_align::left:
|
||||
STD_FMT::format_to(out, "<");
|
||||
break;
|
||||
case fmt_align::right:
|
||||
STD_FMT::format_to(out, ">");
|
||||
break;
|
||||
case fmt_align::center:
|
||||
STD_FMT::format_to(out, "^");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (specs.width >= 1) STD_FMT::format_to(out, "{}", specs.width);
|
||||
return STD_FMT::format_to(out, "}}");
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
template<typename Dimension, typename Unit, typename Rep, typename Locale, typename CharT,
|
||||
std::output_iterator<CharT> OutputIt>
|
||||
struct quantity_formatter {
|
||||
OutputIt out;
|
||||
Rep val;
|
||||
const quantity_format_specs<CharT>& specs;
|
||||
Locale loc;
|
||||
|
||||
} // namespace units
|
||||
explicit quantity_formatter(OutputIt o, quantity<Dimension, Unit, Rep> q, const quantity_format_specs<CharT>& fspecs,
|
||||
Locale lc) :
|
||||
out(o), val(std::move(q).number()), specs(fspecs), loc(std::move(lc))
|
||||
{}
|
||||
|
||||
template<std::forward_iterator It, std::sentinel_for<It> S>
|
||||
void on_text(It begin, S end)
|
||||
{
|
||||
std::copy(begin, end, out);
|
||||
}
|
||||
|
||||
template<std::forward_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);
|
||||
}
|
||||
|
||||
void on_quantity_unit([[maybe_unused]] CharT)
|
||||
{
|
||||
auto txt = unit_text<Dimension, Unit>();
|
||||
if (specs.unit.ascii_only) {
|
||||
STD_FMT::format_to(out, "{}", txt.ascii().c_str());
|
||||
} else {
|
||||
STD_FMT::format_to(out, "{}", txt.standard().c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace units::detail
|
||||
|
||||
template<typename Dimension, typename Unit, typename Rep, typename CharT>
|
||||
struct fmt::formatter<units::quantity<Dimension, Unit, Rep>, CharT> {
|
||||
struct STD_FMT::formatter<units::quantity<Dimension, Unit, Rep>, CharT> {
|
||||
private:
|
||||
using quantity = units::quantity<Dimension, Unit, Rep>;
|
||||
using iterator = TYPENAME fmt::basic_format_parse_context<CharT>::iterator;
|
||||
using arg_ref_type = fmt::detail::arg_ref<CharT>;
|
||||
using iterator = TYPENAME STD_FMT::basic_format_parse_context<CharT>::iterator;
|
||||
|
||||
units::detail::global_format_specs<CharT> global_specs;
|
||||
units::detail::rep_format_specs rep_specs;
|
||||
units::detail::unit_format_specs unit_specs;
|
||||
bool quantity_value = false;
|
||||
bool quantity_unit = false;
|
||||
bool quantity_unit_ascii_only = false;
|
||||
arg_ref_type width_ref;
|
||||
arg_ref_type precision_ref;
|
||||
fmt::basic_string_view<CharT> format_str;
|
||||
units::detail::quantity_format_specs<CharT> specs;
|
||||
std::basic_string_view<CharT> format_str;
|
||||
|
||||
struct spec_handler {
|
||||
formatter& f;
|
||||
fmt::basic_format_parse_context<CharT>& context;
|
||||
fmt::basic_string_view<CharT> format_str;
|
||||
STD_FMT::basic_format_parse_context<CharT>& context;
|
||||
|
||||
template<typename Id>
|
||||
constexpr arg_ref_type make_arg_ref(Id arg_id)
|
||||
{
|
||||
context.check_arg_id(arg_id);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
void on_error(const char* msg) { throw STD_FMT::format_error(msg); }
|
||||
constexpr void on_fill(std::basic_string_view<CharT> fill) { f.specs.global.fill = fill; }
|
||||
constexpr void on_align(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(units::detail::fmt_sign s) { f.specs.rep.sign = s; }
|
||||
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 arg_ref_type make_arg_ref(fmt::basic_string_view<CharT> arg_id)
|
||||
{
|
||||
context.check_arg_id(arg_id);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
constexpr arg_ref_type make_arg_ref(fmt::detail::auto_id)
|
||||
{
|
||||
return arg_ref_type(context.next_arg_id());
|
||||
}
|
||||
|
||||
void on_error(const char* msg) { throw fmt::format_error(msg); }
|
||||
constexpr void on_fill(basic_string_view<CharT> fill) { f.global_specs.fill = fill; } // global
|
||||
constexpr void on_align(align_t align) { f.global_specs.align = align; } // global
|
||||
constexpr void on_width(int width) { f.global_specs.width = width; } // global
|
||||
constexpr void on_plus() { f.rep_specs.sign = fmt::sign::plus; } // rep
|
||||
constexpr void on_minus() { f.rep_specs.sign = fmt::sign::minus; } // rep
|
||||
constexpr void on_space() { f.rep_specs.sign = fmt::sign::space; } // rep
|
||||
constexpr void on_alt() { f.rep_specs.alt = true; } // rep
|
||||
constexpr void on_precision(int precision) { f.rep_specs.precision = precision; } // rep
|
||||
constexpr void on_locale() { f.rep_specs.use_locale = true; } // rep
|
||||
constexpr void on_type(char type) // rep
|
||||
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.rep_specs.type = type;
|
||||
f.specs.rep.type = type;
|
||||
} else {
|
||||
on_error("invalid quantity type specifier");
|
||||
}
|
||||
}
|
||||
constexpr void on_modifier(char mod) {
|
||||
|
||||
constexpr void on_unit_modifier(char mod)
|
||||
{
|
||||
constexpr auto valid_modifiers = std::string_view{"A"};
|
||||
if (valid_modifiers.find(mod) != std::string_view::npos) {
|
||||
f.unit_specs.modifier = mod;
|
||||
f.specs.unit.ascii_only = true;
|
||||
} else {
|
||||
on_error("invalid unit modifier specified");
|
||||
}
|
||||
} // unit
|
||||
constexpr void end_precision() {}
|
||||
|
||||
template<typename Id>
|
||||
constexpr void on_dynamic_width(Id arg_id)
|
||||
{
|
||||
f.width_ref = make_arg_ref(arg_id);
|
||||
}
|
||||
|
||||
template<typename Id>
|
||||
constexpr void on_dynamic_precision(Id arg_id)
|
||||
template<typename T>
|
||||
constexpr void on_dynamic_width(T t)
|
||||
{
|
||||
f.precision_ref = make_arg_ref(arg_id);
|
||||
f.specs.global.dynamic_width_index = units::detail::on_dynamic_arg(t, context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr void on_dynamic_precision(T t)
|
||||
{
|
||||
f.specs.rep.dynamic_precision_index = units::detail::on_dynamic_arg(t, context);
|
||||
}
|
||||
|
||||
constexpr void on_text(const CharT*, const CharT*) {}
|
||||
constexpr void on_quantity_value(const CharT* begin, const CharT* end)
|
||||
{
|
||||
if (begin != end) {
|
||||
units::detail::parse_units_rep(begin, end, *this, units::treat_as_floating_point<Rep>);
|
||||
}
|
||||
if (begin != end) units::detail::parse_units_rep(begin, end, *this, units::treat_as_floating_point<Rep>);
|
||||
f.quantity_value = true;
|
||||
}
|
||||
constexpr void on_quantity_unit(const CharT mod)
|
||||
constexpr void on_quantity_unit(CharT mod)
|
||||
{
|
||||
if (mod != 'q') {
|
||||
f.unit_specs.modifier = mod;
|
||||
}
|
||||
if (mod != 'q') on_unit_modifier(mod);
|
||||
f.quantity_unit = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct parse_range {
|
||||
iterator begin;
|
||||
iterator end;
|
||||
};
|
||||
|
||||
constexpr parse_range do_parse(fmt::basic_format_parse_context<CharT>& ctx)
|
||||
[[nodiscard]] constexpr std::ranges::subrange<iterator> do_parse(STD_FMT::basic_format_parse_context<CharT>& ctx)
|
||||
{
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if(begin == end || *begin == '}')
|
||||
return {begin, begin};
|
||||
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, format_str};
|
||||
spec_handler handler{*this, ctx};
|
||||
|
||||
// parse alignment
|
||||
begin = fmt::detail::parse_align(begin, end, handler);
|
||||
if(begin == end)
|
||||
return {begin, begin};
|
||||
begin = units::detail::parse_align(begin, end, handler);
|
||||
if (begin == end) return {begin, begin};
|
||||
|
||||
// parse width
|
||||
begin = fmt::detail::parse_width(begin, end, handler);
|
||||
if(begin == end)
|
||||
return {begin, begin};
|
||||
begin = units::detail::parse_width(begin, end, handler);
|
||||
if (begin == end) return {begin, begin};
|
||||
|
||||
// parse units-specific specification
|
||||
end = units::detail::parse_units_format(begin, end, handler);
|
||||
|
||||
if(global_specs.align == fmt::align_t::none && (!quantity_unit || quantity_value))
|
||||
if (specs.global.align == units::detail::fmt_align::none && (!quantity_unit || quantity_value))
|
||||
// quantity values should behave like numbers (by default aligned to right)
|
||||
global_specs.align = fmt::align_t::right;
|
||||
specs.global.align = units::detail::fmt_align::right;
|
||||
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template<std::output_iterator<CharT> 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 = units::detail::format_units_quantity_value<CharT>(out, q.number(), specs.rep, ctx.locale());
|
||||
constexpr auto symbol = units::detail::unit_text<Dimension, Unit>();
|
||||
if constexpr (symbol.standard().size()) {
|
||||
*out++ = CharT(' ');
|
||||
STD_FMT::format_to(out, "{}", symbol.standard().c_str());
|
||||
}
|
||||
} else {
|
||||
// user provided format
|
||||
units::detail::quantity_formatter f(out, q, specs, ctx.locale());
|
||||
units::detail::parse_units_format(begin, end, f);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr auto parse(fmt::basic_format_parse_context<CharT>& ctx)
|
||||
[[nodiscard]] constexpr auto parse(STD_FMT::basic_format_parse_context<CharT>& ctx)
|
||||
{
|
||||
auto range = do_parse(ctx);
|
||||
format_str = fmt::basic_string_view<CharT>(&*range.begin, fmt::detail::to_unsigned(range.end - range.begin));
|
||||
return range.end;
|
||||
format_str = std::basic_string_view<CharT>(range.begin(), range.end());
|
||||
return range.end();
|
||||
}
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(const units::quantity<Dimension, Unit, Rep>& q, FormatContext& ctx)
|
||||
auto format(const quantity& q, FormatContext& ctx)
|
||||
{
|
||||
auto begin = format_str.begin(), end = format_str.end();
|
||||
|
||||
// process dynamic width and precision
|
||||
fmt::detail::handle_dynamic_spec<fmt::detail::width_checker>(global_specs.width, width_ref, ctx);
|
||||
fmt::detail::handle_dynamic_spec<fmt::detail::precision_checker>(rep_specs.precision, precision_ref, ctx);
|
||||
if (specs.global.dynamic_width_index >= 0)
|
||||
specs.global.width =
|
||||
units::detail::get_dynamic_spec<units::detail::width_checker>(specs.global.dynamic_width_index, ctx);
|
||||
if (specs.rep.dynamic_precision_index >= 0)
|
||||
specs.rep.precision =
|
||||
units::detail::get_dynamic_spec<units::detail::precision_checker>(specs.rep.dynamic_precision_index, ctx);
|
||||
|
||||
// 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"
|
||||
// TODO Avoid extra copying if width is not specified
|
||||
fmt::basic_memory_buffer<CharT> quantity_buffer;
|
||||
auto to_quantity_buffer = std::back_inserter(quantity_buffer);
|
||||
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
|
||||
if(begin == end || *begin == '}') {
|
||||
// default format should print value followed by the unit separated with 1 space
|
||||
to_quantity_buffer = units::detail::format_units_quantity_value<CharT>(to_quantity_buffer, q.number(), rep_specs, ctx.locale());
|
||||
constexpr auto symbol = units::detail::unit_text<Dimension, Unit>();
|
||||
if(symbol.standard().size()) {
|
||||
*to_quantity_buffer++ = CharT(' ');
|
||||
fmt::format_to(to_quantity_buffer, "{}", symbol.standard().c_str());
|
||||
}
|
||||
// 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;
|
||||
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 STD_FMT::format("{:*^10}", "1.2_m")
|
||||
return STD_FMT::format_to(ctx.out(), FMT_RUNTIME(global_format_buffer), quantity_buffer);
|
||||
}
|
||||
else {
|
||||
// user provided format
|
||||
units::detail::units_formatter f(to_quantity_buffer, q, global_specs, rep_specs, unit_specs, ctx.locale());
|
||||
parse_units_format(begin, end, f);
|
||||
}
|
||||
|
||||
// In `global_format_buffer` we will create a global format string
|
||||
// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}"
|
||||
fmt::basic_memory_buffer<CharT> global_format_buffer;
|
||||
units::detail::format_global_buffer<CharT>(std::back_inserter(global_format_buffer), global_specs);
|
||||
|
||||
// Format the `quantity buffer` using specs from `global_format_buffer`
|
||||
// In the example, equivalent to fmt::format("{:*^10}", "1.2_m")
|
||||
return fmt::format_to(ctx.out(), fmt::runtime(std::basic_string_view<CharT>(global_format_buffer.data(), global_format_buffer.size())), std::string_view(quantity_buffer.data(), quantity_buffer.size()));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user