diff --git a/core/include/gnuradio-4.0/Tag.hpp b/core/include/gnuradio-4.0/Tag.hpp index 45896f65d..d7a4f28a7 100644 --- a/core/include/gnuradio-4.0/Tag.hpp +++ b/core/include/gnuradio-4.0/Tag.hpp @@ -5,8 +5,7 @@ #include -#include - +#include #include #include "reflection.hpp" diff --git a/meta/include/gnuradio-4.0/meta/formatter.hpp b/meta/include/gnuradio-4.0/meta/formatter.hpp index abbe1edcb..82964bb37 100644 --- a/meta/include/gnuradio-4.0/meta/formatter.hpp +++ b/meta/include/gnuradio-4.0/meta/formatter.hpp @@ -1,6 +1,7 @@ #ifndef GNURADIO_FORMATTER_HPP #define GNURADIO_FORMATTER_HPP +#include #include #include #include @@ -81,13 +82,10 @@ struct fmt::formatter> { // simplified formatter for UncertainValue template struct fmt::formatter> { - template - constexpr auto parse(ParseContext& ctx) { - return ctx.begin(); - } + constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); } template - constexpr auto format(const gr::UncertainValue& value, FormatContext& ctx) const { + constexpr auto format(const gr::UncertainValue& value, FormatContext& ctx) const noexcept { if constexpr (gr::meta::complex_like) { return fmt::format_to(ctx.out(), "({} ± {})", value.value, value.uncertainty); } else { @@ -96,16 +94,99 @@ struct fmt::formatter> { } }; +// pmt formatter + +namespace gr { + +template +constexpr auto format_join(OutputIt out, const Container& container, const Separator& separator) { + auto it = container.begin(); + if (it != container.end()) { + out = fmt::format_to(out, "{}", *it); // format first element + ++it; + } + + for (; it != container.end(); ++it) { + out = fmt::format_to(out, "{}", separator); // insert separator + out = fmt::format_to(out, "{}", *it); // format remaining element + } + + return out; +} + +template +constexpr std::string join(const Container& container, const Separator& separator) { + std::ostringstream ss; + auto out = std::ostream_iterator(ss); + format_join(out, container, separator); + return ss.str(); +} + +} // namespace gr + template<> -struct fmt::formatter { - template - constexpr auto parse(ParseContext& ctx) { - return ctx.begin(); +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); } + + template + auto format(const pmtv::map_t::value_type& kv, FormatContext& ctx) const noexcept { + return fmt::format_to(ctx.out(), "{}: {}", kv.first, kv.second); + } +}; + +template +struct fmt::formatter { // alternate pmtv formatter optimised for compile-time not runtime + constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); } + + template + auto format(const T& value, FormatContext& ctx) const noexcept { + // if the std::visit dispatch is too expensive then maybe manually loop-unroll this + return std::visit([&ctx](const auto& format_arg) { return format_value(format_arg, ctx); }, value); } +private: + template + static auto format_value(const U& arg, FormatContext& ctx) -> decltype(fmt::format_to(ctx.out(), "")) { + if constexpr (pmtv::Scalar || pmtv::Complex) { + return fmt::format_to(ctx.out(), "{}", arg); + } else if constexpr (std::same_as) { + return fmt::format_to(ctx.out(), "{}", arg); + } else if constexpr (pmtv::UniformVector || pmtv::UniformStringVector) { // format vector + fmt::format_to(ctx.out(), "["); + gr::format_join(ctx.out(), arg, ", "); + return fmt::format_to(ctx.out(), "]"); + } else if constexpr (std::same_as>) { // format vector of pmts + fmt::format_to(ctx.out(), "["); + gr::format_join(ctx.out(), arg, ", "); + return fmt::format_to(ctx.out(), "]"); + } else if constexpr (pmtv::PmtMap) { // format map + fmt::format_to(ctx.out(), "{{ "); + for (auto it = arg.begin(); it != arg.end(); ++it) { + format_value(it->first, ctx); // Format key + fmt::format_to(ctx.out(), ": "); + format_value(it->second, ctx); // Format value + if (std::next(it) != arg.end()) { + fmt::format_to(ctx.out(), ", "); + } + } + return fmt::format_to(ctx.out(), " }}"); + } else if constexpr (std::same_as) { + return fmt::format_to(ctx.out(), "null"); + } else { + return fmt::format_to(ctx.out(), "unknown type {}", typeid(U).name()); + } + } +}; + +template<> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); } + template - constexpr auto format(const gr::property_map& value, FormatContext& ctx) const { - return fmt::format_to(ctx.out(), "{{ {} }}", fmt::join(value, ", ")); + constexpr auto format(const pmtv::map_t& value, FormatContext& ctx) const noexcept { + fmt::format_to(ctx.out(), "{{ "); + gr::format_join(ctx.out(), value, ", "); + return fmt::format_to(ctx.out(), " }}"); } }; @@ -125,7 +206,7 @@ struct fmt::formatter> { } template - auto format(const std::vector& v, FormatContext& ctx) const -> decltype(ctx.out()) { + auto format(const std::vector& v, FormatContext& ctx) const noexcept -> decltype(ctx.out()) { auto sep = (presentation == 'c' ? ", " : " "); size_t len = v.size(); fmt::format_to(ctx.out(), "["); @@ -142,9 +223,8 @@ struct fmt::formatter> { template struct fmt::formatter> { - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } + constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); } - // Formats the source_location, using 'f' for file and 'l' for line template auto format(const std::expected& ret, FormatContext& ctx) const -> decltype(ctx.out()) { if (ret.has_value()) { diff --git a/meta/test/qa_formatter.cpp b/meta/test/qa_formatter.cpp index 454b787e3..636bc1c4e 100644 --- a/meta/test/qa_formatter.cpp +++ b/meta/test/qa_formatter.cpp @@ -12,77 +12,82 @@ namespace gr::meta::test { const boost::ut::suite complexFormatter = [] { using namespace boost::ut; using namespace std::literals::complex_literals; + using namespace std::literals::string_literals; + using C = std::complex; "fmt::formatter>"_test = [] { - expect("(1+1i)" == fmt::format("{}", C(1., +1.))); - expect("(1-1i)" == fmt::format("{}", C(1., -1.))); - expect("1" == fmt::format("{}", C(1., 0.))); - expect("(1.234+1.12346e+12i)" == fmt::format("{}", C(1.234, 1123456789012))); - expect("(1+1i)" == fmt::format("{:g}", C(1., +1.))); - expect("(1-1i)" == fmt::format("{:g}", C(1., -1.))); - expect("1" == fmt::format("{:g}", C(1., 0.))); - expect("(1.12346e+12+1.234i)" == fmt::format("{:g}", C(1123456789012, 1.234))); - expect("1.12346e+12" == fmt::format("{:g}", C(1123456789012, 0))); - expect("(1.234+1.12346e+12i)" == fmt::format("{:g}", C(1.234, 1123456789012))); - expect("(1.12346E+12+1.234i)" == fmt::format("{:G}", C(1123456789012, 1.234))); - expect("1.12346E+12" == fmt::format("{:G}", C(1123456789012, 0))); - expect("(1.234+1.12346E+12i)" == fmt::format("{:G}", C(1.234, 1123456789012))); - - expect("(1.000000+1.000000i)" == fmt::format("{:f}", C(1., +1.))); - expect("(1.000000-1.000000i)" == fmt::format("{:f}", C(1., -1.))); - expect("1.000000" == fmt::format("{:f}", C(1., 0.))); - expect("(1.000000+1.000000i)" == fmt::format("{:F}", C(1., +1.))); - expect("(1.000000-1.000000i)" == fmt::format("{:F}", C(1., -1.))); - expect("1.000000" == fmt::format("{:F}", C(1., 0.))); - - expect("(1.000000e+00+1.000000e+00i)" == fmt::format("{:e}", C(1., +1.))); - expect("(1.000000e+00-1.000000e+00i)" == fmt::format("{:e}", C(1., -1.))); - expect("1.000000e+00" == fmt::format("{:e}", C(1., 0.))); - expect("(1.000000E+00+1.000000E+00i)" == fmt::format("{:E}", C(1., +1.))); - expect("(1.000000E+00-1.000000E+00i)" == fmt::format("{:E}", C(1., -1.))); - expect("1.000000E+00" == fmt::format("{:E}", C(1., 0.))); + expect(eq("(1+1i)"s, fmt::format("{}", C(1., +1.)))); + expect(eq("(1-1i)"s, fmt::format("{}", C(1., -1.)))); + expect(eq("1"s, fmt::format("{}", C(1., 0.)))); + expect(eq("(1.234+1.12346e+12i)"s, fmt::format("{}", C(1.234, 1123456789012)))); + expect(eq("(1+1i)"s, fmt::format("{:g}", C(1., +1.)))); + expect(eq("(1-1i)"s, fmt::format("{:g}", C(1., -1.)))); + expect(eq("1"s, fmt::format("{:g}", C(1., 0.)))); + expect(eq("(1.12346e+12+1.234i)"s, fmt::format("{:g}", C(1123456789012, 1.234)))); + expect(eq("1.12346e+12"s, fmt::format("{:g}", C(1123456789012, 0)))); + expect(eq("(1.234+1.12346e+12i)"s, fmt::format("{:g}", C(1.234, 1123456789012)))); + expect(eq("(1.12346E+12+1.234i)"s, fmt::format("{:G}", C(1123456789012, 1.234)))); + expect(eq("1.12346E+12"s, fmt::format("{:G}", C(1123456789012, 0)))); + expect(eq("(1.234+1.12346E+12i)"s, fmt::format("{:G}", C(1.234, 1123456789012)))); + + expect(eq("(1.000000+1.000000i)"s, fmt::format("{:f}", C(1., +1.)))); + expect(eq("(1.000000-1.000000i)"s, fmt::format("{:f}", C(1., -1.)))); + expect(eq("1.000000"s, fmt::format("{:f}", C(1., 0.)))); + expect(eq("(1.000000+1.000000i)"s, fmt::format("{:F}", C(1., +1.)))); + expect(eq("(1.000000-1.000000i)"s, fmt::format("{:F}", C(1., -1.)))); + expect(eq("1.000000"s, fmt::format("{:F}", C(1., 0.)))); + + expect(eq("(1.000000e+00+1.000000e+00i)"s, fmt::format("{:e}", C(1., +1.)))); + expect(eq("(1.000000e+00-1.000000e+00i)"s, fmt::format("{:e}", C(1., -1.)))); + expect(eq("1.000000e+00"s, fmt::format("{:e}", C(1., 0.)))); + expect(eq("(1.000000E+00+1.000000E+00i)"s, fmt::format("{:E}", C(1., +1.)))); + expect(eq("(1.000000E+00-1.000000E+00i)"s, fmt::format("{:E}", C(1., -1.)))); + expect(eq("1.000000E+00"s, fmt::format("{:E}", C(1., 0.)))); }; }; const boost::ut::suite uncertainValueFormatter = [] { using namespace boost::ut; using namespace std::literals::complex_literals; + using namespace std::literals::string_literals; using UncertainDouble = gr::UncertainValue; using UncertainComplex = gr::UncertainValue>; "fmt::formatter>"_test = [] { // Test with UncertainValue - expect("(1.23 ± 0.45)" == fmt::format("{}", UncertainDouble{ 1.23, 0.45 })); - expect("(3.14 ± 0.01)" == fmt::format("{}", UncertainDouble{ 3.14, 0.01 })); - expect("(0 ± 0)" == fmt::format("{}", UncertainDouble{ 0, 0 })); + expect(eq("(1.23 ± 0.45)"s, fmt::format("{}", UncertainDouble{1.23, 0.45}))); + expect(eq("(3.14 ± 0.01)"s, fmt::format("{}", UncertainDouble{3.14, 0.01}))); + expect(eq("(0 ± 0)"s, fmt::format("{}", UncertainDouble{0, 0}))); // Test with UncertainValue> - expect("((1+2i) ± (0.1+0.2i))" == fmt::format("{}", UncertainComplex{ { 1, 2 }, { 0.1, 0.2 } })); - expect("((3.14+1.59i) ± (0.01+0.02i))" == fmt::format("{}", UncertainComplex{ { 3.14, 1.59 }, { 0.01, 0.02 } })); - expect("(0 ± 0)" == fmt::format("{}", UncertainComplex{ { 0, 0 }, { 0, 0 } })); + expect(eq("((1+2i) ± (0.1+0.2i))"s, fmt::format("{}", UncertainComplex{{1, 2}, {0.1, 0.2}}))); + expect(eq("((3.14+1.59i) ± (0.01+0.02i))"s, fmt::format("{}", UncertainComplex{{3.14, 1.59}, {0.01, 0.02}}))); + expect(eq("(0 ± 0)"s, fmt::format("{}", UncertainComplex{{0, 0}, {0, 0}}))); }; }; const boost::ut::suite propertyMapFormatter = [] { using namespace boost::ut; + using namespace std::literals::string_literals; "fmt::formatter"_test = [] { - gr::property_map pmInt{ { "key0", 0 }, { "key1", 1 }, { "key2", 2 } }; - expect("{ key0: 0, key1: 1, key2: 2 }" == fmt::format("{}", pmInt)); + gr::property_map pmInt{{"key0", 0}, {"key1", 1}, {"key2", 2}}; + expect(eq("{ key0: 0, key1: 1, key2: 2 }"s, fmt::format("{}", pmInt))); - gr::property_map pmFloat{ { "key0", 0.01f }, { "key1", 1.01f }, { "key2", 2.01f } }; - expect("{ key0: 0.01, key1: 1.01, key2: 2.01 }" == fmt::format("{}", pmFloat)); + gr::property_map pmFloat{{"key0", 0.01f}, {"key1", 1.01f}, {"key2", 2.01f}}; + expect(eq("{ key0: 0.01, key1: 1.01, key2: 2.01 }"s, fmt::format("{}", pmFloat))); }; }; const boost::ut::suite vectorBoolFormatter = [] { using namespace boost::ut; + using namespace std::literals::string_literals; "fmt::formatter>"_test = [] { - std::vector boolVector{ true, false, true }; - expect("[true, false, true]" == fmt::format("{}", boolVector)); - expect("[true, false, true]" == fmt::format("{:c}", boolVector)); - expect("[true false true]" == fmt::format("{:s}", boolVector)); + std::vector boolVector{true, false, true}; + expect(eq("[true, false, true]"s, fmt::format("{}", boolVector))); + expect(eq("[true, false, true]"s, fmt::format("{:c}", boolVector))); + expect(eq("[true false true]"s, fmt::format("{:s}", boolVector))); }; }; @@ -102,6 +107,4 @@ const boost::ut::suite expectedFormatter = [] { } // namespace gr::meta::test -int -main() { /* tests are statically executed */ -} \ No newline at end of file +int main() { /* tests are statically executed */ } \ No newline at end of file