From b85fa62861a4285e21d0411905eab4a13071648d Mon Sep 17 00:00:00 2001 From: Jessica James Date: Fri, 26 Nov 2021 11:23:39 -0600 Subject: [PATCH] Genericize object:: type names; add operator[] for array_type --- src/bot/console/console_command_context.cpp | 2 +- src/common/object.cpp | 62 +++++++--- src/common/parsers/json.cpp | 10 +- src/include/jessilib/object.hpp | 130 +++++++++++--------- src/test/object.cpp | 36 ++++-- 5 files changed, 152 insertions(+), 88 deletions(-) diff --git a/src/bot/console/console_command_context.cpp b/src/bot/console/console_command_context.cpp index 1a6c31a..e3cd05e 100644 --- a/src/bot/console/console_command_context.cpp +++ b/src/bot/console/console_command_context.cpp @@ -39,7 +39,7 @@ bool console_command_context::publicReply(const jessilib::io::formatted_message& /** Additional contextual details */ jessilib::object console_command_context::details() const { static jessilib::object s_details { - jessilib::object::map_t{ { "table", "console" } } + jessilib::object::map_type{ { "table", "console" } } }; return s_details; diff --git a/src/common/object.cpp b/src/common/object.cpp index 167858e..e52df93 100644 --- a/src/common/object.cpp +++ b/src/common/object.cpp @@ -29,12 +29,12 @@ object::object(object&& in_object) { } object::object(const char* in_str) - : m_value{ std::string{ in_str } } { + : m_value{ string_type{ in_str } } { // Empty ctor body } -object::object(const std::string_view& in_str) - : m_value{ std::string{ in_str.begin(), in_str.end() } } { +object::object(const string_view_type& in_str) + : m_value{ string_type{ in_str.begin(), in_str.end() } } { // Empty ctor body } @@ -52,7 +52,7 @@ size_t object::size() const { // Try array { - const array_t* array = std::get_if(&m_value); + const array_type* array = std::get_if(&m_value); if (array != nullptr) { return array->size(); } @@ -60,7 +60,7 @@ size_t object::size() const { // Try map { - const map_t* map = std::get_if(&m_value); + const map_type* map = std::get_if(&m_value); if (map != nullptr) { return map->size(); } @@ -70,11 +70,11 @@ size_t object::size() const { return 1; } -const object& object::operator[](const std::string& in_key) const { - auto result = std::get_if(&m_value); - if (result != nullptr) { - auto itr = result->find(in_key); - if (itr != result->end()) { +const object& object::operator[](const string_type& in_key) const { + auto map_ptr = std::get_if(&m_value); + if (map_ptr != nullptr) { + auto itr = map_ptr->find(in_key); + if (itr != map_ptr->end()) { return itr->second; } } @@ -83,14 +83,14 @@ const object& object::operator[](const std::string& in_key) const { return s_null_object; } -object& object::operator[](const std::string& in_key) { +object& object::operator[](const string_type& in_key) { if (null()) { - return m_value.emplace()[in_key]; + return m_value.emplace()[in_key]; } - auto result = std::get_if(&m_value); - if (result != nullptr) { - return result->operator[](in_key); + auto map_ptr = std::get_if(&m_value); + if (map_ptr != nullptr) { + return map_ptr->operator[](in_key); } static thread_local object s_null_object; @@ -98,4 +98,34 @@ object& object::operator[](const std::string& in_key) { return s_null_object; } -} // namespace jessilib \ No newline at end of file +const object& object::operator[](index_type in_index) const { + auto array_ptr = std::get_if(&m_value); + if (array_ptr != nullptr + && in_index < array_ptr->size()) { + return array_ptr->at(in_index); + } + + static const object s_null_object; + return s_null_object; +} + +object& object::operator[](index_type in_index) { + if (null()) { + m_value.emplace(); + } + + auto array_ptr = std::get_if(&m_value); + if (array_ptr != nullptr) { + while (array_ptr->size() <= in_index) { + array_ptr->emplace_back(); + } + + return array_ptr->at(in_index); + } + + static thread_local object s_null_object; + s_null_object.m_value.emplace(); + return s_null_object; +} + +} // namespace jessilib diff --git a/src/common/parsers/json.cpp b/src/common/parsers/json.cpp index 01858e5..429492e 100644 --- a/src/common/parsers/json.cpp +++ b/src/common/parsers/json.cpp @@ -406,7 +406,7 @@ object read_json_object(std::string_view& in_data) { advance_whitespace(in_data); // Build and populate result - object result{ object::map_t{} }; + object result{ object::map_type{} }; while (true) { if (in_data.empty()) { throw std::invalid_argument{ "Invalid JSON data; unexpected end of data when parsing object map" }; @@ -473,8 +473,8 @@ object json_parser::deserialize(std::string_view in_data) { } std::string json_parser::serialize(const object& in_object) { - static const object::array_t s_null_array; - static const object::map_t s_null_map; + static const object::array_type s_null_array; + static const object::map_type s_null_map; switch (in_object.type()) { case object::type::null: @@ -504,7 +504,7 @@ std::string json_parser::serialize(const object& in_object) { result = '['; // Serialize all objects in array - for (auto& obj : in_object.get(s_null_array)) { + for (auto& obj : in_object.get(s_null_array)) { result += json_parser::serialize(obj); result += ','; } @@ -523,7 +523,7 @@ std::string json_parser::serialize(const object& in_object) { result = '{'; // Serialize all objects in map - for (auto& item : in_object.get(s_null_map)) { + for (auto& item : in_object.get(s_null_map)) { result += make_json_string(item.first); result += ":"sv; result += json_parser::serialize(item.second); diff --git a/src/include/jessilib/object.hpp b/src/include/jessilib/object.hpp index a83f7d3..3cf625f 100644 --- a/src/include/jessilib/object.hpp +++ b/src/include/jessilib/object.hpp @@ -30,8 +30,11 @@ namespace jessilib { class object { public: - using array_t = std::vector; - using map_t = std::map; + using array_type = std::vector; + using string_type = std::string; + using string_view_type = std::string_view; + using map_type = std::map; + using index_type = std::size_t; /** is_backing */ @@ -59,21 +62,21 @@ public: }; template - struct is_backing::value>::type> { + struct is_backing::value>::type> { static constexpr bool value = true; - using type = std::string; + using type = string_type; }; template struct is_backing::value>::type> { static constexpr bool value = true; - using type = array_t; + using type = array_type; }; template struct is_backing::value>::type> { - static constexpr bool value = std::is_same::key_type, std::string>::value; - using type = map_t; + static constexpr bool value = std::is_same::key_type, string_type>::value; + using type = map_type; }; /** type */ @@ -83,7 +86,7 @@ public: boolean, integer, decimal, - string, // TODO: consider separating into 'binary' (std::vector) and 'text' (std::string) types + string, // TODO: consider separating into 'binary' (std::vector) and 'text' (string_type) types array, map }; @@ -98,7 +101,7 @@ public: template::type>::value && !is_sequence_container::type>::value - && (!is_associative_container::type>::value || std::is_same::type, map_t>::value)>::type* = nullptr> + && (!is_associative_container::type>::value || std::is_same::type, map_type>::value)>::type* = nullptr> object(T&& in_value) : m_value{ typename is_backing::type>::type{ std::forward(in_value) } } { // Empty ctor body @@ -108,7 +111,7 @@ public: typename std::enable_if::type>::value && !std::is_same::type, std::vector>::value>::type* = nullptr> object(T&& in_value) - : m_value{ array_t{ in_value.begin(), in_value.end() } } { + : m_value{ array_type{ in_value.begin(), in_value.end() } } { // Empty ctor body } @@ -116,8 +119,8 @@ public: template::type, std::vector>::value>::type* = nullptr> object(T&& in_value) - : m_value{ array_t{} } { - auto& array = std::get(m_value); + : m_value{ array_type{} } { + auto& array = std::get(m_value); array.reserve(in_value.size()); for (const auto& item : in_value) { @@ -125,32 +128,32 @@ public: } } - // std::unordered_map + // std::unordered_map template::type>::value - && std::is_same::type>::key_type, std::string>::value + && std::is_same::type>::key_type, string_type>::value && std::is_same::type>::value_type, object>::value>::type* = nullptr> object(T&& in_value) - : m_value{ map_t{ in_value.begin(), in_value.end() } } { + : m_value{ map_type{ in_value.begin(), in_value.end() } } { // Empty ctor body } - // Non-map_t associative containers (container) + // Non-map_type associative containers (container) template::type>::value - && (std::is_convertible::type>::key_type, std::string>::value - || std::is_convertible::type>::key_type, std::string_view>::value) + && (std::is_convertible::type>::key_type, string_type>::value + || std::is_convertible::type>::key_type, string_view_type>::value) && !std::is_same::type>::value_type, object>::value>::type* = nullptr> object(T&& in_value) - : m_value{ map_t{} } { - auto& map = std::get(m_value); + : m_value{ map_type{} } { + auto& map = std::get(m_value); for (auto& pair : in_value) { map.emplace(pair.first, pair.second); } } object(const char* in_str); - object(const std::string_view& in_str); + object(const string_view_type& in_str); // Comparison operators bool operator==(const object& rhs) const { @@ -189,8 +192,10 @@ public: return *this; } - const object& operator[](const std::string& in_key) const; - object& operator[](const std::string& in_key); + const object& operator[](const string_type& in_key) const; + object& operator[](const string_type& in_key); + const object& operator[](index_type in_index) const; + object& operator[](index_type in_index); /** Accessors */ @@ -226,9 +231,9 @@ public: // TODO: support other basic_string types template::value && std::is_convertible::type, T>::value>::type* = nullptr> + typename std::enable_if::value && std::is_convertible::type, T>::value>::type* = nullptr> T get(DefaultT&& in_default_value = {}) const { - const std::string* result = std::get_if(&m_value); + const string_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -238,9 +243,9 @@ public: // TODO: support other basic_string_view types template::value && std::is_same::type, std::string_view>::value>::type* = nullptr> + typename std::enable_if::value && std::is_same::type, string_view_type>::value>::type* = nullptr> T get(DefaultT&& in_default_value) const { - const std::string* result = std::get_if(&m_value); + const string_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -250,11 +255,11 @@ public: /** arrays */ - // reference getter (array_t) + // reference getter (array_type) template::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> const T& get(const T& in_default_value) const { - const array_t* result = std::get_if(&m_value); + const array_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -262,11 +267,11 @@ public: return in_default_value; } - // copy getter (array_t) + // copy getter (array_type) template::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> T get(T&& in_default_value = {}) const { - const array_t* result = std::get_if(&m_value); + const array_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -274,13 +279,13 @@ public: return std::move(in_default_value); } - // conversion getter (non-array_t) + // conversion getter (non-array_type) template::value && !std::is_same::value && std::is_same::type, T>::value>::type* = nullptr> + typename std::enable_if::value && !std::is_same::value && std::is_same::type, T>::value>::type* = nullptr> T get(DefaultT&& in_default_value = {}) const { using backing_t = typename is_sequence_container::type; - const array_t* array = std::get_if(&m_value); + const array_type* array = std::get_if(&m_value); if (array != nullptr) { T result; // Expand capacity to fit values (if possible) @@ -338,13 +343,13 @@ public: /** maps */ - // TODO: implement in a way that does not require exposing map_t + // TODO: implement in a way that does not require exposing map_type - // reference getter (map_t) + // reference getter (map_type) template::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> const T& get(const T& in_default_value) const { - const map_t* result = std::get_if(&m_value); + const map_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -352,11 +357,11 @@ public: return in_default_value; } - // copy getter (map_t) + // copy getter (map_type) template::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> T get(T&& in_default_value = {}) const { - const map_t* result = std::get_if(&m_value); + const map_type* result = std::get_if(&m_value); if (result != nullptr) { return *result; } @@ -364,7 +369,7 @@ public: return std::move(in_default_value); } - // TODO: conversion getter (non-map_t, i.e: unordered_map) + // TODO: conversion getter (non-map_type, i.e: unordered_map) /** set */ @@ -379,39 +384,39 @@ public: // string template::type, std::string>::value>::type* = nullptr> + typename std::enable_if::type, string_type>::value>::type* = nullptr> void set(T&& in_value) { - m_value.emplace(std::forward(in_value)); + m_value.emplace(std::forward(in_value)); } // string_view template::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> void set(const T& in_value) { - m_value.emplace(in_value.begin(), in_value.end()); + m_value.emplace(in_value.begin(), in_value.end()); } - // array_t + // array_type template::type, array_t>::value>::type* = nullptr> + typename std::enable_if::type, array_type>::value>::type* = nullptr> void set(T&& in_value) { - m_value.emplace(std::forward(in_value)); + m_value.emplace(std::forward(in_value)); } // is_sequence_container template::type>::value - && !std::is_same::type, array_t>::value + && !std::is_same::type, array_type>::value && !std::is_same::type, std::vector>::value>::type* = nullptr> void set(T&& in_value) { - m_value.emplace(in_value.begin(), in_value.end()); + m_value.emplace(in_value.begin(), in_value.end()); } // std::vector template::type, std::vector>::value>::type* = nullptr> void set(T&& in_value) { - auto& array = m_value.emplace(); + auto& array = m_value.emplace(); array.reserve(in_value.size()); for (const auto& item : in_value) { @@ -433,7 +438,7 @@ public: if constexpr (std::is_same::value) { return 0; } - else if constexpr (std::is_same::value) { + else if constexpr (std::is_same::value) { size_t result{}; for (auto& obj : value) { result += obj.hash(); @@ -441,10 +446,10 @@ public: return result; } - else if constexpr (std::is_same::value) { + else if constexpr (std::is_same::value) { size_t result{}; for (auto& pair : value) { - result += std::hash{}(pair.first); + result += std::hash{}(pair.first); } return result; @@ -457,7 +462,16 @@ public: private: using null_variant_t = void*; - std::variant m_value; + // TODO: consider replacing std::string with std::u8string (for strings) & std::vector (for data) + // TODO: consider some more generic mechanism for underlying string type, to support utf-16 & utf-32 strings + std::variant m_value; + + // TODO: note for future self, just use either first or last element in array_type to hold XML attributes + // OR, have every XML tag objects be a map, with all subobjects being in a "__values" array subobject or such + // For extra syntactical sugar, could have xml_object class extend object w/o additional members to allow arbitrary + // static_cast usage + // This may be a good justification for separate 'xml_config' and 'xml' parsers, as config files are easier to + // represent as a map, whereas an actual xml document is sequenced }; // object } // namespace jessilib diff --git a/src/test/object.cpp b/src/test/object.cpp index b074f78..d266782 100644 --- a/src/test/object.cpp +++ b/src/test/object.cpp @@ -67,8 +67,8 @@ TEST(ObjectTest, basic_has) { EXPECT_FALSE(obj.has()); EXPECT_FALSE(obj.has()); EXPECT_FALSE(obj.has()); - EXPECT_FALSE(obj.has()); - EXPECT_FALSE(obj.has()); + EXPECT_FALSE(obj.has()); + EXPECT_FALSE(obj.has()); } TEST(ObjectTest, basic_has_vector) { @@ -200,8 +200,8 @@ TEST(ObjectTest, basic_get) { EXPECT_EQ(obj.get(), double{}); EXPECT_EQ(obj.get(), long_double_t{}); EXPECT_EQ(obj.get(), std::string{}); - EXPECT_TRUE(obj.get().empty()); - EXPECT_TRUE(obj.get().empty()); + EXPECT_TRUE(obj.get().empty()); + EXPECT_TRUE(obj.get().empty()); } TEST(ObjectTest, basic_get_vector) { @@ -352,7 +352,7 @@ TEST(ObjectTest, basic_value_constructor) { OBJECT_BASIC_VALUE_CONSTRUCTOR_TEST(double); OBJECT_BASIC_VALUE_CONSTRUCTOR_TEST(long_double_t); OBJECT_BASIC_VALUE_CONSTRUCTOR_TEST(std::string); - OBJECT_BASIC_VALUE_CONSTRUCTOR_TEST(object::array_t); + OBJECT_BASIC_VALUE_CONSTRUCTOR_TEST(object::array_type); // const char* { @@ -503,7 +503,7 @@ TEST(ObjectTest, basic_set) { OBJECT_BASIC_SET_TEST(obj, double); OBJECT_BASIC_SET_TEST(obj, long_double_t); OBJECT_BASIC_SET_TEST(obj, std::string); - OBJECT_BASIC_SET_TEST(obj, object::array_t); + OBJECT_BASIC_SET_TEST(obj, object::array_type); // const char* obj.set(""); @@ -664,7 +664,7 @@ TEST(ObjectTest, basic_assignment_operator) { OBJECT_BASIC_ASSIGNMENT_OPERATOR_TEST(obj, double); OBJECT_BASIC_ASSIGNMENT_OPERATOR_TEST(obj, long_double_t); OBJECT_BASIC_ASSIGNMENT_OPERATOR_TEST(obj, std::string); - OBJECT_BASIC_ASSIGNMENT_OPERATOR_TEST(obj, object::array_t); + OBJECT_BASIC_ASSIGNMENT_OPERATOR_TEST(obj, object::array_type); // const char* obj = ""; @@ -805,7 +805,7 @@ TEST(ObjectTest, basic_assignment_operator_unordered_multiset) { /** basic_access_operator */ -TEST(ObjectTest, basic_access_operator) { +TEST(ObjectTest, basic_map_access_operator) { object obj; obj["test"] = 1234; @@ -821,6 +821,26 @@ TEST(ObjectTest, basic_access_operator) { EXPECT_EQ(obj["test2"].get(), 1234); } +TEST(ObjectTest, basic_array_access_operator) { + object obj; + + obj[0] = 1234; + EXPECT_EQ(obj.size(), 1); + EXPECT_EQ(obj[0].get(), 1234); + EXPECT_EQ(obj[1].get(), 0); + EXPECT_EQ(obj.size(), 2); + + obj[0] = 4567; + EXPECT_EQ(obj[0].get(), 4567); + EXPECT_EQ(obj[1].get(), 0); + EXPECT_EQ(obj.size(), 2); + + obj[1] = 1234; + EXPECT_EQ(obj[0].get(), 4567); + EXPECT_EQ(obj[1].get(), 1234); + EXPECT_EQ(obj.size(), 2); +} + /** end basic tests */ TEST(ObjectTest, set_bool) {