From 411a2e587813b678f5a912a804b34cf3b3b37cc4 Mon Sep 17 00:00:00 2001 From: Yaraslau Tamashevich Date: Mon, 8 Sep 2025 21:16:18 +0200 Subject: [PATCH] Introduces formatter functionality --- README.md | 17 ++++++++- include/rsl/format | 66 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/format/CMakeLists.txt | 3 ++ test/format/format.cpp | 73 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 include/rsl/format create mode 100644 test/format/CMakeLists.txt create mode 100644 test/format/format.cpp diff --git a/README.md b/README.md index 418335d..3ffea11 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,19 @@ int main() { ### -`rsl::tuple` is a reflective reimplementation of `std::tuple`. \ No newline at end of file +`rsl::tuple` is a reflective reimplementation of `std::tuple`. + +### +`rsl::format` is an implementation of a formatter for the arbitrary aggregate types + +``` cpp +struct S +{ + int i; + int j; +}; + +rsl::format(S{1,2}); // S{.i=1, .j=2} +``` +If you define `RSL_INJECT_GLOBAL_FORMATTER`, this will automatically generate formatting of aggregate types for `std::formatter` to use. +You can also use it directly inlude this functionality in the godbolt using `#include ` diff --git a/include/rsl/format b/include/rsl/format new file mode 100644 index 0000000..29dd3ec --- /dev/null +++ b/include/rsl/format @@ -0,0 +1,66 @@ +#pragma once +#include +#include + +namespace rsl { + +template +auto format(T const& t) { + std::string result; + std::string_view type_label = "(unnamed-type)"; + if constexpr (has_identifier(^^T)) + type_label = identifier_of(^^T); + auto out = std::format_to(std::back_inserter(result), "{}{{", type_label); + + auto delim = [first = true, &out]() mutable { + if (!first) { + *out++ = ','; + *out++ = ' '; + } + first = false; + }; + + constexpr auto access_ctx = std::meta::access_context::unchecked(); + + template for (constexpr auto base : define_static_array(bases_of(^^T, access_ctx))) { + delim(); + out = std::format_to(out, "{}", (typename[:type_of(base):] const&)(t)); + } + + template for (constexpr auto mem : + define_static_array(nonstatic_data_members_of(^^T, access_ctx))) { + delim(); + + std::string_view mem_label = "unnamed-member"; + if constexpr (has_identifier(mem)) + mem_label = identifier_of(mem); + + if constexpr (is_bit_field(mem) && !has_identifier(mem)) + out = std::format_to(out, "(unnamed-bitfield)"); + if constexpr (std::formattable) + out = std::format_to(out, ".{}={}", mem_label, t.[:mem:]); + else + out = std::format_to(out, ".{}={}", mem_label, format(t.[:mem:])); + } + + *out++ = '}'; + return result; +} + +} // namespace rsl + +#if defined(RSL_INJECT_GLOBAL_FORMATTER) + +# include + +template +requires std::is_aggregate_v +struct std::formatter +{ + constexpr auto parse(auto& ctx) { return ctx.begin(); } + auto format(T const& t, auto& ctx) const + { + return std::format_to(ctx.out(), "{}", rsl::format(t)); + } +}; +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2d6f6e3..c0b7b67 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(expect) add_subdirectory(serializer) add_subdirectory(string_view) add_subdirectory(span) +add_subdirectory(format) diff --git a/test/format/CMakeLists.txt b/test/format/CMakeLists.txt new file mode 100644 index 0000000..72e9b5c --- /dev/null +++ b/test/format/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(rsl_util_test PRIVATE + format.cpp +) diff --git a/test/format/format.cpp b/test/format/format.cpp new file mode 100644 index 0000000..c681ad7 --- /dev/null +++ b/test/format/format.cpp @@ -0,0 +1,73 @@ +#include +#define RSL_INJECT_GLOBAL_FORMATTER +#include + +struct S { + int a; + int b; + int c; + int d; + int e; + int f; + int g; +}; + +struct B { + S i; + S j; +}; + +struct BB { + S i; + S j; +}; + + +struct I: BB, B, S { + S Member; +}; + +template <> +struct std::formatter : std::formatter { + auto format(B const& t, auto& ctx) const { + return std::formatter::format("B TYPE", ctx); + } +}; + +TEST(Format, Format) { + S s{1, 2, 3, 4, 5, 6, 7}; + auto formatted = rsl::format(s); + ASSERT_EQ(formatted, "S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}"); + + B b{ + {1, 2, 3, 4, 5, 6, 7}, + {8, 9, 10, 11, 12, 13, 14} + }; + auto formattedB = rsl::format(b); + ASSERT_EQ(formattedB, + "B{.i=S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}, .j=S{.a=8, .b=9, .c=10, .d=11, .e=12, .f=13, .g=14}}"); + + BB bb{ + {1, 2, 3, 4, 5, 6, 7}, + {8, 9, 10, 11, 12, 13, 14} + }; + auto formattedBB = rsl::format(bb); + ASSERT_EQ(formattedBB, + "BB{.i=S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}, .j=S{.a=8, .b=9, .c=10, .d=11, .e=12, .f=13, .g=14}}"); + + auto formattedI = rsl::format(I{bb, b, s, S{-1, -2}}); + ASSERT_EQ(formattedI, + "I{BB{.i=S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}, .j=S{.a=8, .b=9, .c=10, .d=11, .e=12, .f=13, .g=14}}, B TYPE, S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}, .Member=S{.a=-1, .b=-2, .c=0, .d=0, .e=0, .f=0, .g=0}}"); + +} + +TEST(StringView, formatter_injection){ + B b{{1, 2, 3, 4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14}}; + auto formattedB = std::format("{}", b); + ASSERT_EQ(formattedB,"B TYPE"); + + BB bb{{1, 2, 3, 4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14}}; + auto formattedBB = std::format("{}", bb); + ASSERT_EQ(formattedBB, + "BB{.i=S{.a=1, .b=2, .c=3, .d=4, .e=5, .f=6, .g=7}, .j=S{.a=8, .b=9, .c=10, .d=11, .e=12, .f=13, .g=14}}"); +}