Skip to content

Commit

Permalink
optimised custom pmtv formatter
Browse files Browse the repository at this point in the history
Signed-off-by: Ralph J. Steinhagen <[email protected]>
  • Loading branch information
RalphSteinhagen committed Sep 5, 2024
1 parent b68fad3 commit 63f82c7
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 60 deletions.
3 changes: 1 addition & 2 deletions core/include/gnuradio-4.0/Tag.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

#include <pmtv/pmt.hpp>

#include <pmtv/format.hpp>

#include <gnuradio-4.0/meta/formatter.hpp>
#include <gnuradio-4.0/meta/utils.hpp>

#include "reflection.hpp"
Expand Down
108 changes: 94 additions & 14 deletions meta/include/gnuradio-4.0/meta/formatter.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef GNURADIO_FORMATTER_HPP
#define GNURADIO_FORMATTER_HPP

#include <chrono>
#include <complex>
#include <expected>
#include <fmt/chrono.h>
Expand Down Expand Up @@ -81,13 +82,10 @@ struct fmt::formatter<std::complex<T>> {
// simplified formatter for UncertainValue
template<gr::arithmetic_or_complex_like T>
struct fmt::formatter<gr::UncertainValue<T>> {
template<typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
return ctx.begin();
}
constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); }

template<typename FormatContext>
constexpr auto format(const gr::UncertainValue<T>& value, FormatContext& ctx) const {
constexpr auto format(const gr::UncertainValue<T>& value, FormatContext& ctx) const noexcept {
if constexpr (gr::meta::complex_like<T>) {
return fmt::format_to(ctx.out(), "({} ± {})", value.value, value.uncertainty);
} else {
Expand All @@ -96,16 +94,99 @@ struct fmt::formatter<gr::UncertainValue<T>> {
}
};

// pmt formatter

namespace gr {

template<typename OutputIt, typename Container, typename Separator>
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<typename Container, typename Separator>
constexpr std::string join(const Container& container, const Separator& separator) {
std::ostringstream ss;
auto out = std::ostream_iterator<char>(ss);
format_join(out, container, separator);
return ss.str();
}

} // namespace gr

template<>
struct fmt::formatter<gr::property_map> {
template<typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
return ctx.begin();
struct fmt::formatter<pmtv::map_t::value_type> {
constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); }

template<typename FormatContext>
auto format(const pmtv::map_t::value_type& kv, FormatContext& ctx) const noexcept {
return fmt::format_to(ctx.out(), "{}: {}", kv.first, kv.second);
}
};

template<pmtv::IsPmt T>
struct fmt::formatter<T> { // 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<typename FormatContext>
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<typename FormatContext, typename U>
static auto format_value(const U& arg, FormatContext& ctx) -> decltype(fmt::format_to(ctx.out(), "")) {
if constexpr (pmtv::Scalar<U> || pmtv::Complex<U>) {
return fmt::format_to(ctx.out(), "{}", arg);
} else if constexpr (std::same_as<U, std::string>) {
return fmt::format_to(ctx.out(), "{}", arg);
} else if constexpr (pmtv::UniformVector<U> || pmtv::UniformStringVector<U>) { // 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<U, std::vector<pmtv::pmt>>) { // 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<U>) { // 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<std::monostate, U>) {
return fmt::format_to(ctx.out(), "null");
} else {
return fmt::format_to(ctx.out(), "unknown type {}", typeid(U).name());
}
}
};

template<>
struct fmt::formatter<pmtv::map_t> {
constexpr auto parse(format_parse_context& ctx) const noexcept -> decltype(ctx.begin()) { return ctx.begin(); }

template<typename FormatContext>
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(), " }}");
}
};

Expand All @@ -125,7 +206,7 @@ struct fmt::formatter<std::vector<bool>> {
}

template<typename FormatContext>
auto format(const std::vector<bool>& v, FormatContext& ctx) const -> decltype(ctx.out()) {
auto format(const std::vector<bool>& v, FormatContext& ctx) const noexcept -> decltype(ctx.out()) {
auto sep = (presentation == 'c' ? ", " : " ");
size_t len = v.size();
fmt::format_to(ctx.out(), "[");
Expand All @@ -142,9 +223,8 @@ struct fmt::formatter<std::vector<bool>> {

template<typename Value, typename Error>
struct fmt::formatter<std::expected<Value, Error>> {
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<typename FormatContext>
auto format(const std::expected<Value, Error>& ret, FormatContext& ctx) const -> decltype(ctx.out()) {
if (ret.has_value()) {
Expand Down
91 changes: 47 additions & 44 deletions meta/test/qa_formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>;
"fmt::formatter<std::complex<T>>"_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<double>;
using UncertainComplex = gr::UncertainValue<std::complex<double>>;

"fmt::formatter<gr::meta::UncertainValue<T>>"_test = [] {
// Test with UncertainValue<double>
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<std::complex<double>>
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<gr::property_map>"_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<vector<bool>>"_test = [] {
std::vector<bool> 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<bool> 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)));
};
};

Expand All @@ -102,6 +107,4 @@ const boost::ut::suite expectedFormatter = [] {

} // namespace gr::meta::test

int
main() { /* tests are statically executed */
}
int main() { /* tests are statically executed */ }

0 comments on commit 63f82c7

Please sign in to comment.