Commit 04ede6c4 authored by Dominik Charousset's avatar Dominik Charousset Committed by Dominik Charousset

Add automagic unboxing for homogeneous lists/maps

parent dc0f0375
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "caf/default_sum_type_access.hpp" #include "caf/default_sum_type_access.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/optional.hpp"
#include "caf/sum_type.hpp" #include "caf/sum_type.hpp"
#include "caf/sum_type_access.hpp" #include "caf/sum_type_access.hpp"
#include "caf/timestamp.hpp" #include "caf/timestamp.hpp"
...@@ -163,11 +164,308 @@ private: ...@@ -163,11 +164,308 @@ private:
variant_type data_; variant_type data_;
}; };
/*
/// Enable `holds_alternative`, `get`, `get_if`, and `visit` for `config_value`. /// Enable `holds_alternative`, `get`, `get_if`, and `visit` for `config_value`.
/// @relates config_value /// @relates config_value
/// @relates SumType /// @relates SumType
///
template <> template <>
struct sum_type_access<config_value> : default_sum_type_access<config_value> {}; struct sum_type_access<config_value> : default_sum_type_access<config_value> {};
*/
template <class T>
struct config_value_access;
template <class T>
bool holds_alternative(const config_value& x) {
return config_value_access<T>::is(x);
}
template <class T>
auto get_if(const config_value* x)
-> decltype(config_value_access<T>::get_if(x)) {
return config_value_access<T>::get_if(x);
}
template <class T>
auto get(const config_value& x) -> decltype(config_value_access<T>::get(x)) {
return config_value_access<T>::get(x);
}
template <class Visitor>
auto visit(Visitor&& f, config_value& x)
-> decltype(visit(std::forward<Visitor>(f), x.get_data())) {
return visit(std::forward<Visitor>(f), x.get_data());
}
template <class Visitor>
auto visit(Visitor&& f, const config_value& x)
-> decltype(visit(std::forward<Visitor>(f), x.get_data())) {
return visit(std::forward<Visitor>(f), x.get_data());
}
template <class T>
struct default_config_value_access {
static bool is(const config_value& x) {
return holds_alternative<T>(x.get_data());
}
static const T* get_if(const config_value* x) {
return caf::get_if<T>(&(x->get_data()));
}
static const T& get(const config_value& x) {
return caf::get<T>(x.get_data());
}
};
#define CAF_CONFIG_VALUE_DEFAULT_ACCESS(subtype) \
template <> \
struct config_value_access<config_value::subtype> \
: default_config_value_access<config_value::subtype> {}
CAF_CONFIG_VALUE_DEFAULT_ACCESS(integer);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(boolean);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(real);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(atom);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(timespan);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(string);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(list);
CAF_CONFIG_VALUE_DEFAULT_ACCESS(dictionary);
/// Implements automagic unboxing of `std::vector<T>` from a homogeneous
/// `config_value::list`.
/// @relates config_value
template <class T>
struct config_value_access<std::vector<T>> {
using vector_type = std::vector<T>;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::list>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const config_value& x) {
return caf::holds_alternative<T>(x);
});
}
return false;
}
static optional<vector_type> get_if(const config_value* x) {
vector_type result;
auto extract = [&](const config_value& y) {
auto opt = caf::get_if<T>(&y);
if (opt) {
result.emplace_back(*opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::list>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static vector_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
/// Implements automagic unboxing of `std::map<std::string, V>` from a
/// homogeneous `config_value::dictionary`.
/// @relates config_value
template <class V>
struct config_value_access<std::map<std::string, V>> {
using map_type = std::map<std::string, V>;
using kvp = std::pair<const std::string, config_value>;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::dictionary>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const kvp& x) {
return config_value_access<V>::is(x.second);
});
}
return false;
}
static optional<map_type> get_if(const config_value* x) {
map_type result;
auto extract = [&](const kvp& y) {
auto opt = caf::get_if<V>(&(y.second));
if (opt) {
result.emplace(y.first, std::move(*opt));
return true;
}
return false;
};
auto lst = caf::get_if<config_value::dictionary>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static map_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
/// Retrieves.
/// @relates config_value
template <class T>
optional<T> get_if(const config_value::dictionary* xs,
std::initializer_list<std::string> path) {
// Sanity check.
if (path.size() == 0)
return none;
// Resolve path, i.e., find the requested submap.
auto current = xs;
auto leaf = path.end() - 1;
for (auto i = path.begin(); i != leaf; ++i) {
auto j = current->find(*i);
if (j == current->end())
return none;
current = caf::get_if<config_value::dictionary>(&j->second);
if (current == nullptr)
return none;
}
// Get the leaf value.
auto j = current->find(*leaf);
if (j == current->end())
return none;
auto result = caf::get_if<T>(&j->second);
if (result)
return *result;
return none;
}
/// @relates config_value
template <class T>
optional<T> get_if(const config_value::dictionary* xs, std::string path) {
return get_if<T>(xs, {path});
}
/// @relates config_value
template <class T>
T get(const config_value::dictionary& xs,
std::initializer_list<std::string> path) {
auto result = get_if<T>(&xs, std::move(path));
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
/// @relates config_value
template <class T>
T get(const config_value::dictionary& xs, std::string path) {
return get<T>(xs, {path});
}
/// @relates config_value
template <class T>
T get_or(const config_value::dictionary& xs,
std::initializer_list<std::string> path, const T& default_value) {
auto result = get<T>(xs, std::move(path));
if (result)
return *result;
return default_value;
}
/// @relates config_value
template <class T>
T get_or(const config_value::dictionary& xs, std::string path,
const T& default_value) {
return get_or(xs, {path}, default_value);
}
/*
template <class T>
struct config_value_access<std::vector<T>> {
using vector_type = std::vector<T>;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::list>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const config_value& x) {
return caf::holds_alternative<T>(x);
});
}
return false;
}
static optional<vector_type> get_if(const config_value* x) {
vector_type result;
auto extract = [&](const config_value& y) {
auto opt = caf::get_if<T>(&y);
if (opt) {
result.emplace_back(*opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::list>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static vector_type get(const config_value& x) {
auto result = get_if(x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
template <class V>
struct config_value_access<std::map<std::string, V>> {
using map_type = std::map<std::string, V>;
using kvp = std::pair<const std::string, config_value>;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::dictionary>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const kvp& x) {
return config_value_access<V>::is(x.second);
});
}
return false;
}
static optional<map_type> get_if(const config_value* x) {
map_type result;
auto extract = [&](const kvp& y) {
auto opt = caf::get_if<V>(y.second);
if (opt) {
result.emplace_back(y.first, *opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::dictionary>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static map_type get(const config_value& x) {
auto result = get_if(x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
*/
/// @relates config_value /// @relates config_value
bool operator<(const config_value& x, const config_value& y); bool operator<(const config_value& x, const config_value& y);
......
...@@ -39,6 +39,33 @@ using list = config_value::list; ...@@ -39,6 +39,33 @@ using list = config_value::list;
using dictionary = config_value::dictionary; using dictionary = config_value::dictionary;
struct dictionary_builder {
dictionary dict;
dictionary_builder&& add(const char* key, config_value value) && {
dict.emplace(key, std::move(value));
return std::move(*this);
}
dictionary make() && {
return std::move(dict);
}
config_value make_cv() && {
return config_value{std::move(dict)};
}
};
dictionary_builder dict() {
return {};
}
template <class... Ts>
config_value cfg_lst(Ts&&... xs) {
config_value::list lst{config_value{std::forward<Ts>(xs)}...};
return config_value{std::move(lst)};
}
} // namespace <anonymous> } // namespace <anonymous>
CAF_TEST(default_constructed) { CAF_TEST(default_constructed) {
...@@ -49,9 +76,13 @@ CAF_TEST(default_constructed) { ...@@ -49,9 +76,13 @@ CAF_TEST(default_constructed) {
} }
CAF_TEST(list) { CAF_TEST(list) {
auto x = make_config_value_list(1, 2, 3); using integer_list = std::vector<int64_t>;
CAF_CHECK_EQUAL(to_string(x), "[1, 2, 3]"); auto xs = make_config_value_list(1, 2, 3);
CAF_CHECK_EQUAL(x.type_name(), config_value::type_name_of<list>()); CAF_CHECK_EQUAL(to_string(xs), "[1, 2, 3]");
CAF_CHECK_EQUAL(xs.type_name(), config_value::type_name_of<list>());
CAF_CHECK_EQUAL(holds_alternative<config_value::list>(xs), true);
CAF_CHECK_EQUAL(holds_alternative<integer_list>(xs), true);
CAF_CHECK_EQUAL(get<integer_list>(xs), integer_list({1, 2, 3}));
} }
CAF_TEST(convert_to_list) { CAF_TEST(convert_to_list) {
...@@ -74,15 +105,40 @@ CAF_TEST(append) { ...@@ -74,15 +105,40 @@ CAF_TEST(append) {
CAF_CHECK_EQUAL(to_string(x), "[1, 2, 'foo']"); CAF_CHECK_EQUAL(to_string(x), "[1, 2, 'foo']");
} }
CAF_TEST(maps) { CAF_TEST(homogeneous dictionary) {
dictionary xs; using integer_map = std::map<std::string, int64_t>;
xs["num"] = int64_t{42}; auto xs = dict()
xs["atm"] = atom("hello"); .add("value-1", config_value{1})
xs["str"] = string{"foobar"}; .add("value-2", config_value{2})
xs["dur"] = timespan{100}; .add("value-3", config_value{3})
config_value x{xs}; .add("value-4", config_value{4})
CAF_CHECK_EQUAL(x.type_name(), config_value::type_name_of<dictionary>()); .make();
CAF_CHECK_EQUAL( integer_map ys{
to_string(x), {"value-1", 1},
R"([("atm", 'hello'), ("dur", 100ns), ("num", 42), ("str", "foobar")])"); {"value-2", 2},
{"value-3", 3},
{"value-4", 4},
};
CAF_CHECK_EQUAL(get<int64_t>(xs, "value-1"), 1);
CAF_CHECK_EQUAL(get<integer_map>(config_value{xs}), ys);
}
CAF_TEST(heterogeneous dictionary) {
using string_list = std::vector<std::string>;
auto xs = dict()
.add("scheduler", dict()
.add("policy", config_value{atom("none")})
.add("max-threads", config_value{2})
.make_cv())
.add("nodes", dict()
.add("preload", cfg_lst("sun", "venus", "mercury",
"earth", "mars"))
.make_cv())
.make();
CAF_CHECK_EQUAL(get<atom_value>(xs, {"scheduler", "policy"}), atom("none"));
CAF_CHECK_EQUAL(get<int64_t>(xs, {"scheduler", "max-threads"}), 2);
CAF_CHECK_EQUAL(get_if<double>(&xs, {"scheduler", "max-threads"}), none);
string_list nodes{"sun", "venus", "mercury", "earth", "mars"};
CAF_CHECK_EQUAL(get<string_list>(xs, {"nodes", "preload"}), nodes);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment