From 78ebeaaa7b349d7ef2f7612b1a316c0795d6dbab Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 6 Mar 2021 09:28:14 +0100 Subject: [PATCH] Add Gdt parsing --- src/ObjCommon/Obj/GDT/GDT.cpp | 321 +++++++++++++++++++++++ src/ObjCommon/Obj/GDT/GDT.h | 18 ++ src/ObjCommon/Obj/GDT/GDTEntry.cpp | 19 ++ src/ObjCommon/Obj/GDT/GDTEntry.h | 17 ++ src/ObjCommon/Obj/GDT/GDTProperty.cpp | 0 src/ObjCommon/Obj/GDT/GDTProperty.h | 0 src/ObjCommon/Obj/GDT/GDTPropertyType.h | 0 src/ObjCommon/Obj/Gdt/GdtVersion.cpp | 12 + src/ObjCommon/Obj/Gdt/GdtVersion.h | 13 + test/ObjCommonTests/Obj/Gdt/GdtTests.cpp | 240 +++++++++++++++++ 10 files changed, 640 insertions(+) delete mode 100644 src/ObjCommon/Obj/GDT/GDTProperty.cpp delete mode 100644 src/ObjCommon/Obj/GDT/GDTProperty.h delete mode 100644 src/ObjCommon/Obj/GDT/GDTPropertyType.h create mode 100644 src/ObjCommon/Obj/Gdt/GdtVersion.cpp create mode 100644 src/ObjCommon/Obj/Gdt/GdtVersion.h create mode 100644 test/ObjCommonTests/Obj/Gdt/GdtTests.cpp diff --git a/src/ObjCommon/Obj/GDT/GDT.cpp b/src/ObjCommon/Obj/GDT/GDT.cpp index e69de29b..f56bcfd5 100644 --- a/src/ObjCommon/Obj/GDT/GDT.cpp +++ b/src/ObjCommon/Obj/GDT/GDT.cpp @@ -0,0 +1,321 @@ +#include "Gdt.h" + +#include + +class GdtConst +{ +public: + static constexpr const char* VERSION_ENTRY_NAME = "version"; + static constexpr const char* VERSION_ENTRY_GDF = "version.gdf"; + static constexpr const char* VERSION_KEY_GAME = "game"; + static constexpr const char* VERSION_KEY_VERSION = "version"; +}; + +class GdtReader +{ + std::istream& m_stream; + char m_char; + bool m_peeked; + int m_line; + + void PrintError(const std::string& message) const + { + std::cout << "GDT Error at line " << m_line << ": " << message << "\n"; + } + + int PeekChar() + { + if (m_peeked) + return m_char; + + int c; + do + { + c = m_stream.get(); + } + while (isspace(c)); + + m_peeked = true; + m_char = c; + return c; + } + + int NextChar() + { + if (m_peeked) + { + m_peeked = false; + return m_char; + } + + int c; + do + { + c = m_stream.get(); + } + while (isspace(c)); + + return c; + } + + bool ReadStringContent(std::string& str) + { + std::ostringstream ss; + + if (NextChar() != '"') + { + PrintError("Expected string opening tag"); + return false; + } + + auto c = m_stream.get(); + while (c != '"' && c != '\n' && c != EOF) + { + ss << static_cast(c); + c = m_stream.get(); + } + + if (c == '"') + { + str = ss.str(); + return true; + } + + return false; + } + + static GdtEntry* GetEntryByName(const Gdt& gdt, const std::string& name) + { + for (const auto& entry : gdt.m_entries) + { + if (entry->m_name == name) + return entry.get(); + } + + return nullptr; + } + + bool ReadProperties(GdtEntry& entry) + { + while (PeekChar() == '"') + { + std::string propertyKey; + std::string propertyValue; + + if (!ReadStringContent(propertyKey)) + return false; + + if (PeekChar() != '"') + { + PrintError("Expected value string"); + return false; + } + + if (!ReadStringContent(propertyValue)) + return false; + + entry.m_properties.emplace(std::move(propertyKey), std::move(propertyValue)); + } + + + if (NextChar() != '}') + { + PrintError("Expected closing tags"); + return false; + } + + return true; + } + + bool AddEntry(Gdt& gdt, GdtEntry& entry) const + { + if(entry.m_name == GdtConst::VERSION_ENTRY_NAME + && entry.m_gdf_name == GdtConst::VERSION_ENTRY_GDF) + { + auto foundEntry = entry.m_properties.find(GdtConst::VERSION_KEY_GAME); + if(foundEntry == entry.m_properties.end()) + { + PrintError("Version does not feature game property"); + return false; + } + gdt.m_version.m_game = foundEntry->second; + + foundEntry = entry.m_properties.find(GdtConst::VERSION_KEY_VERSION); + if (foundEntry == entry.m_properties.end()) + { + PrintError("Version does not feature version property"); + return false; + } + gdt.m_version.m_version = strtol(foundEntry->second.c_str(), nullptr, 0); + } + else + { + gdt.m_entries.emplace_back(std::make_unique(std::move(entry))); + } + + return true; + } + +public: + explicit GdtReader(std::istream& stream) + : m_stream(stream), + m_char(0), + m_peeked(false), + m_line(0) + { + } + + bool Read(Gdt& gdt) + { + if (NextChar() != '{') + { + PrintError("Expected opening tag"); + return false; + } + + while (PeekChar() == '"') + { + GdtEntry entry; + + if (!ReadStringContent(entry.m_name)) + { + PrintError("Failed to read string"); + return false; + } + + if (PeekChar() == '(') + { + NextChar(); + if (!ReadStringContent(entry.m_gdf_name)) + return false; + if (NextChar() != ')') + { + PrintError("Expected closing parenthesis"); + return false; + } + } + else if (PeekChar() == '[') + { + NextChar(); + std::string parentName; + if (!ReadStringContent(parentName)) + return false; + if (NextChar() != ']') + { + PrintError("Expected closing square brackets"); + return false; + } + entry.m_parent = GetEntryByName(gdt, parentName); + if (entry.m_parent == nullptr) + { + PrintError("Could not find parent with name"); + return false; + } + } + else + { + return false; + } + + if (NextChar() != '{') + { + PrintError("Expected opening tag for entries"); + return false; + } + + if (!ReadProperties(entry)) + return false; + + if (!AddEntry(gdt, entry)) + return false; + } + + + if (NextChar() != '}') + { + PrintError("Expected closing tags"); + return false; + } + + return true; + } +}; + +class GdtWriter +{ + std::ostream& m_stream; + unsigned m_intendation_level; + + void DoIntendation() const + { + for (auto i = 0u; i < m_intendation_level; i++) + m_stream << "\t"; + } + + void WriteVersion(const GdtVersion& gdtVersion) + { + GdtEntry versionEntry; + versionEntry.m_name = GdtConst::VERSION_ENTRY_NAME; + versionEntry.m_gdf_name = GdtConst::VERSION_ENTRY_GDF; + versionEntry.m_properties[GdtConst::VERSION_KEY_GAME] = gdtVersion.m_game; + versionEntry.m_properties[GdtConst::VERSION_KEY_VERSION] = std::to_string(gdtVersion.m_version); + + WriteEntry(versionEntry); + } + + void WriteEntry(const GdtEntry& entry) + { + DoIntendation(); + m_stream << "\"" << entry.m_name << "\" "; + if (entry.m_parent) + m_stream << "[ \"" << entry.m_parent->m_name << "\" ]\n"; + else + m_stream << "( \"" << entry.m_gdf_name << " )\n"; + m_stream << "{\n"; + + m_intendation_level++; + + for (const auto& [propertyKey, propertyValue] : entry.m_properties) + { + DoIntendation(); + m_stream << "\"" << propertyKey << "\" \"" << propertyValue << "\"\n"; + } + + m_intendation_level--; + m_stream << "}\n"; + } + +public: + explicit GdtWriter(std::ostream& stream) + : m_stream(stream), + m_intendation_level(0) + { + } + + void Write(const Gdt& gdt) + { + m_stream << "{\n"; + m_intendation_level++; + + WriteVersion(gdt.m_version); + for (const auto& entry : gdt.m_entries) + { + WriteEntry(*entry); + } + + m_intendation_level--; + m_stream << "}"; + } +}; + +bool Gdt::ReadFromStream(std::istream& stream) +{ + GdtReader reader(stream); + return reader.Read(*this); +} + +void Gdt::WriteToStream(std::ostream& stream) const +{ + GdtWriter writer(stream); + writer.Write(*this); +} diff --git a/src/ObjCommon/Obj/GDT/GDT.h b/src/ObjCommon/Obj/GDT/GDT.h index e69de29b..8c0b509f 100644 --- a/src/ObjCommon/Obj/GDT/GDT.h +++ b/src/ObjCommon/Obj/GDT/GDT.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "GdtEntry.h" +#include "GdtVersion.h" + +class Gdt +{ +public: + GdtVersion m_version; + std::vector> m_entries; + + bool ReadFromStream(std::istream& stream); + void WriteToStream(std::ostream& stream) const; +}; diff --git a/src/ObjCommon/Obj/GDT/GDTEntry.cpp b/src/ObjCommon/Obj/GDT/GDTEntry.cpp index e69de29b..9d2d2822 100644 --- a/src/ObjCommon/Obj/GDT/GDTEntry.cpp +++ b/src/ObjCommon/Obj/GDT/GDTEntry.cpp @@ -0,0 +1,19 @@ +#include "GdtEntry.h" + +GdtEntry::GdtEntry() + : m_parent(nullptr) +{ +} + +GdtEntry::GdtEntry(std::string name, std::string gdfName) + : m_name(std::move(name)), + m_gdf_name(std::move(gdfName)), + m_parent(nullptr) +{ +} + +GdtEntry::GdtEntry(std::string name, GdtEntry* parent) + : m_name(std::move(name)), + m_parent(parent) +{ +} diff --git a/src/ObjCommon/Obj/GDT/GDTEntry.h b/src/ObjCommon/Obj/GDT/GDTEntry.h index e69de29b..5463b5f5 100644 --- a/src/ObjCommon/Obj/GDT/GDTEntry.h +++ b/src/ObjCommon/Obj/GDT/GDTEntry.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class GdtEntry +{ +public: + std::string m_name; + std::string m_gdf_name; + GdtEntry* m_parent; + std::map m_properties; + + GdtEntry(); + GdtEntry(std::string name, std::string gdfName); + GdtEntry(std::string name, GdtEntry* parent); +}; diff --git a/src/ObjCommon/Obj/GDT/GDTProperty.cpp b/src/ObjCommon/Obj/GDT/GDTProperty.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ObjCommon/Obj/GDT/GDTProperty.h b/src/ObjCommon/Obj/GDT/GDTProperty.h deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ObjCommon/Obj/GDT/GDTPropertyType.h b/src/ObjCommon/Obj/GDT/GDTPropertyType.h deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ObjCommon/Obj/Gdt/GdtVersion.cpp b/src/ObjCommon/Obj/Gdt/GdtVersion.cpp new file mode 100644 index 00000000..42143c60 --- /dev/null +++ b/src/ObjCommon/Obj/Gdt/GdtVersion.cpp @@ -0,0 +1,12 @@ +#include "GdtVersion.h" + +GdtVersion::GdtVersion() + : m_version(0) +{ +} + +GdtVersion::GdtVersion(std::string game, const int version) + : m_game(std::move(game)), + m_version(version) +{ +} diff --git a/src/ObjCommon/Obj/Gdt/GdtVersion.h b/src/ObjCommon/Obj/Gdt/GdtVersion.h new file mode 100644 index 00000000..02adf3fe --- /dev/null +++ b/src/ObjCommon/Obj/Gdt/GdtVersion.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class GdtVersion +{ +public: + std::string m_game; + int m_version; + + GdtVersion(); + GdtVersion(std::string game, int version); +}; \ No newline at end of file diff --git a/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp b/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp new file mode 100644 index 00000000..f94b3f7d --- /dev/null +++ b/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp @@ -0,0 +1,240 @@ +#include + +#include + +#include "Obj/Gdt/Gdt.h" + +namespace obj::gdt +{ + TEST_CASE("Gdt: Ensure can parse simple gdt", "[gdt]") + { + std::string gdtString = "{\n" + "\t\"test_entry\" ( \"test.gdf\" )\n" + "\t{\n" + "\t\t\"testkey\" \"testvalue\"\n" + "\t\t\"test2key\" \"test2value\"\n" + "\t}\n" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_entries.size() == 1); + + { + const auto& entry = *gdt.m_entries[0]; + REQUIRE(entry.m_name == "test_entry"); + REQUIRE(entry.m_gdf_name == "test.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 2); + + REQUIRE(entry.m_properties.at("testkey") == "testvalue"); + REQUIRE(entry.m_properties.at("test2key") == "test2value"); + } + } + + TEST_CASE("Gdt: Ensure can parse compact gdt", "[gdt]") + { + std::string gdtString = "{" + R"("test_entry"("test.gdf"))" + "{" + R"("testkey""testvalue")" + R"("test2key""test2value")" + "}" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_entries.size() == 1); + + { + const auto& entry = *gdt.m_entries[0]; + REQUIRE(entry.m_name == "test_entry"); + REQUIRE(entry.m_gdf_name == "test.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 2); + + REQUIRE(entry.m_properties.at("testkey") == "testvalue"); + REQUIRE(entry.m_properties.at("test2key") == "test2value"); + } + } + + TEST_CASE("Gdt: Ensure can parse version section", "[gdt]") + { + std::string gdtString = "{" + R"("version" ( "version.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "1337")" + "}" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_entries.empty()); + REQUIRE(gdt.m_version.m_game == "t6"); + REQUIRE(gdt.m_version.m_version == 1337); + } + + TEST_CASE("Gdt: Ensure can parse version section and entries", "[gdt]") + { + std::string gdtString = "{" + R"("version" ( "version.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "1337")" + "}" + R"("test_entry" ( "another_test.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "420")" + "}" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_version.m_game == "t6"); + REQUIRE(gdt.m_version.m_version == 1337); + + REQUIRE(gdt.m_entries.size() == 1); + + { + const auto& entry = *gdt.m_entries[0]; + REQUIRE(entry.m_name == "test_entry"); + REQUIRE(entry.m_gdf_name == "another_test.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 2); + + REQUIRE(entry.m_properties.at("game") == "t6"); + REQUIRE(entry.m_properties.at("version") == "420"); + } + } + + TEST_CASE("Gdt: Ensure can parse multiple entries", "[gdt]") + { + std::string gdtString = "{" + R"("version" ( "version.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "1337")" + "}" + R"("test_entry" ( "another_test.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "420")" + "}" + R"("yet_another_entry" ( "super_kewl_asset_type.gdf" ))" + "{" + R"("name" "hello")" + R"("value" "asdf")" + R"("value2" "22")" + "}" + R"("final_entry" ( "quite_boring.gdf" ))" + "{" + R"("_HI_" "Hello World")" + "}" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_version.m_game == "t6"); + REQUIRE(gdt.m_version.m_version == 1337); + + REQUIRE(gdt.m_entries.size() == 3); + + { + const auto& entry = *gdt.m_entries[0]; + REQUIRE(entry.m_name == "test_entry"); + REQUIRE(entry.m_gdf_name == "another_test.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 2); + + REQUIRE(entry.m_properties.at("game") == "t6"); + REQUIRE(entry.m_properties.at("version") == "420"); + } + + { + const auto& entry = *gdt.m_entries[1]; + REQUIRE(entry.m_name == "yet_another_entry"); + REQUIRE(entry.m_gdf_name == "super_kewl_asset_type.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 3); + + REQUIRE(entry.m_properties.at("name") == "hello"); + REQUIRE(entry.m_properties.at("value") == "asdf"); + REQUIRE(entry.m_properties.at("value2") == "22"); + } + + { + const auto& entry = *gdt.m_entries[2]; + REQUIRE(entry.m_name == "final_entry"); + REQUIRE(entry.m_gdf_name == "quite_boring.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 1); + + REQUIRE(entry.m_properties.at("_HI_") == "Hello World"); + } + } + + TEST_CASE("Gdt: Ensure can parse entries with parent", "[gdt]") + { + std::string gdtString = "{" + R"("version" ( "version.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "1337")" + "}" + R"("test_entry" ( "another_test.gdf" ))" + "{" + R"("game" "t6")" + R"("version" "420")" + "}" + R"("yet_another_entry" [ "test_entry" ])" + "{" + R"("name" "hello")" + R"("value" "asdf")" + R"("value2" "22")" + "}" + "}"; + std::istringstream ss(gdtString); + + Gdt gdt; + REQUIRE(gdt.ReadFromStream(ss)); + + REQUIRE(gdt.m_version.m_game == "t6"); + REQUIRE(gdt.m_version.m_version == 1337); + + REQUIRE(gdt.m_entries.size() == 2); + + { + const auto& entry = *gdt.m_entries[0]; + REQUIRE(entry.m_name == "test_entry"); + REQUIRE(entry.m_gdf_name == "another_test.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 2); + + REQUIRE(entry.m_properties.at("game") == "t6"); + REQUIRE(entry.m_properties.at("version") == "420"); + } + + { + const auto& entry = *gdt.m_entries[1]; + REQUIRE(entry.m_name == "yet_another_entry"); + REQUIRE(entry.m_parent == gdt.m_entries[0].get()); + REQUIRE(entry.m_properties.size() == 3); + + REQUIRE(entry.m_properties.at("name") == "hello"); + REQUIRE(entry.m_properties.at("value") == "asdf"); + REQUIRE(entry.m_properties.at("value2") == "22"); + } + } +} \ No newline at end of file