Created
October 8, 2024 06:32
-
-
Save jevinskie/cf3f34569a86bdab8395293f3cebe80b to your computer and use it in GitHub Desktop.
{fmt} extended hexdump
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "common.hpp" | |
#include "fmt/base.h" | |
#include <functional> | |
#include <iterator> | |
#include <type_traits> | |
#undef NDEBUG | |
#include <algorithm> | |
#include <cassert> | |
#include <cctype> | |
#include <concepts> | |
#include <cstdio> | |
#include <string_view> | |
#include <sys/ioctl.h> | |
#include <unistd.h> | |
#include <fmt/compile.h> | |
#include <fmt/format.h> | |
#include <fmt/printf.h> | |
#include <fmt/ranges.h> | |
namespace gsnr { | |
using namespace fmt::literals; | |
static constexpr size_t ehexdump_fallback_term_width = 80; | |
static constexpr size_t ehexdump_min_bytes_per_line = 4; | |
static constexpr size_t ehexdump_max_bytes_per_line = 256; | |
static constexpr const std::string_view ehexdump_extascii_table[] = { | |
"▪"sv, "☺"sv, "☻"sv, "♥"sv, "♦"sv, "♣"sv, "♠"sv, "•"sv, "◘"sv, "○"sv, "◙"sv, "♂"sv, "♀"sv, | |
"♪"sv, "♫"sv, "☼"sv, "►"sv, "◄"sv, "↕"sv, "‼"sv, "¶"sv, "§"sv, "▬"sv, "↨"sv, "↑"sv, "↓"sv, | |
"→"sv, "←"sv, "∟"sv, "↔"sv, "▲"sv, "▼"sv, " "sv, "!"sv, "\""sv, "#"sv, "$"sv, "%"sv, "&"sv, | |
"'"sv, "("sv, ")"sv, "*"sv, "+"sv, ","sv, "-"sv, "."sv, "/"sv, "0"sv, "1"sv, "2"sv, "3"sv, | |
"4"sv, "5"sv, "6"sv, "7"sv, "8"sv, "9"sv, ":"sv, ";"sv, "<"sv, "="sv, ">"sv, "?"sv, "@"sv, | |
"A"sv, "B"sv, "C"sv, "D"sv, "E"sv, "F"sv, "G"sv, "H"sv, "I"sv, "J"sv, "K"sv, "L"sv, "M"sv, | |
"N"sv, "O"sv, "P"sv, "Q"sv, "R"sv, "S"sv, "T"sv, "U"sv, "V"sv, "W"sv, "X"sv, "Y"sv, "Z"sv, | |
"["sv, "\\"sv, "]"sv, "^"sv, "_"sv, "`"sv, "a"sv, "b"sv, "c"sv, "d"sv, "e"sv, "f"sv, "g"sv, | |
"h"sv, "i"sv, "j"sv, "k"sv, "l"sv, "m"sv, "n"sv, "o"sv, "p"sv, "q"sv, "r"sv, "s"sv, "t"sv, | |
"u"sv, "v"sv, "w"sv, "x"sv, "y"sv, "z"sv, "{"sv, "|"sv, "}"sv, "~"sv, "⌂"sv, "█"sv, "⡀"sv, | |
"⢀"sv, "⣀"sv, "⠠"sv, "⡠"sv, "⢠"sv, "⣠"sv, "⠄"sv, "⡄"sv, "⢄"sv, "⣄"sv, "⠤"sv, "⡤"sv, "⢤"sv, | |
"⣤"sv, "⠁"sv, "⡁"sv, "⢁"sv, "⣁"sv, "⠡"sv, "⡡"sv, "⢡"sv, "⣡"sv, "⠅"sv, "⡅"sv, "⢅"sv, "⣅"sv, | |
"⠥"sv, "⡥"sv, "⢥"sv, "⣥"sv, "⠃"sv, "⡃"sv, "⢃"sv, "⣃"sv, "⠣"sv, "⡣"sv, "⢣"sv, "⣣"sv, "⠇"sv, | |
"⡇"sv, "⢇"sv, "⣇"sv, "⠧"sv, "⡧"sv, "⢧"sv, "⣧"sv, "⠉"sv, "⡉"sv, "⢉"sv, "⣉"sv, "⠩"sv, "⡩"sv, | |
"⢩"sv, "⣩"sv, "⠍"sv, "⡍"sv, "⢍"sv, "⣍"sv, "⠭"sv, "⡭"sv, "⢭"sv, "⣭"sv, "⠊"sv, "⡊"sv, "⢊"sv, | |
"⣊"sv, "⠪"sv, "⡪"sv, "⢪"sv, "⣪"sv, "⠎"sv, "⡎"sv, "⢎"sv, "⣎"sv, "⠮"sv, "⡮"sv, "⢮"sv, "⣮"sv, | |
"⠑"sv, "⡑"sv, "⢑"sv, "⣑"sv, "⠱"sv, "⡱"sv, "⢱"sv, "⣱"sv, "⠕"sv, "⡕"sv, "⢕"sv, "⣕"sv, "⠵"sv, | |
"⡵"sv, "⢵"sv, "⣵"sv, "⠚"sv, "⡚"sv, "⢚"sv, "⣚"sv, "⠺"sv, "⡺"sv, "⢺"sv, "⣺"sv, "⠞"sv, "⡞"sv, | |
"⢞"sv, "⣞"sv, "⠾"sv, "⡾"sv, "⢾"sv, "⣾"sv, "⠛"sv, "⡛"sv, "⢛"sv, "⣛"sv, "⠻"sv, "⡻"sv, "⢻"sv, | |
"⣻"sv, "⠟"sv, "⡟"sv, "⢟"sv, "⣟"sv, "⠿"sv, "⡿"sv, "⢿"sv, "⣿"sv, | |
}; | |
static_assert(sizeof_array(ehexdump_extascii_table) == 256); | |
static constexpr size_t ehexdump_extascii_table_max_len = sv_table_max_len(ehexdump_extascii_table); | |
static constexpr size_t ehexdump_get_terminal_width() { | |
if consteval { | |
return ehexdump_fallback_term_width; | |
} | |
struct winsize w; | |
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { | |
return w.ws_col; | |
} | |
return ehexdump_fallback_term_width; | |
} | |
static constexpr char classic_hexdump_repr(const uint8_t b) { | |
if (b >= 0x20 && b < 0x7F) { | |
return static_cast<char>(b); | |
} | |
return '.'; | |
} | |
#if 1 | |
static constexpr bool ehexdump_debug = true; | |
#else | |
static constexpr bool ehexdump_debug = false; | |
#endif | |
#if 1 | |
static constexpr bool ehexdump_debug_reserve = true; | |
#else | |
static constexpr bool ehexdump_debug_reserve = false; | |
#endif | |
GSNR_EXPORT fmt::memory_buffer ehexdump_mb(const void *data, const size_t sz, | |
const bool extended = true, const bool offset = true, | |
const bool caps = false, const size_t width = 0) { | |
constexpr size_t hex_len_per_byte = 3; // 2 hex digits + 1 space | |
constexpr size_t ascii_len_per_byte = 1; // 1 ASCII character | |
constexpr auto ascii_max_sz_per_byte = ehexdump_extascii_table_max_len; | |
constexpr auto pad_between_off_and_hex = " "sv; | |
constexpr auto pad_between_off_and_hex_sz = pad_between_off_and_hex.size(); | |
constexpr auto pad_between_hex_and_repr = " |"sv; | |
constexpr auto pad_between_hex_and_repr_sz = pad_between_hex_and_repr.size(); | |
constexpr auto off_pre = "0x"sv; | |
constexpr auto off_pre_len = off_pre.size(); | |
constexpr auto line_end = "|\n"sv; | |
constexpr auto line_end_sz = line_end.size(); | |
constexpr auto line_end_sz_no_nl = line_end_sz - 1; | |
const auto byte_data = static_cast<const uint8_t *>(data); | |
// Calculate num_off_digits using integer arithmetic | |
const size_t num_off_digits = offset ? hex_nibbles_needed_even(sz) : (sizeof(void *) * 2); | |
const size_t calc_len = width == 0 ? ehexdump_get_terminal_width() : width; | |
// Calculate bytes_per_line using integer arithmetic | |
const size_t off_len = off_pre_len + num_off_digits; | |
const size_t non_data_len = | |
off_len + pad_between_off_and_hex_sz + pad_between_hex_and_repr_sz + line_end_sz_no_nl; | |
const size_t avail_len = (calc_len > non_data_len) ? (calc_len - non_data_len) : 0; | |
size_t intermediate_bytes_per_line = avail_len / (hex_len_per_byte + ascii_len_per_byte); | |
// Ensure a minimum of 4 bytes per line | |
intermediate_bytes_per_line = | |
std::max(ehexdump_min_bytes_per_line, intermediate_bytes_per_line); | |
// Ensure a maximum of 256 bytes per line | |
intermediate_bytes_per_line = | |
std::min(ehexdump_max_bytes_per_line, intermediate_bytes_per_line); | |
const size_t bytes_per_line = intermediate_bytes_per_line; | |
// Calculate the exact total size to reserve upfront | |
const size_t num_lines_full = sz / bytes_per_line; | |
const size_t num_bytes_trailing = sz % bytes_per_line; | |
const size_t max_full_line_sz = off_len + (bytes_per_line * hex_len_per_byte) + | |
pad_between_off_and_hex_sz + pad_between_hex_and_repr_sz + | |
(bytes_per_line * ascii_max_sz_per_byte) + line_end_sz; | |
const size_t max_trailing_line_sz = | |
num_bytes_trailing ? (off_len + (bytes_per_line * hex_len_per_byte) + | |
pad_between_off_and_hex_sz + pad_between_hex_and_repr_sz + | |
(num_bytes_trailing * ascii_max_sz_per_byte) + line_end_sz) | |
: 0; | |
const size_t max_total_sz = num_lines_full * max_full_line_sz + max_trailing_line_sz; | |
fmt::memory_buffer buf; | |
#if 1 | |
buf.reserve(max_total_sz); | |
#else | |
// add space for NUL terminator for no-copy std::string conversion | |
// NOTE: std::string and/or C++ is dumb and can't do this | |
buf.reserve(max_total_sz + 1); | |
#endif | |
if (ehexdump_debug) { | |
fmt::print( | |
::stderr, | |
"ehexdump dbg: data: {} sz: {:d} extended: {:s} offset: {:s} caps: {:s} width {:d}\n" | |
"hex_len_per_byte: {:d} ascii_len_per_byte: {:d} ascii_max_sz_per_byte: {:d}\n" | |
"pad_between_off_and_hex: \"{:s}\" pad_between_off_and_hex_sz: {:d}\n" | |
"pad_between_hex_and_repr: \"{:s}\" pad_between_hex_and_repr_sz: {:d}\n" | |
"off_pre_len: {:d} num_off_digits: {:d} off_len: {:d} line_end_sz: {:d}\n" | |
"calc_len: {:d} non_data_len: {:d} avail_len: {:d} bytes_per_line: {:d}\n" | |
"num_lines_full: {:d} num_bytes_trailing: {:d} max_full_line_sz: {:d} " | |
"max_trailing_line_sz: {:d} max_total_sz: {:d}\n", | |
fmt::ptr(data), sz, boolmoji(extended), boolmoji(offset), boolmoji(caps), width, | |
hex_len_per_byte, ascii_len_per_byte, ascii_max_sz_per_byte, pad_between_off_and_hex, | |
pad_between_off_and_hex_sz, pad_between_hex_and_repr, pad_between_hex_and_repr_sz, | |
off_pre_len, num_off_digits, off_len, line_end_sz, calc_len, non_data_len, avail_len, | |
bytes_per_line, num_lines_full, num_bytes_trailing, max_full_line_sz, | |
max_trailing_line_sz, max_total_sz); | |
} | |
for (size_t line_offset = 0; line_offset < sz; line_offset += bytes_per_line) { | |
const size_t line_bytes = std::min(bytes_per_line, sz - line_offset); | |
// Offset column | |
const uintptr_t off = | |
offset ? line_offset : reinterpret_cast<uintptr_t>(byte_data) + line_offset; | |
if (caps) { | |
fmt::format_to(std::back_inserter(buf), "0x{:0{}X} "sv, off, num_off_digits); | |
} else { | |
fmt::format_to(std::back_inserter(buf), "0x{:0{}x} "sv, off, num_off_digits); | |
} | |
// Hex values column | |
if (caps) { | |
fmt::format_to( | |
std::back_inserter(buf), "{:02X}", | |
fmt::join(&byte_data[line_offset], &byte_data[line_offset + line_bytes], " "sv)); | |
} else { | |
fmt::format_to( | |
std::back_inserter(buf), "{:02x}", | |
fmt::join(&byte_data[line_offset], &byte_data[line_offset + line_bytes], " "sv)); | |
} | |
// Padding for missing bytes if any | |
if (line_bytes < bytes_per_line) { | |
fmt::format_to(std::back_inserter(buf), "{:{}s}"sv, ""sv, | |
(bytes_per_line - line_bytes) * hex_len_per_byte); | |
} | |
// Padding between hex and ASCII representation columns | |
buf.append(pad_between_hex_and_repr); | |
// (extended) ASCII representation column | |
if (extended) { | |
for (size_t i = 0; i < line_bytes; ++i) { | |
buf.append(ehexdump_extascii_table[byte_data[line_offset + i]]); | |
} | |
} else { | |
for (size_t i = 0; i < line_bytes; ++i) { | |
buf.push_back(classic_hexdump_repr(byte_data[line_offset + i])); | |
} | |
} | |
buf.append(line_end); | |
} | |
if (ehexdump_debug_reserve) { | |
fmt::print(::stderr, "max_total_sz: {:d} buf capacity: {:d} size: {:d}\n", max_total_sz, | |
buf.capacity(), buf.size()); | |
} | |
return buf; | |
} | |
template <typename T> | |
GSNR_EXPORT fmt::memory_buffer ehexdump_mb(const std::span<T> &span, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
return ehexdump_mb(static_cast<const void *>(span.data()), span.size_bytes(), extended, offset, | |
caps, width); | |
} | |
template <typename T, size_t N> | |
GSNR_EXPORT fmt::memory_buffer ehexdump_mb(const std::span<T, N> &span, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
return ehexdump_mb(static_cast<const void *>(span.data()), span.size_bytes(), extended, offset, | |
caps, width); | |
} | |
template <typename T> | |
requires(!std::is_pointer_v<T>) | |
GSNR_EXPORT fmt::memory_buffer ehexdump_mb(const T &data, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
return ehexdump_mb(static_cast<const void *>(&data), sizeof(data), extended, offset, caps, | |
width); | |
} | |
GSNR_EXPORT std::string ehexdump(const void *data, const size_t sz, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
auto mb = ehexdump_mb(data, sz, extended, offset, caps, width); | |
const auto mb_sz = mb.size(); | |
assume(mb_sz < std::string{}.max_size()); | |
// elide SSO codegen | |
assume(mb_sz > sizeof(std::string{})); | |
return {mb.data(), mb_sz}; | |
} | |
template <typename T> | |
GSNR_EXPORT std::string ehexdump(const std::span<T> &span, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
auto mb = ehexdump_mb(span, extended, offset, caps, width); | |
const auto mb_sz = mb.size(); | |
assume(mb_sz < std::string{}.max_size()); | |
// elide SSO codegen | |
assume(mb_sz > sizeof(std::string{})); | |
return {mb.data(), mb_sz}; | |
} | |
template <typename T, size_t N> | |
GSNR_EXPORT std::string ehexdump(const std::span<T, N> &span, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
auto mb = ehexdump_mb(span, extended, offset, caps, width); | |
const auto mb_sz = mb.size(); | |
assume(mb_sz < std::string{}.max_size()); | |
// elide SSO codegen | |
assume(mb_sz > sizeof(std::string{})); | |
return {mb.data(), mb_sz}; | |
} | |
template <typename T> | |
requires(!std::is_pointer_v<T>) | |
GSNR_EXPORT std::string ehexdump(const T &data, const bool extended = true, | |
const bool offset = true, const bool caps = false, | |
const size_t width = 0) { | |
auto mb = ehexdump_mb(data, extended, offset, caps, width); | |
const auto mb_sz = mb.size(); | |
assume(mb_sz < std::string{}.max_size()); | |
// elide SSO codegen | |
assume(mb_sz > sizeof(std::string{})); | |
return {mb.data(), mb_sz}; | |
} | |
} // namespace gsnr | |
namespace fmt { | |
// Struct to hold formatting options for the ehexdump formatter | |
struct ehexdump_specs { | |
uint32_t data_ = 0; | |
enum : uint32_t { | |
width_mask = 0x000000FFu, // First 8 bits for width (4-256 - 4) | |
precision_mask = 0x07FFFF00u, // Next 19 bits for precision (0-524287) | |
not_offset_flag = 0x08000000u, // Bit 27 for not_offset | |
uppercase_flag = 0x10000000u, // Bit 28 for uppercase | |
not_extended_flag = 0x20000000u, // Bit 29 for non-extended Unicode form (`#`) | |
dynamic_width_flag = 0x40000000u, // Bit 30 for dynamic width | |
dynamic_precision_flag = 0x80000000u, // Bit 31 for dynamic precision | |
}; | |
static constexpr uint32_t width_min = 4; | |
static constexpr uint32_t width_max = 256; | |
static constexpr uint32_t precision_max = 524287; | |
static constexpr uint32_t width_shift = 0; | |
static constexpr uint32_t precision_shift = 8; | |
constexpr void set_width(const uint32_t w) { | |
fmt::print("specs::set_width({})\n", w); | |
if (w < width_min) { | |
throw format_error("Width < 4 in format string"); | |
} else if (w > width_max) { | |
throw format_error("Width > 256 in format string"); | |
} else { | |
data_ = (data_ & ~width_mask) | | |
(((w - width_min + 1) & (width_mask >> width_shift)) << width_shift); | |
} | |
} | |
constexpr uint32_t width() const { | |
const auto w = ((data_ & width_mask) >> width_shift) + (width_min - 1); | |
return w < width_min ? 0 : w; | |
} | |
constexpr bool set_precision(const size_t p) { | |
fmt::print("specs::set_precision({})\n", p); | |
if (p > precision_max) { | |
fmt::print("p ({}) > precision_max ({}), setting dynamic_precision_flag\n", p, | |
precision_max); | |
set_dynamic_precision(true); // Mark precision as dynamic if it's too large | |
return true; | |
} else { | |
data_ = (data_ & ~precision_mask) | | |
((p & (precision_mask >> precision_shift)) << precision_shift); | |
} | |
return false; | |
} | |
constexpr size_t precision() const { | |
return (data_ & precision_mask) >> precision_shift; | |
} | |
constexpr void set_not_offset(const bool not_offset) { | |
fmt::print("specs::set_not_offset({})\n", not_offset); | |
if (not_offset) { | |
data_ |= not_offset_flag; | |
} else { | |
data_ &= ~not_offset_flag; | |
} | |
} | |
constexpr bool is_not_offset() const { | |
return (data_ & not_offset_flag) != 0; | |
} | |
constexpr void set_uppercase(const bool uppercase) { | |
fmt::print("specs::set_uppercase({})\n", uppercase); | |
if (uppercase) { | |
data_ |= uppercase_flag; | |
} else { | |
data_ &= ~uppercase_flag; | |
} | |
} | |
constexpr bool is_uppercase() const { | |
return (data_ & uppercase_flag) != 0; | |
} | |
constexpr void set_not_extended(const bool not_extended) { | |
fmt::print("specs::set_not_extended({})\n", not_extended); | |
if (not_extended) { | |
data_ |= not_extended_flag; | |
} else { | |
data_ &= ~not_extended_flag; | |
} | |
} | |
constexpr bool is_not_extended() const { | |
return (data_ & not_extended_flag) != 0; | |
} | |
constexpr void set_dynamic_width(const bool dynamic_width) { | |
fmt::print("specs::set_dynamic_width({})\n", dynamic_width); | |
if (dynamic_width) { | |
data_ |= dynamic_width_flag; | |
} else { | |
data_ &= ~dynamic_width_flag; | |
} | |
} | |
constexpr bool is_dynamic_width() const { | |
return (data_ & dynamic_width_flag) != 0; | |
} | |
constexpr void set_dynamic_precision(const bool dynamic_precision) { | |
fmt::print("specs::set_dynamic_precision({})\n", dynamic_precision); | |
if (dynamic_precision) { | |
data_ |= dynamic_precision_flag; | |
} else { | |
data_ &= ~dynamic_precision_flag; | |
} | |
} | |
constexpr bool is_dynamic_precision() const { | |
return (data_ & dynamic_precision_flag) != 0; | |
} | |
}; | |
// Base formatter to handle common parsing logic | |
struct ehexdump_formatter_base { | |
ehexdump_specs specs; | |
uint32_t dynamic_width = 0; | |
size_t dynamic_precision = std::numeric_limits<size_t>::max(); | |
int dynamic_width_arg_id = -1; | |
int dynamic_precision_arg_id = -1; | |
bool custom_ehexdump = false; | |
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { | |
auto it = ctx.begin(); | |
const auto end = ctx.end(); | |
fmt::print("ehexdump_formatter_base::parse() begin of \"{}\"\n", std::string_view{it, end}); | |
// Parse extended flag | |
if (it != end && *it == '#') { | |
specs.set_not_extended(true); | |
++it; | |
} | |
// Parse not_offset flag | |
if (it != end && *it == '0') { | |
specs.set_not_offset(true); | |
++it; | |
} | |
if (it != end) { | |
if (*it >= '0' && *it <= '9') { | |
// Parse static width | |
uint32_t w = 0; | |
while (it != end && (*it >= '0' && *it <= '9')) { | |
w = w * 10 + (*it - '0'); | |
++it; | |
} | |
specs.set_width(w); | |
} else if (*it == '{') { | |
// Parse dynamic width | |
dynamic_width_arg_id = ctx.next_arg_id(); | |
it = std::find(it, end, '}'); | |
++it; | |
fmt::print("setting dynamic_width_arg_id: {}\n", dynamic_width_arg_id); | |
specs.set_dynamic_width(true); | |
} | |
} | |
if (it != end) { | |
if (*it == '.') { | |
// Parse precision | |
++it; | |
if (it == end) { | |
throw format_error("Expected precision after '.'"); | |
} | |
if (*it == '{') { | |
// Handle dynamic precision | |
dynamic_precision_arg_id = ctx.next_arg_id(); | |
it = std::find(it, end, '}'); | |
++it; | |
fmt::print("setting dynamic_precision_arg_id: {}\n", dynamic_precision_arg_id); | |
specs.set_dynamic_precision(true); | |
} else if (*it >= '0' && *it <= '9') { | |
fmt::print("static precision parse start\n"); | |
size_t p = 0; | |
while (it != end && (*it >= '0' && *it <= '9')) { | |
p = p * 10 + (*it - '0'); | |
++it; | |
} | |
if (p == 0) { | |
throw format_error("Expected non-zero precision"); | |
} | |
fmt::print("setting precision: {}\n", p); | |
if (specs.set_precision(p)) { | |
// static precision too large to fit in specs | |
dynamic_precision = p; | |
} | |
} else { | |
throw format_error("Expected precision after '.'"); | |
} | |
} | |
} | |
if (it != end) { | |
// Handle type specifier (h or H) | |
if (*it == 'h') { | |
custom_ehexdump = true; | |
++it; | |
} else if (*it == 'H') { | |
custom_ehexdump = true; | |
specs.set_uppercase(true); | |
++it; | |
} | |
} | |
fmt::print("ehexdump_formatter_base::parse() end of \"{}\"\n", std::string_view{it, end}); | |
// FIXME: if !custom_ehexdump return begin? | |
return it; | |
}; | |
}; | |
struct ehexdump_formatter_base_stub { | |
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { | |
return ctx.begin(); | |
} | |
}; | |
} // namespace fmt | |
namespace gsnr { | |
// An argument visitor that returns true iff arg is a zero integer. | |
struct int_visitor { | |
template <typename T, FMT_ENABLE_IF(std::is_integral_v<T>)> auto operator()(T value) -> int { | |
return value; | |
} | |
template <typename T, FMT_ENABLE_IF(!std::is_integral_v<T>)> auto operator()(T) -> int { | |
return -1; | |
} | |
}; | |
template <typename T> struct generic_ehexdump_formatter : fmt::ehexdump_formatter_base { | |
constexpr auto format(const T &f, fmt::format_context &ctx) const -> decltype(ctx.out()) { | |
fmt::print("sizeof(T): {} specs.data_: {:#08x} custom_ehexdump: {} dynamic_precision: {} " | |
"dynamic_precision_arg_id: {} dynamic_width: {} dynamic_width_arg_id: {} " | |
"precision(): {} width(): {} is_uppercase(): {} is_not_extended(): {} " | |
"is_not_offset(): {} is_dynamic_precision(): {} is_dynamic_width(): {}\n", | |
sizeof(T), specs.data_, custom_ehexdump, dynamic_precision, | |
dynamic_precision_arg_id, dynamic_width, dynamic_width_arg_id, specs.precision(), | |
specs.width(), specs.is_uppercase(), specs.is_not_extended(), | |
specs.is_not_offset(), specs.is_dynamic_precision(), specs.is_dynamic_width()); | |
if (custom_ehexdump) { | |
const auto d = reinterpret_cast<const uint8_t *>(&f); | |
const auto sz = sizeof(T); | |
if (!specs.is_dynamic_precision() && !specs.is_dynamic_width()) { | |
const auto mb = gsnr::ehexdump_mb(d, specs.precision() ? specs.precision() : sz, | |
!specs.is_not_extended(), !specs.is_not_offset(), | |
specs.is_uppercase(), specs.width()); | |
fmt::detail::copy<typename decltype(mb)::value_type()>( | |
mb.data(), mb.data() + mb.size(), ctx.out()); | |
return ctx.out(); | |
} else if (specs.is_dynamic_precision() && specs.is_dynamic_width()) { | |
const auto mb = gsnr::ehexdump_mb(d, dynamic_precision ? dynamic_precision : sz, | |
!specs.is_not_extended(), !specs.is_not_offset(), | |
specs.is_uppercase(), dynamic_width); | |
fmt::detail::copy<typename decltype(mb)::value_type()>( | |
mb.data(), mb.data() + mb.size(), ctx.out()); | |
return ctx.out(); | |
} else if (specs.is_dynamic_precision() && !specs.is_dynamic_width()) { | |
const auto precision_arg = ctx.arg(dynamic_precision_arg_id); | |
const auto precision_val = | |
dynamic_precision_arg_id >= 0 | |
? ctx.arg(dynamic_precision_arg_id).visit(int_visitor()) | |
: dynamic_precision; | |
fmt::print("dynamic_precision_arg_id: {} dynamic_precision_arg: {}\n", | |
dynamic_precision_arg_id, precision_val); | |
const auto mb = | |
gsnr::ehexdump_mb(d, std::min(precision_val, sz), !specs.is_not_extended(), | |
!specs.is_not_offset(), specs.is_uppercase(), specs.width()); | |
fmt::detail::copy<typename decltype(mb)::value_type()>( | |
mb.data(), mb.data() + mb.size(), ctx.out()); | |
return ctx.out(); | |
} else if (!specs.is_dynamic_precision() && specs.is_dynamic_width()) { | |
const auto width_arg = ctx.arg(dynamic_width_arg_id); | |
const auto width_val = width_arg.visit(int_visitor()); | |
fmt::print("dynamic_width_arg_id: {} dynamic_width_arg: {}\n", dynamic_width_arg_id, | |
width_val); | |
const auto mb = gsnr::ehexdump_mb(d, specs.precision() ? specs.precision() : sz, | |
!specs.is_not_extended(), !specs.is_not_offset(), | |
specs.is_uppercase(), width_val); | |
fmt::detail::copy<typename decltype(mb)::value_type()>( | |
mb.data(), mb.data() + mb.size(), ctx.out()); | |
return ctx.out(); | |
} else { | |
assert(!"dont reach me dynamic precision/width"); | |
} | |
} else { | |
// return fmt::format_to(ctx.out(), f); | |
assert(!"not custom_ehexdump"); | |
} | |
} | |
}; | |
} // namespace gsnr | |
struct Foo { | |
uint32_t a; | |
uint8_t b; | |
}; | |
struct Bar { | |
char fourcc[4]; | |
void *ptr; | |
}; | |
namespace fmt { | |
template <> struct formatter<Foo> : gsnr::generic_ehexdump_formatter<Foo> {}; | |
template <> struct formatter<Bar> : gsnr::generic_ehexdump_formatter<Bar> {}; | |
} // namespace fmt |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment