forked from mpusz/mp-units
385 lines
12 KiB
C++
385 lines
12 KiB
C++
// 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.
|
|
|
|
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-union-access)
|
|
#pragma once
|
|
|
|
#include <mp-units/bits/module_macros.h>
|
|
#include <mp-units/compat_macros.h>
|
|
#include <mp-units/ext/algorithm.h>
|
|
|
|
#ifndef MP_UNITS_IN_MODULE_INTERFACE
|
|
#include <array>
|
|
#include <concepts>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string_view>
|
|
#include <mp-units/ext/contracts.h>
|
|
#endif
|
|
|
|
// most of the below code is based on/copied from fmtlib
|
|
|
|
namespace mp_units::detail {
|
|
|
|
// TODO the below should be exposed by the C++ Standard Library (used in our examples)
|
|
MP_UNITS_EXPORT_BEGIN
|
|
|
|
enum class fmt_align : std::int8_t { none, left, right, center, numeric };
|
|
enum class fmt_arg_id_kind : std::int8_t {
|
|
none,
|
|
#if MP_UNITS_USE_FMTLIB
|
|
name,
|
|
#endif
|
|
index
|
|
};
|
|
|
|
template<typename Char>
|
|
struct fmt_arg_ref {
|
|
fmt_arg_id_kind kind = fmt_arg_id_kind::none;
|
|
union value {
|
|
int index = 0;
|
|
#if MP_UNITS_USE_FMTLIB
|
|
std::basic_string_view<Char> name;
|
|
#endif
|
|
|
|
constexpr value() {}
|
|
constexpr explicit value(int idx) : index(idx) {}
|
|
#if MP_UNITS_USE_FMTLIB
|
|
constexpr value(std::basic_string_view<Char> n) : name(n) {}
|
|
#endif
|
|
} val{};
|
|
|
|
fmt_arg_ref() = default;
|
|
constexpr explicit fmt_arg_ref(int index) : kind(fmt_arg_id_kind::index), val(index) {}
|
|
#if MP_UNITS_USE_FMTLIB
|
|
constexpr explicit fmt_arg_ref(std::basic_string_view<Char> name) : kind(fmt_arg_id_kind::name), val(name) {}
|
|
#endif
|
|
|
|
[[nodiscard]] constexpr fmt_arg_ref& operator=(int idx)
|
|
{
|
|
kind = fmt_arg_id_kind::index;
|
|
val.index = idx;
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
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)
|
|
std::array<Char, max_size> data_ = {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 < max_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_.data(); }
|
|
|
|
[[nodiscard]] constexpr Char& operator[](size_t index) { return data_[index]; }
|
|
[[nodiscard]] constexpr const Char& operator[](size_t index) const { return data_[index]; }
|
|
};
|
|
|
|
MP_UNITS_EXPORT_END
|
|
|
|
template<typename T>
|
|
inline constexpr bool is_integer =
|
|
std::is_integral_v<T> && !std::is_same_v<T, bool> && !std::is_same_v<T, char> && !std::is_same_v<T, wchar_t>;
|
|
|
|
// 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 std::underlying_type_t<Char> to_ascii(Char value)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
// Casts a nonnegative integer to unsigned.
|
|
template<typename Int>
|
|
[[nodiscard]] constexpr std::make_unsigned_t<Int> to_unsigned(Int value)
|
|
{
|
|
MP_UNITS_EXPECTS(std::is_unsigned_v<Int> || value >= 0);
|
|
return static_cast<std::make_unsigned_t<Int>>(value);
|
|
}
|
|
|
|
template<class Handler, typename FormatArg>
|
|
[[nodiscard]] constexpr int get_dynamic_spec(FormatArg arg)
|
|
{
|
|
const unsigned long long value = MP_UNITS_STD_FMT::visit_format_arg(Handler{}, arg);
|
|
if (value > ::mp_units::detail::to_unsigned(std::numeric_limits<int>::max())) {
|
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
|
}
|
|
return static_cast<int>(value);
|
|
}
|
|
|
|
template<typename Context, typename ID>
|
|
[[nodiscard]] constexpr auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id))
|
|
{
|
|
auto arg = ctx.arg(MP_UNITS_FMT_TO_ARG_ID(id));
|
|
if (!arg) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("argument not found"));
|
|
return arg;
|
|
}
|
|
|
|
// TODO the below should be exposed by the C++ Standard Library (used in our examples)
|
|
MP_UNITS_EXPORT_BEGIN
|
|
|
|
template<class Handler, typename Context>
|
|
constexpr void handle_dynamic_spec(int& value, fmt_arg_ref<typename Context::char_type> ref, Context& ctx)
|
|
{
|
|
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;
|
|
#if MP_UNITS_USE_FMTLIB
|
|
case fmt_arg_id_kind::name:
|
|
value = ::mp_units::detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name));
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
MP_UNITS_EXPORT_END
|
|
|
|
// 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<typename Char>
|
|
[[nodiscard]] constexpr int parse_nonnegative_int(const Char*& begin, const Char* end, int error_value)
|
|
{
|
|
MP_UNITS_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 = ::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<typename Char>
|
|
[[nodiscard]] constexpr bool is_name_start(Char c)
|
|
{
|
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_';
|
|
}
|
|
|
|
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') {
|
|
int index = 0;
|
|
constexpr int max = (std::numeric_limits<int>::max)();
|
|
if (c != '0')
|
|
index = ::mp_units::detail::parse_nonnegative_int(begin, end, max);
|
|
else
|
|
++begin;
|
|
if (begin == end || (*begin != '}' && *begin != ':'))
|
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
|
handler.on_index(index);
|
|
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"));
|
|
return begin;
|
|
}
|
|
auto it = begin;
|
|
do {
|
|
++it;
|
|
} while (it != end && (::mp_units::detail::is_name_start(*it) || ('0' <= *it && *it <= '9')));
|
|
#if MP_UNITS_USE_FMTLIB
|
|
handler.on_name({begin, ::mp_units::detail::to_unsigned(it - begin)});
|
|
#else
|
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("named arguments are not supported in the C++ standard facilities"));
|
|
#endif
|
|
|
|
return it;
|
|
}
|
|
|
|
template<typename Char, typename Handler>
|
|
[[nodiscard]] constexpr const Char* parse_arg_id(const Char* begin, const Char* end, Handler& handler)
|
|
{
|
|
MP_UNITS_EXPECTS(begin != end);
|
|
Char c = *begin;
|
|
if (c != '}' && c != ':') return ::mp_units::detail::do_parse_arg_id(begin, end, handler);
|
|
handler.on_auto();
|
|
return begin;
|
|
}
|
|
|
|
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()
|
|
{
|
|
const int id = MP_UNITS_FMT_FROM_ARG_ID(ctx.next_arg_id());
|
|
ref = fmt_arg_ref<Char>(id);
|
|
#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L
|
|
ctx.check_dynamic_spec(id);
|
|
#endif
|
|
}
|
|
constexpr void on_index(int id)
|
|
{
|
|
ref = fmt_arg_ref<Char>(id);
|
|
ctx.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(id));
|
|
#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L
|
|
ctx.check_dynamic_spec(id);
|
|
#endif
|
|
}
|
|
#if MP_UNITS_USE_FMTLIB
|
|
constexpr void on_name([[maybe_unused]] std::basic_string_view<Char> id)
|
|
{
|
|
ref = fmt_arg_ref<Char>(id);
|
|
ctx.check_arg_id(id);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
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)
|
|
{
|
|
MP_UNITS_EXPECTS(begin != end);
|
|
if ('0' <= *begin && *begin <= '9') {
|
|
const int val = ::mp_units::detail::parse_nonnegative_int(begin, end, -1);
|
|
if (val != -1)
|
|
value = val;
|
|
else
|
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big"));
|
|
} else if (*begin == '{') {
|
|
++begin;
|
|
if (*begin == '%') return begin - 1; // mp-units extension
|
|
auto handler = dynamic_spec_id_handler<Char>{ctx, ref};
|
|
if (begin != end) begin = ::mp_units::detail::parse_arg_id(begin, end, handler);
|
|
if (begin != end && *begin == '}') return ++begin;
|
|
MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string"));
|
|
}
|
|
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 std::array 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};
|
|
const 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<typename Char, typename Specs>
|
|
[[nodiscard]] constexpr const Char* parse_align(const Char* begin, const Char* end, Specs& specs,
|
|
fmt_align default_align = fmt_align::none)
|
|
{
|
|
MP_UNITS_EXPECTS(begin != end);
|
|
auto align = fmt_align::none;
|
|
auto p = begin + code_point_length(begin);
|
|
if (end - p <= 0) 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;
|
|
}
|
|
if (align != fmt_align::none) {
|
|
if (p != begin) {
|
|
auto c = *begin;
|
|
if (c == '}') return 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;
|
|
} else {
|
|
++begin;
|
|
}
|
|
break;
|
|
}
|
|
if (p == begin) break;
|
|
p = begin;
|
|
}
|
|
if (align == fmt_align::none) align = default_align; // mp-units extension
|
|
specs.align = align;
|
|
return begin;
|
|
}
|
|
|
|
} // namespace mp_units::detail
|
|
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-union-access)
|