// 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 #include #include #ifndef MP_UNITS_IN_MODULE_INTERFACE #include #include #include #include #include #include #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 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 name; #endif constexpr value() {} constexpr explicit value(int idx) : index(idx) {} #if MP_UNITS_USE_FMTLIB constexpr value(std::basic_string_view 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 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 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 data_ = {Char{' '}}; unsigned char size_ = 1; public: constexpr fill_t& operator=(std::basic_string_view 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(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 inline constexpr bool is_integer = std::is_integral_v && !std::is_same_v && !std::is_same_v && !std::is_same_v; // Converts a character to ASCII. Returns a number > 127 on conversion failure. template [[nodiscard]] constexpr Char to_ascii(Char value) { return value; } template requires std::is_enum_v [[nodiscard]] constexpr std::underlying_type_t to_ascii(Char value) { return value; } // Casts a nonnegative integer to unsigned. template [[nodiscard]] constexpr std::make_unsigned_t to_unsigned(Int value) { MP_UNITS_EXPECTS(std::is_unsigned_v || value >= 0); return static_cast>(value); } template [[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::max())) { MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); } return static_cast(value); } template [[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 constexpr void handle_dynamic_spec(int& value, fmt_arg_ref 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(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(get_arg(ctx, ref.val.name)); break; #endif } } struct width_checker { template [[nodiscard]] constexpr unsigned long long operator()(T value) const { if constexpr (is_integer) { if constexpr (std::numeric_limits::is_signed) if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width")); return static_cast(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 [[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::digits10) return static_cast(value); // Check for overflow. const unsigned max = ::mp_units::detail::to_unsigned((std::numeric_limits::max)()); return num_digits == std::numeric_limits::digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max ? static_cast(value) : error_value; } template [[nodiscard]] constexpr bool is_name_start(Char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } template [[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::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 [[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 struct dynamic_spec_id_handler { MP_UNITS_STD_FMT::basic_format_parse_context& ctx; fmt_arg_ref& ref; constexpr void on_auto() { const int id = MP_UNITS_FMT_FROM_ARG_ID(ctx.next_arg_id()); ref = fmt_arg_ref(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(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 id) { ref = fmt_arg_ref(id); ctx.check_arg_id(id); } #endif }; template [[nodiscard]] constexpr const Char* parse_dynamic_spec(const Char* begin, const Char* end, int& value, fmt_arg_ref& ref, MP_UNITS_STD_FMT::basic_format_parse_context& 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{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 constexpr int code_point_length(It begin) { if constexpr (sizeof(std::iter_value_t) != 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(*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 [[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)