#pragma once
#include <whiteout/models/wem/reflect.h>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <optional>
#include <ostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
namespace whiteout {
namespace models {
namespace wem {
class TextDumpVisitor {
public:
explicit TextDumpVisitor(std::ostream& out) : out_(out) {}
static constexpr bool kReading = false;
template <class T>
void field(const char* name, T& value) {
constexpr FieldKind kind = ClassifyField<T>();
if constexpr (kind == FieldKind::Enum) {
line(name, std::to_string(static_cast<std::int64_t>(
static_cast<std::underlying_type_t<T>>(value))));
} else if constexpr (kind == FieldKind::Bool) {
line(name, value ? "true" : "false");
} else if constexpr (kind == FieldKind::String) {
line(name, quote(value));
} else if constexpr (kind == FieldKind::Vector) {
dumpVector(name, value);
} else if constexpr (kind == FieldKind::Reflected) {
open(name, "");
value.reflect(*this);
close();
} else {
line(name, raw(value));
}
}
template <class T>
void optional(const char* name, std::optional<T>& value) {
if (!value.has_value()) {
line(name, "absent");
return;
}
field(name, *value);
}
template <class T>
void inlineList(const char* name, std::vector<T>& value) {
open(name, "[" + std::to_string(value.size()) + "]");
for (T& element : value) {
field("", element);
}
close();
}
template <class Alt, class Variant>
void chunkAlternative(const char* name, Variant& value, u32& slot) {
if (!std::holds_alternative<Alt>(value)) {
line(name, "skipped, preserved at slot " + std::to_string(slot));
return;
}
chunk(name, std::get<Alt>(value));
}
template <class T>
void chunk(const char* name, T& value) {
open(name, "");
value.reflect(*this);
close();
}
template <class C>
void count(const char* name, const C& container) {
line(name, "[" + std::to_string(container.size()) + "]");
}
TextDumpVisitor& since(u32) {
return *this;
}
private:
template <class T>
void dumpVector(const char* name, std::vector<T>& value) {
if constexpr (kHasReflect<T>) {
open(name, "[" + std::to_string(value.size()) + "]");
for (T& element : value) {
open("", "");
element.reflect(*this);
close();
}
close();
} else {
std::string text = "[" + std::to_string(value.size()) + "]";
const std::size_t shown = value.size() < 4 ? value.size() : 4;
for (std::size_t i = 0; i < shown; ++i) {
text += " " + raw(value[i]);
}
if (shown < value.size()) {
text += " ...";
}
line(name, text);
}
}
template <class T>
static std::string raw(const T& value);
static std::string quote(const std::string& text);
void line(const char* name, const std::string& value);
void open(const char* name, const std::string& suffix);
void close();
std::ostream& out_;
int depth_ = 0;
};
namespace detail {
template <class T>
inline constexpr bool kIsByteArray = false;
template <std::size_t N>
inline constexpr bool kIsByteArray<std::array<u8, N>> = true;
inline std::string Real(f32 value) {
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "%.9g", static_cast<double>(value));
return buffer;
}
inline std::string Hex(const void* data, std::size_t size) {
static const char* kDigits = "0123456789abcdef";
const auto* bytes = static_cast<const u8*>(data);
std::string out;
out.reserve(size * 2);
for (std::size_t i = 0; i < size; ++i) {
out.push_back(kDigits[bytes[i] >> 4]);
out.push_back(kDigits[bytes[i] & 0x0F]);
}
return out;
}
}
template <class T>
std::string TextDumpVisitor::raw(const T& value) {
if constexpr (std::is_floating_point_v<T>) {
return detail::Real(static_cast<f32>(value));
} else if constexpr (std::is_integral_v<T>) {
return std::to_string(static_cast<std::int64_t>(value));
} else if constexpr (detail::kIsByteArray<T>) {
return detail::Hex(&value, sizeof(T));
} else if constexpr (sizeof(T) % sizeof(f32) == 0) {
f32 parts[sizeof(T) / sizeof(f32)];
std::memcpy(parts, &value, sizeof(T));
std::string out;
for (const f32 part : parts) {
out += (out.empty() ? "" : " ");
out += detail::Real(part);
}
return out;
} else {
return detail::Hex(&value, sizeof(T));
}
}
template <class T>
void TextDump(std::ostream& out, const char* name, T& value) {
TextDumpVisitor visitor(out);
visitor.field(name, value);
}
} } }