diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7464ad6..19fee01 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,3 +9,7 @@ function(add_example name) endfunction() add_example(vfthook) +add_example(libfmt) + +CPMAddPackage("gh:fmtlib/fmt#12.2.0") +target_link_libraries(libhat_example_libfmt PRIVATE fmt::fmt) diff --git a/examples/libfmt/main.cpp b/examples/libfmt/main.cpp new file mode 100644 index 0000000..0023311 --- /dev/null +++ b/examples/libfmt/main.cpp @@ -0,0 +1,41 @@ +#include +#include +#include + +template + requires(hat::formattable) +struct fmt::formatter : hat::formatter {}; + +template + requires(hat::disable_range_formatter) +struct fmt::is_range : std::false_type {}; + +int main() { + using namespace std::literals; + using namespace hat::literals; + + fmt::println("{}", hat::fixed_string{"abc"}); + + fmt::println("{}", "abc"_s); + + fmt::println("{}", "abc"_csv); + + fmt::println("{}", hat::cow_string{"abc"s}); + fmt::println("{}", hat::cow_string{"abc"sv}); + + fmt::println("{}", hat::cow_cstring{"abc"s}); + fmt::println("{}", hat::cow_cstring{"abc"_csv}); + + std::vector data{1, 2, 3}; + fmt::println("{}", hat::cow_span{data}); + fmt::println("{}", hat::cow_span{std::span{data}}); + // fmt::println("{}", hat::cow_writable_span{data}); + // fmt::println("{}", hat::cow_writable_span{std::span{data}}); + + fmt::println("{}", hat::signature_element{std::byte{0x10}, std::byte{0xF0}}); + + constexpr auto sig = "11 22 33"_sig; + fmt::println("{}", hat::signature(sig.begin(), sig.end())); + fmt::println("{}", hat::signature_view{sig}); + fmt::println("{}", sig); +} diff --git a/include/libhat.hpp b/include/libhat.hpp index de903b1..9510fd3 100644 --- a/include/libhat.hpp +++ b/include/libhat.hpp @@ -6,6 +6,7 @@ #include "libhat/cstring_view.hpp" #include "libhat/defines.hpp" #include "libhat/fixed_string.hpp" +#include "libhat/formatter.hpp" #include "libhat/memory.hpp" #include "libhat/memory_protector.hpp" #include "libhat/process.hpp" diff --git a/include/libhat/cow.hpp b/include/libhat/cow.hpp index 5c57b21..fe107c6 100644 --- a/include/libhat/cow.hpp +++ b/include/libhat/cow.hpp @@ -15,6 +15,7 @@ #include "detail/compressed_pair.hpp" #include "cstring_view.hpp" #include "export.hpp" +#include "formatter.hpp" LIBHAT_EXPORT namespace hat { @@ -450,4 +451,12 @@ LIBHAT_EXPORT namespace hat { template using cow_writable_span = cow_writable_span>; } + + template class Formatter> + struct formatter, CharT, Formatter> : Formatter { + template + constexpr auto format(const cow& value, FormatContext& ctx) const { + return Formatter::format(value.viewed(), ctx); + } + }; } diff --git a/include/libhat/cstring_view.hpp b/include/libhat/cstring_view.hpp index 9718153..7341772 100644 --- a/include/libhat/cstring_view.hpp +++ b/include/libhat/cstring_view.hpp @@ -7,6 +7,7 @@ #endif #include "export.hpp" +#include "formatter.hpp" LIBHAT_EXPORT namespace hat { @@ -308,6 +309,17 @@ LIBHAT_EXPORT namespace hat { #endif using u16cstring_view = basic_cstring_view; using u32cstring_view = basic_cstring_view; + + template class Formatter> + struct formatter, CharT, Formatter> : Formatter, CharT> { + template + constexpr auto format(const basic_cstring_view& value, FormatContext& ctx) const { + return Formatter, CharT>::format(value, ctx); + } + }; + + template + constexpr bool disable_range_formatter> = true; } namespace hat::detail { diff --git a/include/libhat/fixed_string.hpp b/include/libhat/fixed_string.hpp index ac3777d..aa21582 100644 --- a/include/libhat/fixed_string.hpp +++ b/include/libhat/fixed_string.hpp @@ -9,6 +9,7 @@ #include "cstring_view.hpp" #include "export.hpp" +#include "formatter.hpp" LIBHAT_EXPORT namespace hat { @@ -174,16 +175,25 @@ LIBHAT_EXPORT namespace hat { }; #define LIBHAT_DEFINE_FIXED_STRING(name, type) \ - template \ + template \ struct name : public basic_fixed_string { \ using basic_fixed_string::basic_fixed_string; \ }; \ - template \ + template \ name(const type(&str)[N]) -> name; \ - template \ + template \ constexpr inline auto operator+(const type (&cstr)[N], const basic_fixed_string& lstr) { \ return name{cstr} + lstr; \ - } + } \ + template class Formatter> \ + struct formatter, type, Formatter> : Formatter, type> { \ + template \ + constexpr auto format(const name& value, FormatContext& ctx) const { \ + return Formatter, type>::format(value.to_view(), ctx); \ + } \ + }; \ + template \ + constexpr bool disable_range_formatter> = true; LIBHAT_DEFINE_FIXED_STRING(fixed_string, char) LIBHAT_DEFINE_FIXED_STRING(wfixed_string, wchar_t) diff --git a/include/libhat/formatter.hpp b/include/libhat/formatter.hpp new file mode 100644 index 0000000..9b57376 --- /dev/null +++ b/include/libhat/formatter.hpp @@ -0,0 +1,35 @@ +#pragma once + +#ifndef LIBHAT_MODULE + #include + #include + #include +#endif + +#include "export.hpp" + +LIBHAT_EXPORT namespace hat { + + template class Formatter> + struct formatter { + formatter() = delete; + formatter(const formatter&) = delete; + formatter& operator=(const formatter&) = delete; + }; + + template class Formatter> + concept formattable = std::semiregular, CharT, Formatter>>; + + template + constexpr bool disable_range_formatter = false; +} + +template + requires(hat::formattable) +struct std::formatter : hat::formatter {}; + +#if __cpp_lib_format_ranges >= 202207L +template + requires(hat::disable_range_formatter) +constexpr auto std::format_kind = std::range_format::disabled; +#endif diff --git a/include/libhat/signature.hpp b/include/libhat/signature.hpp index 4479f1f..e0bdb61 100644 --- a/include/libhat/signature.hpp +++ b/include/libhat/signature.hpp @@ -12,8 +12,9 @@ #include "defines.hpp" #include "export.hpp" -#include "strconv.hpp" #include "fixed_string.hpp" +#include "formatter.hpp" +#include "strconv.hpp" LIBHAT_EXPORT namespace hat { @@ -254,38 +255,90 @@ LIBHAT_EXPORT namespace hat { } #endif - [[nodiscard]] constexpr std::string to_string(const signature_view signature) { + constexpr auto format_signature_to(std::output_iterator auto out, const signature_element element) { constexpr std::string_view hex{"0123456789ABCDEF"}; - std::string ret; - ret.reserve(signature.size() * 3); + const bool a = (element.mask() & std::byte{0xF0}) == std::byte{0xF0}; + const bool b = (element.mask() & std::byte{0x0F}) == std::byte{0x0F}; + if (a || b) { + *out++ = a ? hex[static_cast(element.value() >> 4) & 0xFu] : '?'; + *out++ = b ? hex[static_cast(element.value() >> 0) & 0xFu] : '?'; + } else if (element.none()) { + *out++ = '?'; + } else { + *out++ = element.has(7) ? (element.at(7) ? '1' : '0') : '?'; + *out++ = element.has(6) ? (element.at(6) ? '1' : '0') : '?'; + *out++ = element.has(5) ? (element.at(5) ? '1' : '0') : '?'; + *out++ = element.has(4) ? (element.at(4) ? '1' : '0') : '?'; + *out++ = element.has(3) ? (element.at(3) ? '1' : '0') : '?'; + *out++ = element.has(2) ? (element.at(2) ? '1' : '0') : '?'; + *out++ = element.has(1) ? (element.at(1) ? '1' : '0') : '?'; + *out++ = element.has(0) ? (element.at(0) ? '1' : '0') : '?'; + } + return out; + } + + constexpr auto format_signature_to(std::output_iterator auto out, const signature_view signature) { for (auto& element : signature) { - const bool a = (element.mask() & std::byte{0xF0}) == std::byte{0xF0}; - const bool b = (element.mask() & std::byte{0x0F}) == std::byte{0x0F}; - if (a || b) { - ret += { - a ? hex[static_cast(element.value() >> 4) & 0xFu] : '?', - b ? hex[static_cast(element.value() >> 0) & 0xFu] : '?', - ' ' - }; - } else if (element.none()) { - ret += "? "; - } else { - ret += { - element.has(7) ? (element.at(7) ? '1' : '0') : '?', - element.has(6) ? (element.at(6) ? '1' : '0') : '?', - element.has(5) ? (element.at(5) ? '1' : '0') : '?', - element.has(4) ? (element.at(4) ? '1' : '0') : '?', - element.has(3) ? (element.at(3) ? '1' : '0') : '?', - element.has(2) ? (element.at(2) ? '1' : '0') : '?', - element.has(1) ? (element.at(1) ? '1' : '0') : '?', - element.has(0) ? (element.at(0) ? '1' : '0') : '?', - ' ' - }; + out = format_signature_to(out, element); + if (&element != &signature.back()) { + *out++ = ' '; } } - ret.pop_back(); + return out; + } + + [[nodiscard]] constexpr std::string to_string(const signature_element element) { + std::string ret; + format_signature_to(std::back_inserter(ret), element); return ret; } + + [[nodiscard]] constexpr std::string to_string(const signature_view signature) { + std::string ret; + ret.reserve(signature.size() * 3); + format_signature_to(std::back_inserter(ret), signature); + return ret; + } +} + +namespace hat::detail { + + template + struct signature_formatter { + template + constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + constexpr auto format(const T& value, FormatContext& ctx) const { + return format_signature_to(ctx.out(), value); + } + }; +} + +LIBHAT_EXPORT namespace hat { + + template class Formatter> + struct formatter : detail::signature_formatter {}; + + template class Formatter> + struct formatter : detail::signature_formatter {}; + + template<> + constexpr bool disable_range_formatter = true; + + template class Formatter> + struct formatter : detail::signature_formatter {}; + + template<> + constexpr bool disable_range_formatter = true; + + template class Formatter> + struct formatter, char, Formatter> : detail::signature_formatter> {}; + + template + constexpr bool disable_range_formatter> = true; } LIBHAT_EXPORT namespace hat::inline literals::inline signature_literals { diff --git a/include/libhat/string_literal.hpp b/include/libhat/string_literal.hpp index 2aafd53..1965b9c 100644 --- a/include/libhat/string_literal.hpp +++ b/include/libhat/string_literal.hpp @@ -2,6 +2,7 @@ #include "export.hpp" #include "fixed_string.hpp" +#include "formatter.hpp" LIBHAT_EXPORT namespace hat { @@ -26,6 +27,14 @@ LIBHAT_EXPORT namespace hat { template using u32string_literal = basic_string_literal; + + template class Formatter> + struct formatter, typename decltype(str)::value_type, Formatter> : Formatter { + template + constexpr auto format(const string_literal&, FormatContext& ctx) const { + return Formatter::format(str, ctx); + } + }; } LIBHAT_EXPORT namespace hat::inline literals::inline string_literals { diff --git a/module/libhat.cppm b/module/libhat.cppm index 380b0c8..25a26aa 100644 --- a/module/libhat.cppm +++ b/module/libhat.cppm @@ -16,6 +16,7 @@ module; #include #include #include + #include #include #include #include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9ef35d7..69bd469 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,7 @@ register_test(libhat_benchmark_compare benchmark/Compare.cpp) register_test(libhat_benchmark_compare_impl benchmark/CompareImpl.cpp) register_test(libhat_test_scanner tests/Scanner.cpp) register_test(libhat_test_process tests/Process.cpp) +register_test(libhat_test_formatter tests/Formatter.cpp) if(LIBHAT_TESTING_SAMPLE_BIN) CPMAddPackage( diff --git a/test/tests/Formatter.cpp b/test/tests/Formatter.cpp new file mode 100644 index 0000000..7f07ae7 --- /dev/null +++ b/test/tests/Formatter.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; +using namespace hat::literals; + +TEST(FormatterTest, FixedString) { + ASSERT_EQ(std::format("{}", hat::fixed_string{"abc"}), "abc"sv); +} + +TEST(FormatterTest, StringLiteral) { + ASSERT_EQ(std::format("{}", "abc"_s), "abc"sv); +} + +TEST(FormatterTest, CStringView) { + ASSERT_EQ(std::format("{}", "abc"_csv), "abc"sv); +} + +TEST(FormatterTest, CowString) { + ASSERT_EQ(std::format("{}", hat::cow_string{"abc"s}), "abc"sv); + ASSERT_EQ(std::format("{}", hat::cow_string{"abc"sv}), "abc"sv); +} + +TEST(FormatterTest, CowCString) { + ASSERT_EQ(std::format("{}", hat::cow_cstring{"abc"s}), "abc"sv); + ASSERT_EQ(std::format("{}", hat::cow_cstring{"abc"_csv}), "abc"sv); +} + +TEST(FormatterTest, CowSpan) { +#if __cpp_lib_format_ranges >= 202207L + std::vector data{1, 2, 3}; + ASSERT_EQ(std::format("{}", hat::cow_span{data}), "[1, 2, 3]"sv); + ASSERT_EQ(std::format("{}", hat::cow_span{std::span{data}}), "[1, 2, 3]"sv); + // ASSERT_EQ(std::format("{}", hat::cow_writable_span{data}), "[1, 2, 3]"sv); + // ASSERT_EQ(std::format("{}", hat::cow_writable_span{std::span{data}}), "[1, 2, 3]"sv); +#else + GTEST_SKIP_("Formatting ranges requires C++23"); +#endif +} + +TEST(FormatterTest, SignatureElement) { + ASSERT_EQ(std::format("{}", hat::signature_element{std::byte{0x10}, std::byte{0xF0}}), "1?"sv); +} + +TEST(FormatterTest, Signature) { + constexpr auto sig = "11 22 33"_sig; + ASSERT_EQ(std::format("{}", hat::signature(sig.begin(), sig.end())), "11 22 33"sv); + ASSERT_EQ(std::format("{}", hat::signature_view{sig}), "11 22 33"sv); + ASSERT_EQ(std::format("{}", sig), "11 22 33"sv); +}