Browse Source

Genericize object:: type names; add operator[] for array_type

master
Jessica James 3 years ago
parent
commit
b85fa62861
  1. 2
      src/bot/console/console_command_context.cpp
  2. 62
      src/common/object.cpp
  3. 10
      src/common/parsers/json.cpp
  4. 130
      src/include/jessilib/object.hpp
  5. 36
      src/test/object.cpp

2
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;

62
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<array_t>(&m_value);
const array_type* array = std::get_if<array_type>(&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<map_t>(&m_value);
const map_type* map = std::get_if<map_type>(&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<map_t>(&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<map_type>(&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<map_t>()[in_key];
return m_value.emplace<map_type>()[in_key];
}
auto result = std::get_if<map_t>(&m_value);
if (result != nullptr) {
return result->operator[](in_key);
auto map_ptr = std::get_if<map_type>(&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
const object& object::operator[](index_type in_index) const {
auto array_ptr = std::get_if<array_type>(&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<array_type>();
}
auto array_ptr = std::get_if<array_type>(&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<null_variant_t>();
return s_null_object;
}
} // namespace jessilib

10
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<object::array_t>(s_null_array)) {
for (auto& obj : in_object.get<object::array_type>(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<object::map_t>(s_null_map)) {
for (auto& item : in_object.get<object::map_type>(s_null_map)) {
result += make_json_string(item.first);
result += ":"sv;
result += json_parser::serialize(item.second);

130
src/include/jessilib/object.hpp

@ -30,8 +30,11 @@ namespace jessilib {
class object {
public:
using array_t = std::vector<object>;
using map_t = std::map<std::string, object>;
using array_type = std::vector<object>;
using string_type = std::string;
using string_view_type = std::string_view;
using map_type = std::map<string_type, object>;
using index_type = std::size_t;
/** is_backing */
@ -59,21 +62,21 @@ public:
};
template<typename T>
struct is_backing<T, typename std::enable_if<std::is_same<T, std::string>::value>::type> {
struct is_backing<T, typename std::enable_if<std::is_same<T, string_type>::value>::type> {
static constexpr bool value = true;
using type = std::string;
using type = string_type;
};
template<typename T>
struct is_backing<T, typename std::enable_if<is_sequence_container<T>::value>::type> {
static constexpr bool value = true;
using type = array_t;
using type = array_type;
};
template<typename T>
struct is_backing<T, typename std::enable_if<is_associative_container<T>::value>::type> {
static constexpr bool value = std::is_same<typename is_associative_container<T>::key_type, std::string>::value;
using type = map_t;
static constexpr bool value = std::is_same<typename is_associative_container<T>::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<std::byte>) and 'text' (std::string) types
string, // TODO: consider separating into 'binary' (std::vector<std::byte>) and 'text' (string_type) types
array,
map
};
@ -98,7 +101,7 @@ public:
template<typename T,
typename std::enable_if<is_backing<typename std::decay<T>::type>::value
&& !is_sequence_container<typename std::decay<T>::type>::value
&& (!is_associative_container<typename std::decay<T>::type>::value || std::is_same<typename remove_cvref<T>::type, map_t>::value)>::type* = nullptr>
&& (!is_associative_container<typename std::decay<T>::type>::value || std::is_same<typename remove_cvref<T>::type, map_type>::value)>::type* = nullptr>
object(T&& in_value)
: m_value{ typename is_backing<typename std::decay<T>::type>::type{ std::forward<T>(in_value) } } {
// Empty ctor body
@ -108,7 +111,7 @@ public:
typename std::enable_if<is_sequence_container<typename std::decay<T>::type>::value
&& !std::is_same<typename std::decay<T>::type, std::vector<bool>>::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<typename T,
typename std::enable_if<std::is_same<typename std::decay<T>::type, std::vector<bool>>::value>::type* = nullptr>
object(T&& in_value)
: m_value{ array_t{} } {
auto& array = std::get<array_t>(m_value);
: m_value{ array_type{} } {
auto& array = std::get<array_type>(m_value);
array.reserve(in_value.size());
for (const auto& item : in_value) {
@ -125,32 +128,32 @@ public:
}
}
// std::unordered_map<std::string, object>
// std::unordered_map<string_type, object>
template<typename T,
typename std::enable_if<is_unordered_map<typename std::decay<T>::type>::value
&& std::is_same<typename is_unordered_map<typename std::decay<T>::type>::key_type, std::string>::value
&& std::is_same<typename is_unordered_map<typename std::decay<T>::type>::key_type, string_type>::value
&& std::is_same<typename is_unordered_map<typename std::decay<T>::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<std::string, T>)
// Non-map_type associative containers (container<string_type, T>)
template<typename T,
typename std::enable_if<is_associative_container<typename remove_cvref<T>::type>::value
&& (std::is_convertible<typename is_associative_container<typename remove_cvref<T>::type>::key_type, std::string>::value
|| std::is_convertible<typename is_associative_container<typename remove_cvref<T>::type>::key_type, std::string_view>::value)
&& (std::is_convertible<typename is_associative_container<typename remove_cvref<T>::type>::key_type, string_type>::value
|| std::is_convertible<typename is_associative_container<typename remove_cvref<T>::type>::key_type, string_view_type>::value)
&& !std::is_same<typename is_associative_container<typename remove_cvref<T>::type>::value_type, object>::value>::type* = nullptr>
object(T&& in_value)
: m_value{ map_t{} } {
auto& map = std::get<map_t>(m_value);
: m_value{ map_type{} } {
auto& map = std::get<map_type>(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<typename T, typename DefaultT = T,
typename std::enable_if<std::is_same<T, std::string>::value && std::is_convertible<typename std::decay<DefaultT>::type, T>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, string_type>::value && std::is_convertible<typename std::decay<DefaultT>::type, T>::value>::type* = nullptr>
T get(DefaultT&& in_default_value = {}) const {
const std::string* result = std::get_if<std::string>(&m_value);
const string_type* result = std::get_if<string_type>(&m_value);
if (result != nullptr) {
return *result;
}
@ -238,9 +243,9 @@ public:
// TODO: support other basic_string_view types
template<typename T, typename DefaultT = T,
typename std::enable_if<std::is_same<T, std::string>::value && std::is_same<typename std::decay<DefaultT>::type, std::string_view>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, string_type>::value && std::is_same<typename std::decay<DefaultT>::type, string_view_type>::value>::type* = nullptr>
T get(DefaultT&& in_default_value) const {
const std::string* result = std::get_if<std::string>(&m_value);
const string_type* result = std::get_if<string_type>(&m_value);
if (result != nullptr) {
return *result;
}
@ -250,11 +255,11 @@ public:
/** arrays */
// reference getter (array_t)
// reference getter (array_type)
template<typename T,
typename std::enable_if<std::is_same<T, array_t>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, array_type>::value>::type* = nullptr>
const T& get(const T& in_default_value) const {
const array_t* result = std::get_if<array_t>(&m_value);
const array_type* result = std::get_if<array_type>(&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<typename T,
typename std::enable_if<std::is_same<T, array_t>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, array_type>::value>::type* = nullptr>
T get(T&& in_default_value = {}) const {
const array_t* result = std::get_if<array_t>(&m_value);
const array_type* result = std::get_if<array_type>(&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<typename T, typename DefaultT = T,
typename std::enable_if<is_sequence_container<T>::value && !std::is_same<T, array_t>::value && std::is_same<typename std::decay<DefaultT>::type, T>::value>::type* = nullptr>
typename std::enable_if<is_sequence_container<T>::value && !std::is_same<T, array_type>::value && std::is_same<typename std::decay<DefaultT>::type, T>::value>::type* = nullptr>
T get(DefaultT&& in_default_value = {}) const {
using backing_t = typename is_sequence_container<T>::type;
const array_t* array = std::get_if<array_t>(&m_value);
const array_type* array = std::get_if<array_type>(&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<typename T,
typename std::enable_if<std::is_same<T, map_t>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, map_type>::value>::type* = nullptr>
const T& get(const T& in_default_value) const {
const map_t* result = std::get_if<map_t>(&m_value);
const map_type* result = std::get_if<map_type>(&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<typename T,
typename std::enable_if<std::is_same<T, map_t>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, map_type>::value>::type* = nullptr>
T get(T&& in_default_value = {}) const {
const map_t* result = std::get_if<map_t>(&m_value);
const map_type* result = std::get_if<map_type>(&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<typename T,
typename std::enable_if<std::is_convertible<typename std::decay<T>::type, std::string>::value>::type* = nullptr>
typename std::enable_if<std::is_convertible<typename std::decay<T>::type, string_type>::value>::type* = nullptr>
void set(T&& in_value) {
m_value.emplace<std::string>(std::forward<T>(in_value));
m_value.emplace<string_type>(std::forward<T>(in_value));
}
// string_view
template<typename T,
typename std::enable_if<std::is_same<T, std::string_view>::value>::type* = nullptr>
typename std::enable_if<std::is_same<T, string_view_type>::value>::type* = nullptr>
void set(const T& in_value) {
m_value.emplace<std::string>(in_value.begin(), in_value.end());
m_value.emplace<string_type>(in_value.begin(), in_value.end());
}
// array_t
// array_type
template<typename T,
typename std::enable_if<std::is_same<typename std::decay<T>::type, array_t>::value>::type* = nullptr>
typename std::enable_if<std::is_same<typename std::decay<T>::type, array_type>::value>::type* = nullptr>
void set(T&& in_value) {
m_value.emplace<array_t>(std::forward<T>(in_value));
m_value.emplace<array_type>(std::forward<T>(in_value));
}
// is_sequence_container
template<typename T,
typename std::enable_if<is_sequence_container<typename std::decay<T>::type>::value
&& !std::is_same<typename std::decay<T>::type, array_t>::value
&& !std::is_same<typename std::decay<T>::type, array_type>::value
&& !std::is_same<typename std::decay<T>::type, std::vector<bool>>::value>::type* = nullptr>
void set(T&& in_value) {
m_value.emplace<array_t>(in_value.begin(), in_value.end());
m_value.emplace<array_type>(in_value.begin(), in_value.end());
}
// std::vector<bool>
template<typename T,
typename std::enable_if<std::is_same<typename std::decay<T>::type, std::vector<bool>>::value>::type* = nullptr>
void set(T&& in_value) {
auto& array = m_value.emplace<array_t>();
auto& array = m_value.emplace<array_type>();
array.reserve(in_value.size());
for (const auto& item : in_value) {
@ -433,7 +438,7 @@ public:
if constexpr (std::is_same<T, null_variant_t>::value) {
return 0;
}
else if constexpr (std::is_same<T, array_t>::value) {
else if constexpr (std::is_same<T, array_type>::value) {
size_t result{};
for (auto& obj : value) {
result += obj.hash();
@ -441,10 +446,10 @@ public:
return result;
}
else if constexpr (std::is_same<T, map_t>::value) {
else if constexpr (std::is_same<T, map_type>::value) {
size_t result{};
for (auto& pair : value) {
result += std::hash<std::string>{}(pair.first);
result += std::hash<string_type>{}(pair.first);
}
return result;
@ -457,7 +462,16 @@ public:
private:
using null_variant_t = void*;
std::variant<null_variant_t, bool, intmax_t, long double, std::string, array_t, map_t> m_value;
// TODO: consider replacing std::string with std::u8string (for strings) & std::vector<unsigned char> (for data)
// TODO: consider some more generic mechanism for underlying string type, to support utf-16 & utf-32 strings
std::variant<null_variant_t, bool, intmax_t, long double, string_type, array_type, map_type> 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

36
src/test/object.cpp

@ -67,8 +67,8 @@ TEST(ObjectTest, basic_has) {
EXPECT_FALSE(obj.has<double>());
EXPECT_FALSE(obj.has<long double>());
EXPECT_FALSE(obj.has<std::string>());
EXPECT_FALSE(obj.has<object::array_t>());
EXPECT_FALSE(obj.has<object::map_t>());
EXPECT_FALSE(obj.has<object::array_type>());
EXPECT_FALSE(obj.has<object::map_type>());
}
TEST(ObjectTest, basic_has_vector) {
@ -200,8 +200,8 @@ TEST(ObjectTest, basic_get) {
EXPECT_EQ(obj.get<double>(), double{});
EXPECT_EQ(obj.get<long double>(), long_double_t{});
EXPECT_EQ(obj.get<std::string>(), std::string{});
EXPECT_TRUE(obj.get<object::array_t>().empty());
EXPECT_TRUE(obj.get<object::map_t>().empty());
EXPECT_TRUE(obj.get<object::array_type>().empty());
EXPECT_TRUE(obj.get<object::map_type>().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<int>(), 1234);
}
TEST(ObjectTest, basic_array_access_operator) {
object obj;
obj[0] = 1234;
EXPECT_EQ(obj.size(), 1);
EXPECT_EQ(obj[0].get<int>(), 1234);
EXPECT_EQ(obj[1].get<int>(), 0);
EXPECT_EQ(obj.size(), 2);
obj[0] = 4567;
EXPECT_EQ(obj[0].get<int>(), 4567);
EXPECT_EQ(obj[1].get<int>(), 0);
EXPECT_EQ(obj.size(), 2);
obj[1] = 1234;
EXPECT_EQ(obj[0].get<int>(), 4567);
EXPECT_EQ(obj[1].get<int>(), 1234);
EXPECT_EQ(obj.size(), 2);
}
/** end basic tests */
TEST(ObjectTest, set_bool) {

Loading…
Cancel
Save