Commit 9c415944 authored by Dominik Charousset's avatar Dominik Charousset

Port option_value to new config_value interface

parent ed6dc95f
......@@ -26,12 +26,14 @@
#include <functional>
#include "caf/atom.hpp"
#include "caf/error.hpp"
#include "caf/message.hpp"
#include "caf/variant.hpp"
#include "caf/config_value.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/error.hpp"
#include "caf/message.hpp"
#include "caf/static_visitor.hpp"
#include "caf/timestamp.hpp"
#include "caf/variant.hpp"
#include "caf/detail/type_traits.hpp"
......@@ -42,13 +44,13 @@ extern const char* type_name_visitor_tbl[];
/// Helper class to generate config readers for different input types.
class config_option {
public:
using config_reader_sink = std::function<void (size_t, config_value&,
using config_reader_sink = std::function<bool (size_t, config_value&,
optional<std::ostream&>)>;
using legal_types = detail::type_list<bool, float, double, std::string,
atom_value, int8_t, uint8_t, int16_t,
uint16_t, int32_t, uint32_t, int64_t,
uint64_t, duration>;
uint64_t, timespan>;
config_option(const char* cat, const char* nm, const char* expl);
......@@ -95,12 +97,14 @@ public:
return type_name_visitor_tbl[static_cast<size_t>(index)];
}
const char* operator()(const std::vector<config_value>&) {
template <class U>
const char* operator()(const std::vector<U>&) {
return "a list";
}
const char* operator()(const std::map<std::string, config_value>&) {
return "a map";
template <class K, class V>
const char* operator()(const std::map<K, V>&) {
return "a dictionary";
}
const char* operator()(const timespan&) {
......@@ -123,48 +127,6 @@ public:
};
protected:
template <class T, class U>
static bool assign_config_value(T& x, U& y) {
x = std::move(y);
return true;
}
// Catches any integer type that is smaller than int64_t.
template <class T>
static typename std::enable_if<sizeof(T) < sizeof(int64_t), bool>::type
assign_config_value(T& x, int64_t& y) {
if (y < static_cast<int64_t>(std::numeric_limits<T>::lowest())
|| y > static_cast<int64_t>(std::numeric_limits<T>::max()))
return false;
x = static_cast<T>(y);
return true;
}
// Catches size_t and uint64_t (yes, they differ on some compilers).
template <class T>
static typename std::enable_if<std::is_unsigned<T>::value
&& sizeof(T) == sizeof(int64_t), bool>::type
assign_config_value(T& x, int64_t& y) {
if (y < 0)
return false;
x = static_cast<uint64_t>(y);
return true;
}
static bool assign_config_value(float& x, double& y) {
if (y < static_cast<double>(std::numeric_limits<float>::lowest())
|| y > static_cast<double>(std::numeric_limits<float>::max()))
return false;
x = static_cast<float>(y);
return true;
}
template <class T>
static bool assign_config_value(std::vector<T>&,
std::vector<config_value>&) {
// TODO: implement me
}
void report_type_error(size_t ln, config_value& x, const char* expected,
optional<std::ostream&> out);
......@@ -175,67 +137,9 @@ private:
char short_name_;
};
template <class T,
bool IsInsertable = detail::can_insert_elements<T>()
&& !std::is_same<T, std::string>::value>
class config_option_impl : public config_option {
public:
config_option_impl(T& ref, const char* ctg, const char* nm, const char* xp)
: config_option(ctg, nm, xp),
ref_(ref) {
// nop
}
std::string to_string() const override {
return deep_to_string(ref_);
}
message::cli_arg to_cli_arg(bool use_caf_prefix) override {
std::string argname;
if (use_caf_prefix)
argname = "caf#";
if (strcmp(category(), "global") != 0) {
argname += category();
argname += ".";
}
argname += name();
if (short_name() != '\0') {
argname += ',';
argname += short_name();
}
return {std::move(argname), explanation(), ref_};
}
config_reader_sink to_sink() override {
return [=](size_t ln, config_value& x, optional<std::ostream&> errors) {
// the INI parser accepts all integers as int64_t
// and all floating point numbers as doubles
using cfg_type =
typename std::conditional<
std::is_integral<T>::value && !std::is_same<bool, T>::value,
int64_t,
typename std::conditional<
std::is_floating_point<T>::value,
double,
T
>::type
>::type;
if (get_if<cfg_type>(&x) && assign_config_value(ref_, get<cfg_type>(x)))
return;
type_name_visitor tnv;
report_type_error(ln, x, tnv(ref_), errors);
};
}
private:
T& ref_;
};
template <class T>
class config_option_impl<T, true> : public config_option {
class config_option_impl : public config_option {
public:
using value_type = typename T::value_type;
config_option_impl(T& ref, const char* ctg, const char* nm, const char* xp)
: config_option(ctg, nm, xp),
ref_(ref) {
......@@ -264,26 +168,14 @@ public:
config_reader_sink to_sink() override {
return [=](size_t ln, config_value& x, optional<std::ostream&> errors) {
// the INI parser accepts all integers as int64_t
// and all floating point numbers as doubles
using cfg_type =
typename std::conditional<
std::is_integral<value_type>::value &&
!std::is_same<bool, value_type>::value,
int64_t,
typename std::conditional<
std::is_floating_point<value_type>::value,
double,
value_type
>::type
>::type;
value_type tmp;
if (get_if<cfg_type>(&x) && assign_config_value(tmp, get<cfg_type>(x))) {
ref_.insert(ref_.end(), std::move(tmp));
return;
auto res = get_if<T>(&x);
if (res) {
ref_ = *res;
return true;
}
type_name_visitor tnv;
report_type_error(ln, x, tnv(tmp), errors);
report_type_error(ln, x, tnv(ref_), errors);
return false;
};
}
......
......@@ -34,7 +34,7 @@ struct parse_ini_t {
/// Denotes an optional error output stream
using opt_err = optional<std::ostream&>;
/// Denotes a callback for consuming configuration values.
using config_consumer = std::function<void (size_t, std::string,
using config_consumer = std::function<bool (size_t, std::string,
config_value&, opt_err)>;
/// Parse the given input stream as INI formatted data and
......
......@@ -339,6 +339,9 @@ struct message::cli_arg {
/// Creates a CLI argument storing its matched argument in `dest`.
cli_arg(std::string nstr, std::string tstr, atom_value& arg);
/// Creates a CLI argument storing its matched argument in `dest`.
cli_arg(std::string nstr, std::string tstr, timespan& arg);
/// Creates a CLI argument storing its matched argument in `dest`.
cli_arg(std::string nstr, std::string tstr, std::string& arg);
......
......@@ -56,23 +56,24 @@ public:
sinks_.emplace(x->full_name(), x->to_sink());
}
void operator()(size_t ln, const std::string& name, config_value& cv,
bool operator()(size_t ln, const std::string& name, config_value& cv,
optional<std::ostream&> out) {
auto i = sinks_.find(name);
if (i != sinks_.end()) {
(i->second)(ln, cv, none);
return;
return true;
}
// check whether this is an individual actor config
if (name.compare(0, actor_conf_prefix_size, actor_conf_prefix) == 0) {
auto substr = name.substr(actor_conf_prefix_size);
named_actor_sink_(ln, substr, cv);
return;
return true;
}
if (out)
*out << "error in line " << ln
<< R"(: unrecognized parameter name ")" << name << R"(")"
<< std::endl;
return false;
}
private:
......
......@@ -520,6 +520,22 @@ message::cli_arg::cli_arg(std::string nstr, std::string tstr, atom_value& arg)
// nop
}
message::cli_arg::cli_arg(std::string nstr, std::string tstr, timespan& arg)
: name(std::move(nstr)),
text(std::move(tstr)),
fun([&arg](const std::string& str) -> bool {
int64_t count;
std::istringstream iss{str};
if (iss >> count) {
arg = timespan{count};
return true;
}
return false;
}),
flag(nullptr) {
// nop
}
message::cli_arg::cli_arg(std::string nstr, std::string tstr, std::string& arg)
: name(std::move(nstr)),
text(std::move(tstr)),
......
......@@ -35,6 +35,8 @@
using namespace caf;
using std::string;
namespace {
constexpr const char* category = "category";
......@@ -43,193 +45,156 @@ constexpr const char* explanation = "explanation";
constexpr size_t line = 0;
template<class T>
constexpr T zero() { return 0; }
constexpr int64_t overflow() {
return static_cast<int64_t>(std::numeric_limits<T>::max()) + 1;
}
template<class T, class U, class V>
constexpr T overflow() {
// +2 is necessary as after an overflow unsigned integral numbers
// must differ from zero() in the tests
return static_cast<V>(std::numeric_limits<U>::max()) + 2;
template<class T>
constexpr int64_t underflow() {
return static_cast<int64_t>(std::numeric_limits<T>::min()) - 1;
}
template <class T, class U>
std::tuple<T, std::string> run_config_option(const T& init_value,
const U& test_value) {
std::stringstream ostr;
T output_value = init_value;
auto cv = config_value(test_value);
template <class T>
optional<T> read(config_value test_value) {
T output_value{};
auto co = make_config_option(output_value, category, name, explanation);
auto f = co->to_sink();
f(line, cv, static_cast<std::ostream&>(ostr));
return std::make_tuple(output_value, ostr.str());
if (f(line, test_value, none))
return output_value;
return none;
}
// Unsigned integers.
template <class T>
void check_integer_options(std::true_type) {
// Run tests for positive integers.
T xzero = 0;
T xmax = std::numeric_limits<T>::max();
CAF_CHECK_EQUAL(read<T>(config_value{xzero}), xzero);
CAF_CHECK_EQUAL(read<T>(config_value{xmax}), xmax);
CAF_CHECK_EQUAL(read<T>(config_value{overflow<T>()}), none);
}
// Signed integers.
template <class T>
void check_integer_options(std::false_type) {
// Run tests for positive integers.
std::true_type tk;
check_integer_options<T>(tk);
// Run tests for negative integers.
auto xmin = std::numeric_limits<T>::min();
CAF_CHECK_EQUAL(read<T>(config_value{xmin}), xmin);
CAF_CHECK_EQUAL(read<T>(config_value{underflow<T>()}), none);
}
// only works with an integral types and double
template <class T, class U>
void check_num_general_usage() {
// check positive numbers
std::string error_str;
U test_value = 5;
T result;
std::tie(result, error_str) = run_config_option(zero<T>(), test_value);
CAF_CHECK_EQUAL(result, static_cast<T>(test_value));
CAF_CHECK(error_str.empty());
// check negative numbers
test_value = -5;
std::tie(result, error_str) = run_config_option(zero<T>(), test_value);
if (std::numeric_limits<T>::is_signed) {
CAF_CHECK_EQUAL(result, static_cast<T>(test_value));
CAF_CHECK(error_str.empty());
} else {
CAF_CHECK_EQUAL(result, zero<T>());
CAF_CHECK(!error_str.empty());
}
// check vector<T>
test_value = 5;
std::vector<T> vec_result;
std::tie(vec_result, error_str) =
run_config_option(std::vector<T>{}, test_value);
CAF_CHECK(!vec_result.empty());
if (!vec_result.empty()) {
CAF_CHECK_EQUAL(*vec_result.begin(), static_cast<T>(test_value));
}
}
// only works with an integral types (no doubles)
template <class T, class U>
void check_num_boundary_usage() {
std::string error_str;
T result;
U boundary_check = overflow<U,T,U>();
std::tie(result, error_str) = run_config_option(zero<T>(), boundary_check);
T tmp = overflow<T, T, U>();
CAF_CHECK_NOT_EQUAL(result, tmp);
CAF_CHECK_EQUAL(result, zero<T>());
CAF_CHECK(!error_str.empty());
}
// only works with an integral types (no doubles)
template <class T, class U>
void check_num_general_and_boundary_usage() {
check_num_general_usage<T, U>();
check_num_boundary_usage<T, U>();
}
// intended for atoms, strings, and bools
template <class T>
void check_non_num_general_usage(const T& init_value, const T& test_value) {
// general check
std::string error_str;
T result;
std::tie(result, error_str) = run_config_option(init_value, test_value);
CAF_CHECK_EQUAL(result, test_value);
// vector<T> check
std::vector<T> vec_result;
std::tie(vec_result, error_str) =
run_config_option(std::vector<T>{}, test_value);
CAF_CHECK(!vec_result.empty());
if (!vec_result.empty()) {
CAF_CHECK_EQUAL(*vec_result.begin(), test_value);
}
}
void check_non_num_general_usage(bool init_value, bool test_value) {
// general check
std::string error_str;
bool result;
std::tie(result, error_str) = run_config_option(init_value, test_value);
CAF_CHECK_EQUAL(result, test_value);
// vector<T> check
// emplace_back() in class cli_arg do not support <bool> until C++14
void check_integer_options() {
std::integral_constant<bool, std::is_unsigned<T>::value> tk;
check_integer_options<T>(tk);
}
template <class T>
T unbox(optional<T> x) {
if (!x)
CAF_FAIL("no value to unbox");
return std::move(*x);
}
} // namespace <anonymous>
CAF_TEST(type_bool) {
check_non_num_general_usage(false, true);
CAF_CHECK_EQUAL(read<bool>(config_value{true}), true);
CAF_CHECK_EQUAL(read<bool>(config_value{false}), false);
CAF_CHECK_EQUAL(read<bool>(config_value{0}), none);
CAF_CHECK_EQUAL(read<bool>(config_value{1}), none);
}
CAF_TEST(type int8_t) {
check_integer_options<int8_t>();
}
CAF_TEST(type_int8_t) {
check_num_general_and_boundary_usage<int8_t, int64_t>();
CAF_TEST(type uint8_t) {
check_integer_options<uint8_t>();
}
CAF_TEST(type_uint8_t) {
check_num_general_and_boundary_usage<uint8_t, int64_t>();
CAF_TEST(type int16_t) {
check_integer_options<int16_t>();
}
CAF_TEST(type_int16_t) {
check_num_general_and_boundary_usage<int16_t, int64_t>();
CAF_TEST(type uint16_t) {
check_integer_options<uint16_t>();
}
CAF_TEST(type_uint16_t) {
check_num_general_and_boundary_usage<uint16_t, int64_t>();
CAF_TEST(type int32_t) {
check_integer_options<int32_t>();
}
CAF_TEST(type_int32_t) {
check_num_general_and_boundary_usage<int32_t, int64_t>();
CAF_TEST(type uint32_t) {
check_integer_options<uint32_t>();
}
CAF_TEST(type_uint32_t) {
check_num_general_and_boundary_usage<uint32_t, int64_t>();
CAF_TEST(type uint64_t) {
CAF_CHECK_EQUAL(unbox(read<uint64_t>(config_value{0})), 0u);
CAF_CHECK_EQUAL(read<uint64_t>(config_value{-1}), none);
}
CAF_TEST(type_uint64_t) {
check_num_general_usage<uint64_t, int64_t>();
CAF_TEST(type int64_t) {
CAF_CHECK_EQUAL(unbox(read<int64_t>(config_value{-1})), -1);
CAF_CHECK_EQUAL(unbox(read<int64_t>(config_value{0})), 0);
CAF_CHECK_EQUAL(unbox(read<int64_t>(config_value{1})), 1);
}
CAF_TEST(type_int64_t) {
check_num_general_usage<int64_t, int64_t>();
CAF_TEST(type float) {
CAF_CHECK_EQUAL(unbox(read<float>(config_value{-1.0})), -1.0f);
CAF_CHECK_EQUAL(unbox(read<float>(config_value{-0.1})), -0.1f);
CAF_CHECK_EQUAL(read<float>(config_value{0}), none);
CAF_CHECK_EQUAL(read<float>(config_value{"0.1"}), none);
}
CAF_TEST(type_float) {
check_num_general_usage<float, double>();
// check boundaries
std::string error_str;
float result;
float init_value = 0;
// *2 is required as +2 does not change the variable at this size anymore
double boundary_check = static_cast<double>(
std::numeric_limits<float>::max()) * 2;
std::tie(result, error_str) = run_config_option(init_value, boundary_check);
float float_inf = std::numeric_limits<float>::infinity();
// Unit test does not compare inf values correct until now
bool tmp = float_inf == result;
CAF_CHECK_NOT_EQUAL(tmp, true);
CAF_CHECK_EQUAL(result, init_value);
CAF_CHECK_EQUAL(error_str.empty(), false);
CAF_TEST(type double) {
CAF_CHECK_EQUAL(unbox(read<double>(config_value{-1.0})), -1.0);
CAF_CHECK_EQUAL(unbox(read<double>(config_value{-0.1})), -0.1);
CAF_CHECK_EQUAL(read<double>(config_value{0}), none);
CAF_CHECK_EQUAL(read<double>(config_value{"0.1"}), none);
}
CAF_TEST(type_double) {
check_num_general_usage<double, double>();
CAF_TEST(type string) {
CAF_CHECK_EQUAL(unbox(read<string>(config_value{"foo"})), "foo");
}
CAF_TEST(type_string) {
check_non_num_general_usage<std::string>("", "test string");
CAF_TEST(type atom) {
auto foo = atom("foo");
CAF_CHECK_EQUAL(unbox(read<atom_value>(config_value{foo})), foo);
CAF_CHECK_EQUAL(read<atom_value>(config_value{"bar"}), none);
}
CAF_TEST(type_atom) {
// TODO: in class cli_arg std::istringstream do not support atom_value
// check_non_num_general_usage<atom_value>(atom(""), atom("test atom"));
CAF_TEST(type timespan) {
timespan dur{500};
CAF_CHECK_EQUAL(unbox(read<timespan>(config_value{dur})), dur);
}
template <class T>
std::string v(const T& x) {
std::string name_of() {
T x{};
config_option::type_name_visitor v;
return v(x);
}
CAF_TEST(type_names) {
CAF_CHECK_EQUAL(v(true), "a boolean");
CAF_CHECK_EQUAL(v(atom("")), "an atom_value");
CAF_CHECK_EQUAL(v(std::string{}), "a string");
CAF_CHECK_EQUAL(v(zero<float>()), "a float");
CAF_CHECK_EQUAL(v(zero<double>()), "a double");
CAF_CHECK_EQUAL(v(zero<int8_t>()), "an 8-bit integer");
CAF_CHECK_EQUAL(v(zero<uint8_t>()), "an 8-bit unsigned integer");
CAF_CHECK_EQUAL(v(zero<int16_t>()), "a 16-bit integer");
CAF_CHECK_EQUAL(v(zero<uint16_t>()), "a 16-bit unsigned integer");
CAF_CHECK_EQUAL(v(zero<int32_t>()), "a 32-bit integer");
CAF_CHECK_EQUAL(v(zero<uint32_t>()), "a 32-bit unsigned integer");
CAF_CHECK_EQUAL(v(zero<int64_t>()), "a 64-bit integer");
CAF_CHECK_EQUAL(v(zero<uint64_t>()), "a 64-bit unsigned integer");
CAF_CHECK_EQUAL((name_of<std::map<int, int>>()), "a dictionary");
CAF_CHECK_EQUAL(name_of<atom_value>(), "an atom_value");
CAF_CHECK_EQUAL(name_of<bool>(), "a boolean");
CAF_CHECK_EQUAL(name_of<double>(), "a double");
CAF_CHECK_EQUAL(name_of<float>(), "a float");
CAF_CHECK_EQUAL(name_of<int16_t>(), "a 16-bit integer");
CAF_CHECK_EQUAL(name_of<int32_t>(), "a 32-bit integer");
CAF_CHECK_EQUAL(name_of<int64_t>(), "a 64-bit integer");
CAF_CHECK_EQUAL(name_of<int8_t>(), "an 8-bit integer");
CAF_CHECK_EQUAL(name_of<std::vector<int>>(), "a list");
CAF_CHECK_EQUAL(name_of<string>(), "a string");
CAF_CHECK_EQUAL(name_of<uint16_t>(), "a 16-bit unsigned integer");
CAF_CHECK_EQUAL(name_of<uint32_t>(), "a 32-bit unsigned integer");
CAF_CHECK_EQUAL(name_of<uint64_t>(), "a 64-bit unsigned integer");
CAF_CHECK_EQUAL(name_of<uint8_t>(), "an 8-bit unsigned integer");
}
......@@ -129,6 +129,7 @@ struct fixture {
message_visitor mv;
anon_send(config_server, put_atom::value,
std::move(key), visit(mv, value));
return true;
};
load_impl(consume, str);
}
......@@ -137,6 +138,7 @@ struct fixture {
auto consume = [&](size_t, std::string key, config_value& value,
optional<std::ostream&>) {
values.emplace(std::move(key), std::move(value));
return true;
};
load_impl(consume, str);
}
......
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