From 50c05bddb3ce3a0574d97a6b41fd4da98f1c8f7f Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 20 Jun 2025 09:06:49 +0200 Subject: [PATCH] refactor: `format.h` header file made deprecated --- example/clcpp_response.cpp | 1 - example/conversion_factor.cpp | 1 - example/foot_pound_second.cpp | 1 - .../glide_computer_lib/glide_computer_lib.cpp | 2 +- example/hello_units.cpp | 1 - example/hw_voltage.cpp | 1 - example/include/geographic.h | 1 - example/kalman_filter/kalman.h | 1 - .../kalman_filter/kalman_filter-example_1.cpp | 1 - .../kalman_filter/kalman_filter-example_2.cpp | 1 - .../kalman_filter/kalman_filter-example_3.cpp | 1 - .../kalman_filter/kalman_filter-example_4.cpp | 1 - .../kalman_filter/kalman_filter-example_5.cpp | 1 - .../kalman_filter/kalman_filter-example_6.cpp | 1 - .../kalman_filter/kalman_filter-example_7.cpp | 1 - .../kalman_filter/kalman_filter-example_8.cpp | 1 - example/si_constants.cpp | 1 - example/spectroscopy_units.cpp | 1 - example/storage_tank.cpp | 1 - src/core/CMakeLists.txt | 1 + src/core/include/mp-units/bits/format.h | 100 ++++ src/core/include/mp-units/core.h | 1 - src/core/include/mp-units/format.h | 487 +----------------- .../include/mp-units/framework/dimension.h | 72 +++ .../include/mp-units/framework/quantity.h | 247 +++++++++ src/core/include/mp-units/framework/unit.h | 97 ++++ test/runtime/almost_equals.h | 1 - test/runtime/fmt_test.cpp | 1 - test/runtime/linear_algebra_test.cpp | 1 - test_package/test_package.cpp | 1 - 30 files changed, 519 insertions(+), 510 deletions(-) create mode 100644 src/core/include/mp-units/bits/format.h diff --git a/example/clcpp_response.cpp b/example/clcpp_response.cpp index 42ed6d0b..f3b4c14e 100644 --- a/example/clcpp_response.cpp +++ b/example/clcpp_response.cpp @@ -25,7 +25,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/conversion_factor.cpp b/example/conversion_factor.cpp index af80465b..12e297d9 100644 --- a/example/conversion_factor.cpp +++ b/example/conversion_factor.cpp @@ -27,7 +27,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #endif diff --git a/example/foot_pound_second.cpp b/example/foot_pound_second.cpp index 6d93b34e..704cbd88 100644 --- a/example/foot_pound_second.cpp +++ b/example/foot_pound_second.cpp @@ -32,7 +32,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include // IWYU pragma: keep #include diff --git a/example/glide_computer_lib/glide_computer_lib.cpp b/example/glide_computer_lib/glide_computer_lib.cpp index fcefe081..777a6f8e 100644 --- a/example/glide_computer_lib/glide_computer_lib.cpp +++ b/example/glide_computer_lib/glide_computer_lib.cpp @@ -33,7 +33,7 @@ import std; #ifdef MP_UNITS_MODULES import mp_units.core; #else -#include +#include #endif namespace glide_computer { diff --git a/example/hello_units.cpp b/example/hello_units.cpp index e635bc25..6ada5c88 100644 --- a/example/hello_units.cpp +++ b/example/hello_units.cpp @@ -36,7 +36,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/hw_voltage.cpp b/example/hw_voltage.cpp index 2d57c72a..463f0312 100644 --- a/example/hw_voltage.cpp +++ b/example/hw_voltage.cpp @@ -36,7 +36,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/include/geographic.h b/example/include/geographic.h index c163afbb..71d341f0 100644 --- a/example/include/geographic.h +++ b/example/include/geographic.h @@ -36,7 +36,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/kalman_filter/kalman.h b/example/kalman_filter/kalman.h index 789a5ece..06114cb0 100644 --- a/example/kalman_filter/kalman.h +++ b/example/kalman_filter/kalman.h @@ -35,7 +35,6 @@ import std; import mp_units; #else #include -#include #include #include #include diff --git a/example/kalman_filter/kalman_filter-example_1.cpp b/example/kalman_filter/kalman_filter-example_1.cpp index b6cd0e12..2c721375 100644 --- a/example/kalman_filter/kalman_filter-example_1.cpp +++ b/example/kalman_filter/kalman_filter-example_1.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/kalman_filter/kalman_filter-example_2.cpp b/example/kalman_filter/kalman_filter-example_2.cpp index 9a3c562c..a743510c 100644 --- a/example/kalman_filter/kalman_filter-example_2.cpp +++ b/example/kalman_filter/kalman_filter-example_2.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/kalman_filter/kalman_filter-example_3.cpp b/example/kalman_filter/kalman_filter-example_3.cpp index 437ffc17..5b5963ba 100644 --- a/example/kalman_filter/kalman_filter-example_3.cpp +++ b/example/kalman_filter/kalman_filter-example_3.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/kalman_filter/kalman_filter-example_4.cpp b/example/kalman_filter/kalman_filter-example_4.cpp index d28b591f..f8d96afa 100644 --- a/example/kalman_filter/kalman_filter-example_4.cpp +++ b/example/kalman_filter/kalman_filter-example_4.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/kalman_filter/kalman_filter-example_5.cpp b/example/kalman_filter/kalman_filter-example_5.cpp index b6071b27..0a2ece06 100644 --- a/example/kalman_filter/kalman_filter-example_5.cpp +++ b/example/kalman_filter/kalman_filter-example_5.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/kalman_filter/kalman_filter-example_6.cpp b/example/kalman_filter/kalman_filter-example_6.cpp index 93a4d0f9..8874c7de 100644 --- a/example/kalman_filter/kalman_filter-example_6.cpp +++ b/example/kalman_filter/kalman_filter-example_6.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/kalman_filter/kalman_filter-example_7.cpp b/example/kalman_filter/kalman_filter-example_7.cpp index d4acad8b..116863a9 100644 --- a/example/kalman_filter/kalman_filter-example_7.cpp +++ b/example/kalman_filter/kalman_filter-example_7.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/kalman_filter/kalman_filter-example_8.cpp b/example/kalman_filter/kalman_filter-example_8.cpp index c3b37fd7..bbbef161 100644 --- a/example/kalman_filter/kalman_filter-example_8.cpp +++ b/example/kalman_filter/kalman_filter-example_8.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/example/si_constants.cpp b/example/si_constants.cpp index a43779ab..19b94936 100644 --- a/example/si_constants.cpp +++ b/example/si_constants.cpp @@ -35,7 +35,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #endif diff --git a/example/spectroscopy_units.cpp b/example/spectroscopy_units.cpp index 3fce6a8b..e4eb9157 100644 --- a/example/spectroscopy_units.cpp +++ b/example/spectroscopy_units.cpp @@ -31,7 +31,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #endif diff --git a/example/storage_tank.cpp b/example/storage_tank.cpp index e733f56f..8d8715d9 100644 --- a/example/storage_tank.cpp +++ b/example/storage_tank.cpp @@ -34,7 +34,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a67e72b1..bf5d3892 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -89,6 +89,7 @@ if(NOT ${projectPrefix}API_FREESTANDING) ${CMAKE_CURRENT_SOURCE_DIR}/include FILES include/mp-units/bits/fmt.h + include/mp-units/bits/format.h include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h include/mp-units/cartesian_vector.h diff --git a/src/core/include/mp-units/bits/format.h b/src/core/include/mp-units/bits/format.h new file mode 100644 index 00000000..cca6f2d5 --- /dev/null +++ b/src/core/include/mp-units/bits/format.h @@ -0,0 +1,100 @@ +// 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. + +// IWYU pragma: always_keep + +#pragma once + +#include +// +#include +#include +#include + +namespace mp_units::detail { + +template +[[nodiscard]] constexpr It at_most_one_of(It begin, It end, std::string_view modifiers) +{ + const It it = mp_units::detail::find_first_of(begin, end, modifiers.begin(), modifiers.end()); + if (it != end && mp_units::detail::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; +} + +// TODO the below should be exposed by the C++ Standard Library (used in our examples) +MP_UNITS_EXPORT_BEGIN + +template +struct fill_align_width_format_specs { + fill_t fill; + fmt_align align : 4 = fmt_align::none; + int width = 0; + fmt_arg_ref width_ref; +}; + +template +[[nodiscard]] constexpr It parse_fill_align_width( + MP_UNITS_STD_FMT::basic_format_parse_context>& ctx, It begin, It end, Specs& specs, + fmt_align default_align = fmt_align::none) +{ + auto it = begin; + if (it == end || *it == '}') return it; + + it = mp_units::detail::parse_align(it, end, specs, default_align); + if (it == end) return it; + + return mp_units::detail::parse_dynamic_spec(it, end, specs.width, specs.width_ref, ctx); +} + +template It> +constexpr It format_global_buffer(It out, const fill_align_width_format_specs& 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, "}}"); +} + +MP_UNITS_EXPORT_END + +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120625 +template +inline constexpr bool GCC_120625_is_complete = requires { sizeof(T) > 0; }; + +} // namespace mp_units::detail diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index ee07fe0f..1674cc83 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -29,7 +29,6 @@ #if MP_UNITS_HOSTED #include -#include #include #include #include diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 3e5bc8f2..add31137 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -24,489 +24,4 @@ #pragma once -#include -// -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef MP_UNITS_IN_MODULE_INTERFACE -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#endif -#endif - -namespace mp_units::detail { - -template -[[nodiscard]] constexpr It at_most_one_of(It begin, It end, std::string_view modifiers) -{ - const It it = mp_units::detail::find_first_of(begin, end, modifiers.begin(), modifiers.end()); - if (it != end && mp_units::detail::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; -} - -// TODO the below should be exposed by the C++ Standard Library (used in our examples) -MP_UNITS_EXPORT_BEGIN - -template -struct fill_align_width_format_specs { - fill_t fill; - fmt_align align : 4 = fmt_align::none; - int width = 0; - fmt_arg_ref width_ref; -}; - -template -[[nodiscard]] constexpr It parse_fill_align_width( - MP_UNITS_STD_FMT::basic_format_parse_context>& ctx, It begin, It end, Specs& specs, - fmt_align default_align = fmt_align::none) -{ - auto it = begin; - if (it == end || *it == '}') return it; - - it = mp_units::detail::parse_align(it, end, specs, default_align); - if (it == end) return it; - - return mp_units::detail::parse_dynamic_spec(it, end, specs.width, specs.width_ref, ctx); -} - -template It> -constexpr It format_global_buffer(It out, const fill_align_width_format_specs& 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, "}}"); -} - -MP_UNITS_EXPORT_END - -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120625 -template -inline constexpr bool GCC_120625_is_complete = requires { sizeof(T) > 0; }; - -} // namespace mp_units::detail - -// -// Grammar -// -// dimension-format-spec = [fill-and-align], [width], [dimension-spec]; -// dimension-spec = [character-set]; -// character-set = 'U' | 'P'; -// -template - requires mp_units::detail::GCC_120625_is_complete && mp_units::Dimension -class MP_UNITS_STD_FMT::formatter { - struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::dimension_symbol_formatting {}; - format_specs specs_{}; - std::basic_string_view fill_align_width_format_str_; - - template - constexpr It parse_dimension_specs(It begin, It end) - { - auto it = begin; - if (it == end || *it == '}') return begin; - - constexpr auto valid_modifiers = std::string_view{"UP"}; - for (; it != end && *it != '}'; ++it) { - if (valid_modifiers.find(*it) == std::string_view::npos) - throw MP_UNITS_STD_FMT::format_error("invalid dimension modifier specified"); - } - end = it; - - if (it = mp_units::detail::at_most_one_of(begin, end, "UAP"); it != end) - // TODO 'A' stands for an old and deprecated ASCII encoding - specs_.char_set = (*it == 'U') ? mp_units::character_set::utf8 : mp_units::character_set::portable; - - return end; - } - -public: - constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) - { - const auto begin = ctx.begin(); - auto end = ctx.end(); - - auto it = parse_fill_align_width(ctx, begin, end, specs_); - fill_align_width_format_str_ = {begin, it}; - if (it == end) return it; - - return parse_dimension_specs(it, end); - } - - template - constexpr auto format(const D& d, FormatContext& ctx) const -> decltype(ctx.out()) - { - auto specs = specs_; - mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - - if (specs.width == 0) - // Avoid extra copying if width is not specified - return mp_units::dimension_symbol_to(ctx.out(), d, specs); - std::basic_string unit_buffer; - mp_units::dimension_symbol_to(std::back_inserter(unit_buffer), d, specs); - - const std::basic_string global_format_buffer = - "{:" + std::basic_string{fill_align_width_format_str_} + "}"; - return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, - MP_UNITS_STD_FMT::make_format_args(unit_buffer)); - } -}; - - -// -// Grammar -// -// unit-format-spec = [fill-and-align], [width], [unit-spec]; -// unit-spec = [character-set], [unit-symbol-solidus], [unit-symbol-separator], [L] -// | [character-set], [unit-symbol-separator], [unit-symbol-solidus], [L] -// | [unit-symbol-solidus], [character-set], [unit-symbol-separator], [L] -// | [unit-symbol-solidus], [unit-symbol-separator], [character-set], [L] -// | [unit-symbol-separator], [character-set], [unit-symbol-solidus], [L] -// | [unit-symbol-separator], [unit-symbol-solidus], [character-set], [L]; -// unit-symbol-solidus = '1' | 'a' | 'n'; -// unit-symbol-separator = 's' | 'd'; -// -template - requires mp_units::detail::GCC_120625_is_complete && mp_units::Unit -class MP_UNITS_STD_FMT::formatter { - struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::unit_symbol_formatting {}; - format_specs specs_{}; - - std::basic_string_view fill_align_width_format_str_; - - template - constexpr It parse_unit_specs(It begin, It end) - { - auto it = begin; - if (it == end || *it == '}') return begin; - - constexpr auto valid_modifiers = std::string_view{"UAP1ansd"}; - for (; it != end && *it != '}'; ++it) { - if (valid_modifiers.find(*it) == std::string_view::npos) - throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); - } - end = it; - - if (it = mp_units::detail::at_most_one_of(begin, end, "UAP"); it != end) - // TODO 'A' stands for an old and deprecated ASCII encoding - specs_.char_set = (*it == 'U') ? mp_units::character_set::utf8 : mp_units::character_set::portable; - if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) { - switch (*it) { - case '1': - specs_.solidus = mp_units::unit_symbol_solidus::one_denominator; - break; - case 'a': - specs_.solidus = mp_units::unit_symbol_solidus::always; - break; - case 'n': - specs_.solidus = mp_units::unit_symbol_solidus::never; - break; - } - } - if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) { - if (*it == 'd' && specs_.char_set == mp_units::character_set::portable) - throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for UTF-8 encoding"); - specs_.separator = - (*it == 's') ? mp_units::unit_symbol_separator::space : mp_units::unit_symbol_separator::half_high_dot; - } - return end; - } - -public: - constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) - { - const auto begin = ctx.begin(); - auto end = ctx.end(); - - auto it = parse_fill_align_width(ctx, begin, end, specs_); - fill_align_width_format_str_ = {begin, it}; - if (it == end) return it; - - return parse_unit_specs(it, end); - } - - template - constexpr auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out()) - { - auto specs = specs_; - mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - - if (specs.width == 0) - // Avoid extra copying if width is not specified - return mp_units::unit_symbol_to(ctx.out(), u, specs); - std::basic_string unit_buffer; - mp_units::unit_symbol_to(std::back_inserter(unit_buffer), u, specs); - - const std::basic_string global_format_buffer = - "{:" + std::basic_string{fill_align_width_format_str_} + "}"; - return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, - MP_UNITS_STD_FMT::make_format_args(unit_buffer)); - } -}; - - -// -// Grammar -// -// quantity-format-spec = [fill-and-align], [width], [quantity-specs], [defaults-specs]; -// quantity-specs = conversion-spec; -// | quantity-specs, conversion-spec; -// | quantity-specs, literal-char; -// literal-char = ? any character other than '{', '}', or '%' ?; -// conversion-spec = '%', placement-type; -// placement-type = subentity-id | '?' | '%'; -// defaults-specs = ':', default-spec-list; -// default-spec-list = default-spec; -// | default-spec-list, default-spec; -// default-spec = subentity-id, '[' format-spec ']'; -// subentity-id = 'N' | 'U' | 'D'; -// format-spec = ? as specified by the formatter for the argument type ?; -// -#if __cpp_lib_format_ranges && !MP_UNITS_USE_FMTLIB -template Rep> -#else -template -#endif -class MP_UNITS_STD_FMT::formatter, Char> { - static constexpr auto unit = get_unit(Reference); - static constexpr auto dimension = get_quantity_spec(Reference).dimension; - - using quantity_t = mp_units::quantity; - using unit_t = MP_UNITS_NONCONST_TYPE(unit); - using dimension_t = MP_UNITS_NONCONST_TYPE(dimension); - using format_specs = mp_units::detail::fill_align_width_format_specs; - - format_specs specs_{}; - - std::basic_string_view modifiers_format_str_; - std::basic_string rep_format_str_ = "{}"; - std::basic_string unit_format_str_ = "{}"; - std::basic_string dimension_format_str_ = "{}"; - - MP_UNITS_STD_FMT::formatter rep_formatter_; - MP_UNITS_STD_FMT::formatter unit_formatter_; - MP_UNITS_STD_FMT::formatter dimension_formatter_; - - struct format_checker { - constexpr void on_number() const {} - constexpr void on_maybe_space() const {} - constexpr void on_unit() const {} - constexpr void on_dimension() const {} - template - constexpr void on_text(It, It) const - { - } - }; - - template - struct quantity_formatter { - const formatter& f; - OutputIt out; - const quantity_t& q; - std::locale locale; - - void on_number() - { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.rep_format_str_, - MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); - } - void on_maybe_space() - { - if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; - } - void on_unit() - { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); - } - void on_dimension() - { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.dimension_format_str_, - MP_UNITS_STD_FMT::make_format_args(q.dimension)); - } - template - void on_text(It begin, It end) const - { - mp_units::detail::copy(begin, end, out); - } - }; - template - quantity_formatter(const formatter&, OutputIt, Args...) -> quantity_formatter; - - template - constexpr const It parse_quantity_specs(It begin, It end, Handler& handler) const - { - if (begin == end || *begin == ':' || *begin == '}') return begin; - if (*begin != '%') - throw MP_UNITS_STD_FMT::format_error( - "`quantity-specs` should start with a `conversion-spec` ('%' characters expected)"); - auto ptr = begin; - while (ptr != end) { - auto c = *ptr; - if (c == '}') break; - if (c == ':') { - if (ptr + 1 != end && *(ptr + 1) == ':') { - handler.on_text(begin, ++ptr); // account for ':' - ++ptr; // consume the second ':' - continue; - } - // default specs started - break; - } - if (c != '%') { - ++ptr; - continue; - } - if (begin != ptr) handler.on_text(begin, ptr); - ++ptr; // consume '%' - if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `conversion-spec` format"); - - c = *ptr++; - switch (c) { - case 'N': - handler.on_number(); - break; - case 'U': - handler.on_unit(); - break; - case 'D': - handler.on_dimension(); - break; - case '?': - handler.on_maybe_space(); - break; - case '%': - handler.on_text(ptr - 1, ptr); - break; - default: - throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-type` token '") + c + "'"); - } - begin = ptr; - } - if (begin != ptr) handler.on_text(begin, ptr); - return ptr; - } - - template - constexpr It parse_default_spec(It begin, It end, Formatter& f, std::string& format_str) - { - if (begin == end || *begin != '[') - throw MP_UNITS_STD_FMT::format_error("`default-spec` should contain a `[` character"); - auto it = ++begin; - for (int nested_brackets = 0; it != end && !(*it == ']' && nested_brackets == 0); it++) { - if (*it == '[') ++nested_brackets; - if (*it == ']') { - if (nested_brackets == 0) throw MP_UNITS_STD_FMT::format_error("unmatched ']' in format string"); - --nested_brackets; - } - } - format_str = "{:" + std::string(begin, it) + '}'; - if (it == end) throw MP_UNITS_STD_FMT::format_error("unmatched '[' in format string"); - MP_UNITS_STD_FMT::basic_format_parse_context ctx(std::string_view(begin, it)); - auto ptr = f.parse(ctx); - if (ptr != it) throw MP_UNITS_STD_FMT::format_error("invalid subentity format '" + std::string(begin, it) + "'"); - return ++it; // skip `]` - } - - template - [[nodiscard]] constexpr It parse_defaults_specs(It begin, It end) - { - if (begin == end || *begin == '}') return begin; - if (*begin++ != ':') throw MP_UNITS_STD_FMT::format_error("`defaults-specs` should start with a `:`"); - do { - auto c = *begin++; - // TODO check if not repeated - switch (c) { - case 'N': - begin = parse_default_spec(begin, end, rep_formatter_, rep_format_str_); - break; - case 'U': - begin = parse_default_spec(begin, end, unit_formatter_, unit_format_str_); - break; - case 'D': - begin = parse_default_spec(begin, end, dimension_formatter_, dimension_format_str_); - break; - default: - throw MP_UNITS_STD_FMT::format_error(std::string("unknown `subentity-id` token '") + c + "'"); - } - } while (begin != end && *begin != '}'); - return begin; - } - - template - OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const - { - const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); - if (modifiers_format_str_.empty()) { - // default - out = MP_UNITS_STD_FMT::vformat_to(out, locale, rep_format_str_, - MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); - if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; - return MP_UNITS_STD_FMT::vformat_to(out, locale, unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); - } - // user provided format - quantity_formatter f{*this, out, q, locale}; - parse_quantity_specs(modifiers_format_str_.data(), modifiers_format_str_.data() + modifiers_format_str_.size(), f); - return f.out; - } - -public: - constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) - { - auto begin = ctx.begin(), end = ctx.end(); - - begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); - if (begin == end) return begin; - - const format_checker checker{}; - auto it = parse_quantity_specs(begin, end, checker); - modifiers_format_str_ = {begin, it}; - - return parse_defaults_specs(it, end); - } - - template - auto format(const quantity_t& q, FormatContext& ctx) const - { - auto specs = specs_; - mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - - if (specs.width == 0) { - // Avoid extra copying if width is not specified - format_quantity(ctx.out(), q, ctx); - return ctx.out(); - } - std::basic_string quantity_buffer; - format_quantity(std::back_inserter(quantity_buffer), q, ctx); - - std::basic_string fill_align_width_format_str; - mp_units::detail::format_global_buffer(std::back_inserter(fill_align_width_format_str), specs); - return MP_UNITS_STD_FMT::vformat_to(ctx.out(), fill_align_width_format_str, - MP_UNITS_STD_FMT::make_format_args(quantity_buffer)); - } -}; +#warning "This header file is deprecated and does not have to be included anymore." diff --git a/src/core/include/mp-units/framework/dimension.h b/src/core/include/mp-units/framework/dimension.h index 05e8c61a..65eed6ee 100644 --- a/src/core/include/mp-units/framework/dimension.h +++ b/src/core/include/mp-units/framework/dimension.h @@ -315,3 +315,75 @@ MP_UNITS_EXPORT template + +// +// Grammar +// +// dimension-format-spec = [fill-and-align], [width], [dimension-spec]; +// dimension-spec = [character-set]; +// character-set = 'U' | 'P'; +// +template + requires mp_units::detail::GCC_120625_is_complete && mp_units::Dimension +class MP_UNITS_STD_FMT::formatter { + struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::dimension_symbol_formatting {}; + format_specs specs_{}; + std::basic_string_view fill_align_width_format_str_; + + template + constexpr It parse_dimension_specs(It begin, It end) + { + auto it = begin; + if (it == end || *it == '}') return begin; + + constexpr auto valid_modifiers = std::string_view{"UP"}; + for (; it != end && *it != '}'; ++it) { + if (valid_modifiers.find(*it) == std::string_view::npos) + throw MP_UNITS_STD_FMT::format_error("invalid dimension modifier specified"); + } + end = it; + + if (it = mp_units::detail::at_most_one_of(begin, end, "UAP"); it != end) + // TODO 'A' stands for an old and deprecated ASCII encoding + specs_.char_set = (*it == 'U') ? mp_units::character_set::utf8 : mp_units::character_set::portable; + + return end; + } + +public: + constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) + { + const auto begin = ctx.begin(); + auto end = ctx.end(); + + auto it = parse_fill_align_width(ctx, begin, end, specs_); + fill_align_width_format_str_ = {begin, it}; + if (it == end) return it; + + return parse_dimension_specs(it, end); + } + + template + constexpr auto format(const D& d, FormatContext& ctx) const -> decltype(ctx.out()) + { + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); + + if (specs.width == 0) + // Avoid extra copying if width is not specified + return mp_units::dimension_symbol_to(ctx.out(), d, specs); + std::basic_string unit_buffer; + mp_units::dimension_symbol_to(std::back_inserter(unit_buffer), d, specs); + + const std::basic_string global_format_buffer = + "{:" + std::basic_string{fill_align_width_format_str_} + "}"; + return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, + MP_UNITS_STD_FMT::make_format_args(unit_buffer)); + } +}; + +#endif // MP_UNITS_HOSTED diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 63e89b47..34a4ce16 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -734,3 +734,250 @@ public: return {std::numeric_limits::denorm_min(), R}; } }; + +#if MP_UNITS_HOSTED + +#include +#include + +// +// Grammar +// +// quantity-format-spec = [fill-and-align], [width], [quantity-specs], [defaults-specs]; +// quantity-specs = conversion-spec; +// | quantity-specs, conversion-spec; +// | quantity-specs, literal-char; +// literal-char = ? any character other than '{', '}', or '%' ?; +// conversion-spec = '%', placement-type; +// placement-type = subentity-id | '?' | '%'; +// defaults-specs = ':', default-spec-list; +// default-spec-list = default-spec; +// | default-spec-list, default-spec; +// default-spec = subentity-id, '[' format-spec ']'; +// subentity-id = 'N' | 'U' | 'D'; +// format-spec = ? as specified by the formatter for the argument type ?; +// +#if __cpp_lib_format_ranges && !MP_UNITS_USE_FMTLIB +template Rep> +#else +template +#endif +class MP_UNITS_STD_FMT::formatter, Char> { + static constexpr auto unit = get_unit(Reference); + static constexpr auto dimension = get_quantity_spec(Reference).dimension; + + using quantity_t = mp_units::quantity; + using unit_t = MP_UNITS_NONCONST_TYPE(unit); + using dimension_t = MP_UNITS_NONCONST_TYPE(dimension); + using format_specs = mp_units::detail::fill_align_width_format_specs; + + format_specs specs_{}; + + std::basic_string_view modifiers_format_str_; + std::basic_string rep_format_str_ = "{}"; + std::basic_string unit_format_str_ = "{}"; + std::basic_string dimension_format_str_ = "{}"; + + MP_UNITS_STD_FMT::formatter rep_formatter_; + MP_UNITS_STD_FMT::formatter unit_formatter_; + MP_UNITS_STD_FMT::formatter dimension_formatter_; + + struct format_checker { + constexpr void on_number() const {} + constexpr void on_maybe_space() const {} + constexpr void on_unit() const {} + constexpr void on_dimension() const {} + template + constexpr void on_text(It, It) const + { + } + }; + + template + struct quantity_formatter { + const formatter& f; + OutputIt out; + const quantity_t& q; + std::locale locale; + + void on_number() + { + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.rep_format_str_, + MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); + } + void on_maybe_space() + { + if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; + } + void on_unit() + { + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); + } + void on_dimension() + { + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.dimension_format_str_, + MP_UNITS_STD_FMT::make_format_args(q.dimension)); + } + template + void on_text(It begin, It end) const + { + mp_units::detail::copy(begin, end, out); + } + }; + template + quantity_formatter(const formatter&, OutputIt, Args...) -> quantity_formatter; + + template + constexpr const It parse_quantity_specs(It begin, It end, Handler& handler) const + { + if (begin == end || *begin == ':' || *begin == '}') return begin; + if (*begin != '%') + throw MP_UNITS_STD_FMT::format_error( + "`quantity-specs` should start with a `conversion-spec` ('%' characters expected)"); + auto ptr = begin; + while (ptr != end) { + auto c = *ptr; + if (c == '}') break; + if (c == ':') { + if (ptr + 1 != end && *(ptr + 1) == ':') { + handler.on_text(begin, ++ptr); // account for ':' + ++ptr; // consume the second ':' + continue; + } + // default specs started + break; + } + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `conversion-spec` format"); + + c = *ptr++; + switch (c) { + case 'N': + handler.on_number(); + break; + case 'U': + handler.on_unit(); + break; + case 'D': + handler.on_dimension(); + break; + case '?': + handler.on_maybe_space(); + break; + case '%': + handler.on_text(ptr - 1, ptr); + break; + default: + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-type` token '") + c + "'"); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; + } + + template + constexpr It parse_default_spec(It begin, It end, Formatter& f, std::string& format_str) + { + if (begin == end || *begin != '[') + throw MP_UNITS_STD_FMT::format_error("`default-spec` should contain a `[` character"); + auto it = ++begin; + for (int nested_brackets = 0; it != end && !(*it == ']' && nested_brackets == 0); it++) { + if (*it == '[') ++nested_brackets; + if (*it == ']') { + if (nested_brackets == 0) throw MP_UNITS_STD_FMT::format_error("unmatched ']' in format string"); + --nested_brackets; + } + } + format_str = "{:" + std::string(begin, it) + '}'; + if (it == end) throw MP_UNITS_STD_FMT::format_error("unmatched '[' in format string"); + MP_UNITS_STD_FMT::basic_format_parse_context ctx(std::string_view(begin, it)); + auto ptr = f.parse(ctx); + if (ptr != it) throw MP_UNITS_STD_FMT::format_error("invalid subentity format '" + std::string(begin, it) + "'"); + return ++it; // skip `]` + } + + template + [[nodiscard]] constexpr It parse_defaults_specs(It begin, It end) + { + if (begin == end || *begin == '}') return begin; + if (*begin++ != ':') throw MP_UNITS_STD_FMT::format_error("`defaults-specs` should start with a `:`"); + do { + auto c = *begin++; + // TODO check if not repeated + switch (c) { + case 'N': + begin = parse_default_spec(begin, end, rep_formatter_, rep_format_str_); + break; + case 'U': + begin = parse_default_spec(begin, end, unit_formatter_, unit_format_str_); + break; + case 'D': + begin = parse_default_spec(begin, end, dimension_formatter_, dimension_format_str_); + break; + default: + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `subentity-id` token '") + c + "'"); + } + } while (begin != end && *begin != '}'); + return begin; + } + + template + OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const + { + const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); + if (modifiers_format_str_.empty()) { + // default + out = MP_UNITS_STD_FMT::vformat_to(out, locale, rep_format_str_, + MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); + if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; + return MP_UNITS_STD_FMT::vformat_to(out, locale, unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); + } + // user provided format + quantity_formatter f{*this, out, q, locale}; + parse_quantity_specs(modifiers_format_str_.data(), modifiers_format_str_.data() + modifiers_format_str_.size(), f); + return f.out; + } + +public: + constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) + { + auto begin = ctx.begin(), end = ctx.end(); + + begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); + if (begin == end) return begin; + + const format_checker checker{}; + auto it = parse_quantity_specs(begin, end, checker); + modifiers_format_str_ = {begin, it}; + + return parse_defaults_specs(it, end); + } + + template + auto format(const quantity_t& q, FormatContext& ctx) const + { + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); + + if (specs.width == 0) { + // Avoid extra copying if width is not specified + format_quantity(ctx.out(), q, ctx); + return ctx.out(); + } + std::basic_string quantity_buffer; + format_quantity(std::back_inserter(quantity_buffer), q, ctx); + + std::basic_string fill_align_width_format_str; + mp_units::detail::format_global_buffer(std::back_inserter(fill_align_width_format_str), specs); + return MP_UNITS_STD_FMT::vformat_to(ctx.out(), fill_align_width_format_str, + MP_UNITS_STD_FMT::make_format_args(quantity_buffer)); + } +}; + +#endif // MP_UNITS_HOSTED diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index aa10d2d7..863f151a 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -914,3 +914,100 @@ MP_UNITS_EXPORT template + +// +// Grammar +// +// unit-format-spec = [fill-and-align], [width], [unit-spec]; +// unit-spec = [character-set], [unit-symbol-solidus], [unit-symbol-separator], [L] +// | [character-set], [unit-symbol-separator], [unit-symbol-solidus], [L] +// | [unit-symbol-solidus], [character-set], [unit-symbol-separator], [L] +// | [unit-symbol-solidus], [unit-symbol-separator], [character-set], [L] +// | [unit-symbol-separator], [character-set], [unit-symbol-solidus], [L] +// | [unit-symbol-separator], [unit-symbol-solidus], [character-set], [L]; +// unit-symbol-solidus = '1' | 'a' | 'n'; +// unit-symbol-separator = 's' | 'd'; +// +template + requires mp_units::detail::GCC_120625_is_complete && mp_units::Unit +class MP_UNITS_STD_FMT::formatter { + struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::unit_symbol_formatting {}; + format_specs specs_{}; + + std::basic_string_view fill_align_width_format_str_; + + template + constexpr It parse_unit_specs(It begin, It end) + { + auto it = begin; + if (it == end || *it == '}') return begin; + + constexpr auto valid_modifiers = std::string_view{"UAP1ansd"}; + for (; it != end && *it != '}'; ++it) { + if (valid_modifiers.find(*it) == std::string_view::npos) + throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); + } + end = it; + + if (it = mp_units::detail::at_most_one_of(begin, end, "UAP"); it != end) + // TODO 'A' stands for an old and deprecated ASCII encoding + specs_.char_set = (*it == 'U') ? mp_units::character_set::utf8 : mp_units::character_set::portable; + if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) { + switch (*it) { + case '1': + specs_.solidus = mp_units::unit_symbol_solidus::one_denominator; + break; + case 'a': + specs_.solidus = mp_units::unit_symbol_solidus::always; + break; + case 'n': + specs_.solidus = mp_units::unit_symbol_solidus::never; + break; + } + } + if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) { + if (*it == 'd' && specs_.char_set == mp_units::character_set::portable) + throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for UTF-8 encoding"); + specs_.separator = + (*it == 's') ? mp_units::unit_symbol_separator::space : mp_units::unit_symbol_separator::half_high_dot; + } + return end; + } + +public: + constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) + { + const auto begin = ctx.begin(); + auto end = ctx.end(); + + auto it = parse_fill_align_width(ctx, begin, end, specs_); + fill_align_width_format_str_ = {begin, it}; + if (it == end) return it; + + return parse_unit_specs(it, end); + } + + template + constexpr auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out()) + { + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); + + if (specs.width == 0) + // Avoid extra copying if width is not specified + return mp_units::unit_symbol_to(ctx.out(), u, specs); + std::basic_string unit_buffer; + mp_units::unit_symbol_to(std::back_inserter(unit_buffer), u, specs); + + const std::basic_string global_format_buffer = + "{:" + std::basic_string{fill_align_width_format_str_} + "}"; + return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, + MP_UNITS_STD_FMT::make_format_args(unit_buffer)); + } +}; + +#endif // MP_UNITS_HOSTED diff --git a/test/runtime/almost_equals.h b/test/runtime/almost_equals.h index 4c2153a0..1f816bea 100644 --- a/test/runtime/almost_equals.h +++ b/test/runtime/almost_equals.h @@ -27,7 +27,6 @@ #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #endif diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index ebce5ad9..ee04a871 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -40,7 +40,6 @@ import std; import mp_units; #else #include -#include #include // IWYU pragma: keep #include #include diff --git a/test/runtime/linear_algebra_test.cpp b/test/runtime/linear_algebra_test.cpp index 40023a11..34aa9ba2 100644 --- a/test/runtime/linear_algebra_test.cpp +++ b/test/runtime/linear_algebra_test.cpp @@ -32,7 +32,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include // IWYU pragma: keep #include diff --git a/test_package/test_package.cpp b/test_package/test_package.cpp index 6361b004..c52cfa57 100644 --- a/test_package/test_package.cpp +++ b/test_package/test_package.cpp @@ -30,7 +30,6 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include #include