Skip to content

Commit

Permalink
feat(serialization): add DebugSerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Nov 4, 2023
1 parent 39f4c11 commit 73f607b
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 0 deletions.
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ set(CUBOS_CORE_SOURCE
"src/cubos/core/data/fs/standard_archive.cpp"
"src/cubos/core/data/fs/embedded_archive.cpp"
"src/cubos/core/data/ser/serializer.cpp"
"src/cubos/core/data/ser/debug.cpp"

"src/cubos/core/io/window.cpp"
"src/cubos/core/io/cursor.cpp"
Expand Down
36 changes: 36 additions & 0 deletions core/include/cubos/core/data/ser/debug.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// @file
/// @brief Class @ref cubos::core::data::DebugSerializer.
/// @ingroup core-data-ser

#pragma once

#include <cubos/core/data/ser/serializer.hpp>
#include <cubos/core/memory/stream.hpp>

namespace cubos::core::data
{
/// @brief Serializer implementation which prints the given data to a stream in a human-readable
/// format not meant to be parsed.
/// @ingroup core-data-ser
class DebugSerializer : public Serializer
{
public:
/// @brief Constructs.
/// @param stream Stream to serialize to.
DebugSerializer(memory::Stream& stream);

/// @brief Sets whether to pretty-print the output.
/// @param pretty Whether to pretty-print the output.
void pretty(bool pretty);

protected:
bool decompose(const reflection::Type& type, const void* value) override;

private:
void separate(bool first);

memory::Stream& mStream; ///< Stream to serialize to.
bool mPretty{false}; ///< Whether to pretty-print the output.
int mLevel{0}; ///< Indentation level.
};
} // namespace cubos::core::data
175 changes: 175 additions & 0 deletions core/src/cubos/core/data/ser/debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#include <cubos/core/data/ser/debug.hpp>
#include <cubos/core/log.hpp>
#include <cubos/core/reflection/external/primitives.hpp>
#include <cubos/core/reflection/external/string.hpp>
#include <cubos/core/reflection/traits/array.hpp>
#include <cubos/core/reflection/traits/dictionary.hpp>
#include <cubos/core/reflection/traits/fields.hpp>
#include <cubos/core/reflection/traits/string_conversion.hpp>
#include <cubos/core/reflection/type.hpp>

using cubos::core::data::DebugSerializer;
using cubos::core::reflection::ArrayTrait;
using cubos::core::reflection::DictionaryTrait;
using cubos::core::reflection::FieldsTrait;
using cubos::core::reflection::StringConversionTrait;
using cubos::core::reflection::Type;

#define AUTO_HOOK(type, ...) \
this->hook<type>([](Serializer& ser, const Type&, const void* value) { \
auto& debug = static_cast<DebugSerializer&>(ser); \
const auto& typed = *static_cast<const type*>(value); \
__VA_ARGS__; \
return true; \
})

DebugSerializer::DebugSerializer(memory::Stream& stream)
: mStream(stream)
{
AUTO_HOOK(bool, debug.mStream.print(typed ? "true" : "false"));
AUTO_HOOK(char, {
debug.mStream.put('\'');
debug.mStream.put(typed);
debug.mStream.put('\'');
});
AUTO_HOOK(int8_t, debug.mStream.print(typed));
AUTO_HOOK(int16_t, debug.mStream.print(typed));
AUTO_HOOK(int32_t, debug.mStream.print(typed));
AUTO_HOOK(int64_t, debug.mStream.print(typed));
AUTO_HOOK(uint8_t, debug.mStream.print(typed));
AUTO_HOOK(uint16_t, debug.mStream.print(typed));
AUTO_HOOK(uint32_t, debug.mStream.print(typed));
AUTO_HOOK(uint64_t, debug.mStream.print(typed));
AUTO_HOOK(float, debug.mStream.print(typed));
AUTO_HOOK(double, debug.mStream.print(typed));
AUTO_HOOK(std::string, debug.mStream.printf("\"{}\"", typed));
}

void DebugSerializer::pretty(bool pretty)
{
mPretty = pretty;
}

void DebugSerializer::separate(bool first)
{
if (mPretty)
{
if (!first)
{
mStream.put(',');
}

mStream.put('\n');
for (int i = 0; i < mLevel * 2; ++i)
{
mStream.put(' ');
}
}
else if (!first)
{
mStream.print(", ");
}
}

bool DebugSerializer::decompose(const Type& type, const void* value)
{
bool success = true;
bool first = true;

if (type.has<DictionaryTrait>())
{
const auto& trait = type.get<DictionaryTrait>();

mStream.put('{');
mLevel += 1;

for (auto [entryKey, entryValue] : trait.view(value))
{
this->separate(first);
first = false;

if (!this->write(trait.keyType(), entryKey))
{
CUBOS_WARN("Could not write dictionary key");
mStream.put('?');
success = false;
}

mStream.print(": ");

if (!this->write(trait.valueType(), entryValue))
{
CUBOS_WARN("Could not write dictionary value");
mStream.put('?');
success = false;
}
}

mLevel -= 1;
this->separate(true);
mStream.put('}');
}
else if (type.has<ArrayTrait>())
{
const auto& trait = type.get<ArrayTrait>();

mStream.put('[');
mLevel += 1;

for (const auto* element : trait.view(value))
{
this->separate(first);
first = false;

if (!this->write(trait.elementType(), element))
{
CUBOS_WARN("Could not write array element");
mStream.put('?');
success = false;
}
}

mLevel -= 1;
this->separate(true);
mStream.put(']');
}
else if (type.has<FieldsTrait>())
{
const auto& trait = type.get<FieldsTrait>();

mStream.put('(');
mLevel += 1;

for (const auto& [field, fieldValue] : trait.view(value))
{
this->separate(first);
first = false;

mStream.printf("{}: ", field->name());

if (!this->write(field->type(), fieldValue))
{
CUBOS_WARN("Could not write field");
mStream.put('?');
success = false;
}
}

mLevel -= 1;
this->separate(true);
mStream.put(')');
}
else if (type.has<StringConversionTrait>())
{
const auto& trait = type.get<StringConversionTrait>();
mStream.print(trait.into(value));
}
else
{
CUBOS_WARN("Type {} doesn't have any of the supported traits", type.name());
mStream.put('?');
success = false;
}

return success;
}
1 change: 1 addition & 0 deletions core/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_executable(
data/fs/embedded_archive.cpp
data/fs/standard_archive.cpp
data/fs/file_system.cpp
data/ser/debug.cpp
data/context.cpp

memory/any_value.cpp
Expand Down
93 changes: 93 additions & 0 deletions core/tests/data/ser/debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include <doctest/doctest.h>
#include <glm/vec3.hpp>

#include <cubos/core/data/ser/debug.hpp>
#include <cubos/core/memory/buffer_stream.hpp>
#include <cubos/core/reflection/external/glm.hpp>
#include <cubos/core/reflection/external/map.hpp>
#include <cubos/core/reflection/external/primitives.hpp>
#include <cubos/core/reflection/external/string.hpp>
#include <cubos/core/reflection/external/uuid.hpp>
#include <cubos/core/reflection/external/vector.hpp>

#include "../utils.hpp"

using cubos::core::data::DebugSerializer;
using cubos::core::data::Serializer;
using cubos::core::memory::BufferStream;
using cubos::core::memory::SeekOrigin;
using cubos::core::reflection::Type;

namespace
{
struct Empty
{
CUBOS_REFLECT
{
return Type::create("Empty");
}
};
} // namespace

#define AUTO_TEST(type, value, expected, success) \
stream.seek(0, SeekOrigin::Begin); \
CHECK(ser.write<type>(value) == (success)); \
stream.put('\0'); \
stream.seek(0, SeekOrigin::Begin); \
CHECK(dump(stream) == (expected));

#define AUTO_TEST_SPLIT(type, value, expectedUgly, expectedPretty, success) \
if (pretty) \
{ \
AUTO_TEST(type, value, expectedPretty, success); \
} \
else \
{ \
AUTO_TEST(type, value, expectedUgly, success); \
}

TEST_CASE("data::DebugSerializer")
{
BufferStream stream{};
DebugSerializer ser{stream};

bool pretty = false;
PARAMETRIZE_TRUE_OR_FALSE("pretty", pretty);
ser.pretty(pretty);

AUTO_TEST(bool, false, "false", true);
AUTO_TEST(bool, true, "true", true);
AUTO_TEST(char, 'a', "'a'", true);
AUTO_TEST(int8_t, -120, "-120", true);
AUTO_TEST(int16_t, 30000, "30000", true);
AUTO_TEST(int32_t, -2000000000, "-2000000000", true);
AUTO_TEST(int64_t, 9000000000000000000, "9000000000000000000", true);
AUTO_TEST(uint8_t, 120, "120", true);
AUTO_TEST(uint16_t, 60000, "60000", true);
AUTO_TEST(uint32_t, 4000000000, "4000000000", true);
AUTO_TEST(uint64_t, 18000000000000000000ULL, "18000000000000000000", true);
AUTO_TEST(float, 1.5F, "1.5000", true);
AUTO_TEST(double, 1.F, "1.0000", true);
AUTO_TEST(std::string, "Hello, world!", "\"Hello, world!\"", true);
AUTO_TEST(uuids::uuid, *uuids::uuid::from_string("f7063cd4-de44-47b5-b0a9-f0e7a558c9e5"),
"f7063cd4-de44-47b5-b0a9-f0e7a558c9e5", true);

std::map<std::string, bool> map{{"false", false}, {"true", true}};
AUTO_TEST_SPLIT(decltype(map), map, "{\"false\": false, \"true\": true}",
"{\n \"false\": false,\n \"true\": true\n}", true);

std::vector<int> vector{1, 2, 3};
AUTO_TEST_SPLIT(decltype(vector), vector, "[1, 2, 3]", "[\n 1,\n 2,\n 3\n]", true);

glm::tvec3<int> vec3{1, 2, 3};
AUTO_TEST_SPLIT(decltype(vec3), vec3, "(x: 1, y: 2, z: 3)", "(\n x: 1,\n y: 2,\n z: 3\n)", true);

ser.hook<std::string>([](Serializer&, const Type&, const void*) { return false; });
ser.hook<bool>([](Serializer&, const Type&, const void*) { return false; });
ser.hook<int>([](Serializer&, const Type&, const void*) { return false; });
AUTO_TEST_SPLIT(decltype(map), map, "{?: ?, ?: ?}", "{\n ?: ?,\n ?: ?\n}", false);
AUTO_TEST_SPLIT(decltype(vector), vector, "[?, ?, ?]", "[\n ?,\n ?,\n ?\n]", false);
AUTO_TEST_SPLIT(decltype(vec3), vec3, "(x: ?, y: ?, z: ?)", "(\n x: ?,\n y: ?,\n z: ?\n)", false);

AUTO_TEST_SPLIT(Empty, Empty{}, "?", "?", false);
}

0 comments on commit 73f607b

Please sign in to comment.