Commit 31be7eb2 authored by Dominik Charousset's avatar Dominik Charousset

Re-implement config_option using get_as

parent f6ae028a
......@@ -36,19 +36,17 @@ public:
/// Custom vtable-like struct for delegating to type-specific functions and
/// storing type-specific information shared by several config options.
struct meta_state {
/// Performs a type check on the config value and then stores the converted
/// value in the given storage location unless the storage pointer was set
/// to `nullptr`.
error (*store)(void*, const config_value&);
/// Tries to perform this sequence of steps:
/// - Convert the config value to the type of the config option.
/// - Assign the converted value back to the config value to synchronize
/// conversions back to the caller.
/// - Store the converted value in the pointer unless it is `nullptr`.
error (*sync)(void*, config_value&);
/// Tries to extract a value from the given location. Exists for backward
/// compatibility only and will get removed with CAF 0.17.
config_value (*get)(const void*);
/// Tries to parse an input string. Stores and returns the parsed value on
/// success, returns an error otherwise.
expected<config_value> (*parse)(void*, string_view);
/// Human-readable name of the option's type.
std::string type_name;
};
......@@ -88,9 +86,19 @@ public:
/// Returns the full name for this config option as "<category>.<long name>".
string_view full_name() const noexcept;
/// Performs a type check for `x` and then stores `x` in this option unless it
/// is stateless.
error store(const config_value& x) const;
/// Synchronizes the value of this config option with `x` and vice versa.
///
/// Tries to perform this sequence of steps:
/// - Convert the config value to the type of the config option.
/// - Assign the converted value back to the config value to synchronize
/// conversions back to the caller.
/// - Store the converted value unless this option is stateless.
error sync(config_value& x) const;
[[deprecated("use sync instead")]] error store(const config_value& x) const;
[[deprecated("use sync instead")]] expected<config_value>
parse(string_view input) const;
/// Returns a human-readable representation of this option's expected type.
string_view type_name() const noexcept;
......@@ -101,10 +109,6 @@ public:
/// Returns whether the category is optional for CLI options.
bool has_flat_cli_name() const noexcept;
/// Tries to parse an input string. Stores and returns the parsed value on
/// success, returns an error otherwise.
expected<config_value> parse(string_view input) const;
/// @private
// TODO: remove with CAF 0.17
optional<config_value> get() const;
......
......@@ -52,14 +52,10 @@ public:
config_option_adder&
add_neg(bool& ref, string_view name, string_view description);
/// For backward compatibility only. Do not use for new code!
/// @private
config_option_adder&
[[deprecated("use timespan options instead")]] config_option_adder&
add_us(size_t& ref, string_view name, string_view description);
/// For backward compatibility only. Do not use for new code!
/// @private
config_option_adder&
[[deprecated("use timespan options instead")]] config_option_adder&
add_ms(size_t& ref, string_view name, string_view description);
private:
......
......@@ -225,7 +225,7 @@ public:
template <class T>
error assign(const T& x) {
config_value_writer writer{this};
if (detail::save(writer, x))
if (writer.apply(x))
return {};
else
return {writer.move_error()};
......@@ -306,22 +306,6 @@ private:
/// @relates config_value
CAF_CORE_EXPORT std::string to_string(const config_value& x);
// -- convenience constants ----------------------------------------------------
/// Type of the `top_level_cli_parsing` constant.
using top_level_cli_parsing_t = std::false_type;
/// Signals parsing of top-level config values when used as third argument to
/// `parse_cli`.
constexpr auto top_level_cli_parsing = top_level_cli_parsing_t{};
/// Type of the `top_level_cli_parsing` constant.
using nested_cli_parsing_t = std::true_type;
/// Signals parsing of nested config values when used as third argument to
/// `parse_cli`.
constexpr auto nested_cli_parsing = nested_cli_parsing_t{};
// -- conversion via get_as ----------------------------------------------------
template <class T>
......@@ -464,12 +448,17 @@ expected<T> get_as(const config_value& x, inspector_access_type::list) {
if (auto wrapped_values = x.to_list()) {
using value_type = typename T::value_type;
T result;
result.reserve(wrapped_values->size());
if constexpr (detail::has_reserve_t<T>)
result.reserve(wrapped_values->size());
for (const auto& wrapped_value : *wrapped_values)
if (auto maybe_value = get_as<value_type>(wrapped_value))
result.emplace_back(std::move(*maybe_value));
else
if (auto maybe_value = get_as<value_type>(wrapped_value)) {
if constexpr (detail::has_emplace_back_t<T>)
result.emplace_back(std::move(*maybe_value));
else
result.insert(result.end(), std::move(*maybe_value));
} else {
return {std::move(maybe_value.error())};
}
return {std::move(result)};
} else {
return {std::move(wrapped_values.error())};
......@@ -566,11 +555,6 @@ struct default_config_value_access {
static T convert(T x) {
return x;
}
template <class Nested>
static void parse_cli(string_parser_state& ps, T& x, Nested) {
detail::parse(ps, x);
}
};
/// @relates config_value
......@@ -626,11 +610,6 @@ struct CAF_CORE_EXPORT config_value_access<timespan> {
static timespan convert(timespan x) {
return x;
}
template <class Nested>
static void parse_cli(string_parser_state& ps, timespan& x, Nested) {
detail::parse(ps, x);
}
};
template <>
......@@ -656,11 +635,6 @@ struct CAF_CORE_EXPORT config_value_access<float> {
static double convert(float x) {
return x;
}
template <class Nested>
static void parse_cli(string_parser_state& ps, float& x, Nested) {
detail::parse(ps, x);
}
};
template <>
......@@ -686,15 +660,6 @@ struct CAF_CORE_EXPORT config_value_access<std::string> {
static std::string convert(std::string x) {
return x;
}
template <bool IsNested>
static void parse_cli(string_parser_state& ps, std::string& x,
std::integral_constant<bool, IsNested>) {
if constexpr (IsNested)
detail::parse_element(ps, x, ",={}[]");
else
detail::parse(ps, x);
}
};
// -- implementation details for get/get_if/holds_alternative ------------------
......@@ -818,11 +783,6 @@ struct integral_config_value_access {
static T convert(T x) {
return x;
}
template <class Nested>
static void parse_cli(string_parser_state& ps, T& x, Nested) {
detail::parse(ps, x);
}
};
template <class T>
......@@ -875,74 +835,6 @@ struct list_config_value_access {
result.emplace_back(value_access::convert(x));
return result;
}
static void parse_cli(string_parser_state& ps, T& xs,
top_level_cli_parsing_t) {
bool has_open_token;
auto val_token = config_value_access_token<value_type>();
if constexpr (std::is_same<decltype(val_token),
config_value_access_type::list>::value) {
// The outer square brackets are optional in nested lists. This means we
// need to check for "[[" at the beginning and otherwise we assume the
// leading '[' was omitted.
string_parser_state tmp{ps.i, ps.e};
has_open_token = tmp.consume('[') && tmp.consume('[');
if (has_open_token)
ps.consume('[');
} else {
has_open_token = ps.consume('[');
}
do {
ps.skip_whitespaces();
if (has_open_token) {
if (ps.consume(']')) {
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
return;
}
} else if (ps.at_end()) {
// Allow trailing commas and empty strings.
ps.code = pec::success;
return;
}
value_type tmp;
value_access::parse_cli(ps, tmp, nested_cli_parsing);
if (ps.code > pec::trailing_character)
return;
xs.insert(xs.end(), std::move(tmp));
} while (ps.consume(','));
if (has_open_token && !ps.consume(']')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
static void parse_cli(string_parser_state& ps, T& xs, nested_cli_parsing_t) {
if (!ps.consume('[')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
do {
if (ps.consume(']')) {
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
return;
}
value_type tmp;
value_access::parse_cli(ps, tmp, nested_cli_parsing);
if (ps.code > pec::trailing_character)
return;
xs.insert(xs.end(), std::move(tmp));
} while (ps.consume(','));
if (!ps.consume(']')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
};
template <class T>
......@@ -993,11 +885,6 @@ struct map_config_value_access {
return std::move(*result);
}
template <class Nested>
static void parse_cli(string_parser_state& ps, map_type& xs, Nested) {
detail::parse(ps, xs);
}
static config_value::dictionary convert(const map_type& xs) {
config_value::dictionary result;
for (const auto& x : xs)
......@@ -1050,11 +937,6 @@ struct tuple_config_value_access<std::tuple<Ts...>> {
return result;
}
template <class Nested>
static void parse_cli(string_parser_state& ps, tuple_type& xs, Nested) {
rec_parse(ps, xs, detail::int_token<0>(), detail::type_list<Ts...>());
}
private:
template <int Pos>
static void
......@@ -1119,24 +1001,6 @@ private:
return rec_convert(result, xs, detail::int_token<Pos + 1>(),
detail::type_list<Us...>());
}
template <int Pos>
static void rec_parse(string_parser_state&, tuple_type&,
detail::int_token<Pos>, detail::type_list<>) {
// nop
}
template <int Pos, class U, class... Us>
static void rec_parse(string_parser_state& ps, tuple_type& xs,
detail::int_token<Pos>, detail::type_list<U, Us...>) {
using trait = config_value_access_t<U>;
trait::parse_cli(std::get<Pos>(xs), nested_cli_parsing);
if (ps.code > pec::trailing_character)
return;
if (sizeof...(Us) > 0 && !ps.consume(','))
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
rec_parse(ps, xs, detail::int_token<Pos + 1>(), detail::type_list<Us...>());
}
};
} // namespace detail
......@@ -1289,30 +1153,6 @@ struct inspect_config_value_access {
return std::move(*result);
}
template <class Nested>
static void parse_cli(string_parser_state& ps, T& x, Nested token) {
auto first = ps.i;
config_value tmp;
detail::parse(ps, tmp);
// If the first attempt fails, rewind the parser state and try parsing the
// input as a string. This allows unescaped inputs.
if (ps.code != pec::success) {
ps.code = pec::success;
ps.i = first;
std::string str;
config_value_access_t<std::string>::parse_cli(ps, str, token);
if (ps.code == pec::success)
tmp = config_value{std::move(str)};
}
if (ps.code == pec::success) {
if (auto res = caf::get_if<T>(&tmp)) {
x = detail::move_if_not_ptr(res);
} else {
ps.code = pec::invalid_argument;
}
}
}
static config_value convert(const T& x) {
config_value result;
config_value_writer writer{&result};
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include "caf/config_value.hpp"
#include "caf/detail/type_traits.hpp"
namespace caf::detail {
template <class Trait>
struct dispatch_parse_cli_helper {
template <class... Ts>
auto operator()(Ts&&... xs)
-> decltype(Trait::parse_cli(std::forward<Ts>(xs)...)) {
return Trait::parse_cli(std::forward<Ts>(xs)...);
}
};
template <class Access, class T>
void dispatch_parse_cli(std::true_type, string_parser_state& ps, T& x,
const char* char_blacklist) {
Access::parse_cli(ps, x, char_blacklist);
}
template <class Access, class T>
void dispatch_parse_cli(std::false_type, string_parser_state& ps, T& x,
const char*) {
Access::parse_cli(ps, x);
}
template <class T>
void dispatch_parse_cli(string_parser_state& ps, T& x,
const char* char_blacklist) {
using access = caf::select_config_value_access_t<T>;
using helper_fun = dispatch_parse_cli_helper<access>;
using token_type
= bool_token<detail::is_callable_with<helper_fun, string_parser_state&, T&,
const char*>::value>;
token_type token;
dispatch_parse_cli<access>(token, ps, x, char_blacklist);
}
} // namespace caf::detail
......@@ -685,6 +685,44 @@ public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
struct has_reserve {
private:
template <class List>
static auto sfinae(List* l) -> decltype(l->reserve(10), std::true_type());
template <class U>
static auto sfinae(...) -> std::false_type;
using sfinae_type = decltype(sfinae<T>(nullptr));
public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
constexpr bool has_reserve_t = has_reserve<T>::value;
template <class T>
struct has_emplace_back {
private:
template <class List>
static auto sfinae(List* l)
-> decltype(l->emplace_back(std::declval<typename List::value_type>()),
std::true_type());
template <class U>
static auto sfinae(...) -> std::false_type;
using sfinae_type = decltype(sfinae<T>(nullptr));
public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
constexpr bool has_emplace_back_t = has_emplace_back<T>::value;
template <class T>
class has_call_error_handler {
private:
......
......@@ -22,7 +22,6 @@
#include "caf/config_option.hpp"
#include "caf/config_value.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
......@@ -33,51 +32,32 @@
namespace caf::detail {
template <class T>
error store_impl(void* ptr, const config_value& x) {
if (holds_alternative<T>(x)) {
if (ptr)
*static_cast<T*>(ptr) = get<T>(x);
return none;
error sync_impl(void* ptr, config_value& x) {
if (auto val = get_as<T>(x)) {
if (auto err = x.assign(*val); !err) {
if (ptr)
*static_cast<T*>(ptr) = std::move(*val);
return none;
} else {
return err;
}
} else {
return make_error(pec::type_mismatch);
return std::move(val.error());
}
}
template <class T>
config_value get_impl(const void* ptr) {
using trait = detail::config_value_access_t<T>;
return config_value{trait::convert(*reinterpret_cast<const T*>(ptr))};
}
template <class T>
expected<config_value> parse_impl(T* ptr, string_view str) {
if (!ptr) {
T tmp;
return parse_impl(&tmp, str);
}
if constexpr (detail::has_clear_member<T>::value)
ptr->clear();
using trait = detail::config_value_access_t<T>;
string_parser_state ps{str.begin(), str.end()};
trait::parse_cli(ps, *ptr, top_level_cli_parsing);
if (ps.code != pec::success)
return make_error(ps);
return config_value{trait::convert(*ptr)};
}
CAF_CORE_EXPORT expected<config_value> parse_impl(std::string* ptr,
string_view str);
template <class T>
expected<config_value> parse_impl_delegate(void* ptr, string_view str) {
return parse_impl(reinterpret_cast<T*>(ptr), str);
config_value result;
auto err = result.assign(*static_cast<const T*>(ptr));
static_cast<void>(err); // Safe to discard because sync() fails otherwise.
return result;
}
template <class T>
config_option::meta_state* option_meta_state_instance() {
using trait = detail::config_value_access_t<T>;
static config_option::meta_state obj{store_impl<T>, get_impl<T>,
parse_impl_delegate<T>,
static config_option::meta_state obj{sync_impl<T>, get_impl<T>,
trait::type_name()};
return &obj;
}
......@@ -109,12 +89,12 @@ make_negated_config_option(bool& storage, string_view category,
string_view name, string_view description);
// Reads timespans, but stores an integer representing microsecond resolution.
CAF_CORE_EXPORT config_option
[[deprecated("use timespan options instead")]] CAF_CORE_EXPORT config_option
make_us_resolution_config_option(size_t& storage, string_view category,
string_view name, string_view description);
// Reads timespans, but stores an integer representing millisecond resolution.
CAF_CORE_EXPORT config_option
[[deprecated("use timespan options instead")]] CAF_CORE_EXPORT config_option
make_ms_resolution_config_option(size_t& storage, string_view category,
string_view name, string_view description);
......
......@@ -340,7 +340,7 @@ actor_system_config& actor_system_config::set_impl(string_view name,
if (opt == nullptr) {
std::cerr << "*** failed to set config parameter " << name
<< ": invalid name" << std::endl;
} else if (auto err = opt->store(value)) {
} else if (auto err = opt->sync(value)) {
std::cerr << "*** failed to set config parameter " << name << ": "
<< to_string(err) << std::endl;
} else {
......@@ -412,12 +412,13 @@ error actor_system_config::extract_config_file_path(string_list& args) {
args.erase(i);
return make_error(pec::missing_argument, std::move(str));
}
auto evalue = ptr->parse(path);
if (!evalue)
return std::move(evalue.error());
put(content, "config-file", *evalue);
ptr->store(*evalue);
return none;
config_value val{path};
if (auto err = ptr->sync(val); !err) {
put(content, "config-file", std::move(val));
return none;
} else {
return err;
}
}
const settings& content(const actor_system_config& cfg) {
......
......@@ -128,9 +128,13 @@ string_view config_option::full_name() const noexcept {
return buf_slice(buf_[0] == '?' ? 1 : 0, long_name_separator_);
}
error config_option::sync(config_value& x) const {
return meta_->sync(value_, x);
}
error config_option::store(const config_value& x) const {
CAF_ASSERT(meta_->store != nullptr);
return meta_->store(value_, x);
auto cpy = x;
return sync(cpy);
}
string_view config_option::type_name() const noexcept {
......@@ -146,7 +150,11 @@ bool config_option::has_flat_cli_name() const noexcept {
}
expected<config_value> config_option::parse(string_view input) const {
return meta_->parse(value_, input);
config_value val{input};
if (auto err = sync(val))
return {std::move(err)};
else
return {std::move(val)};
}
optional<config_value> config_option::get() const {
......
......@@ -18,8 +18,11 @@
#include "caf/config_option_adder.hpp"
#include "caf/config.hpp"
#include "caf/config_option_set.hpp"
CAF_PUSH_DEPRECATED_WARNING
namespace caf {
config_option_adder::config_option_adder(config_option_set& target,
......@@ -53,3 +56,5 @@ config_option_adder& config_option_adder::add_impl(config_option&& opt) {
}
} // namespace caf
CAF_POP_WARNINGS
......@@ -137,6 +137,12 @@ auto config_option_set::parse(settings& config, argument_iterator first,
// Parses an argument.
using iter = string::const_iterator;
auto consume = [&](const config_option& opt, iter arg_begin, iter arg_end) {
auto to_pec_code = [](const error& err) {
if (err.category() == type_id_v<pec>)
return static_cast<pec>(err.code());
else
return pec::invalid_argument;
};
// Extract option name and category.
auto opt_name = opt.long_name();
auto opt_ctg = opt.category();
......@@ -144,27 +150,31 @@ auto config_option_set::parse(settings& config, argument_iterator first,
auto& entry = opt_ctg == "global" ? config : select_entry(config, opt_ctg);
// Flags only consume the current element.
if (opt.is_flag()) {
if (arg_begin != arg_end)
if (arg_begin == arg_end) {
config_value cfg_true{true};
if (auto err = opt.sync(cfg_true); !err) {
entry[opt_name] = cfg_true;
return pec::success;
} else {
return to_pec_code(err);
}
} else {
return pec::invalid_argument;
config_value cfg_true{true};
opt.store(cfg_true);
entry[opt_name] = cfg_true;
}
} else {
if (arg_begin == arg_end)
if (arg_begin != arg_end) {
auto arg_size = static_cast<size_t>(std::distance(arg_begin, arg_end));
config_value val{string_view{std::addressof(*arg_begin), arg_size}};
if (auto err = opt.sync(val); !err) {
entry[opt_name] = std::move(val);
return pec::success;
} else {
return to_pec_code(err);
}
} else {
return pec::missing_argument;
auto slice_size = static_cast<size_t>(std::distance(arg_begin, arg_end));
string_view slice{&*arg_begin, slice_size};
auto val = opt.parse(slice);
if (!val) {
auto& err = val.error();
if (err.category() == type_id_v<pec>)
return static_cast<pec>(err.code());
return pec::invalid_argument;
}
opt.store(*val);
entry[opt_name] = std::move(*val);
}
return pec::success;
};
// We loop over the first N-1 values, because we always consider two
// arguments at once.
......
......@@ -159,7 +159,7 @@ pec config_consumer::value_impl(config_value&& x) {
// Sync with config option object if available.
if (options_ != nullptr)
if (auto opt = options_->qualified_name_lookup(category_, current_key_))
if (auto err = opt->store(x))
if (auto err = opt->sync(x))
return pec::type_mismatch;
// Insert / replace value in the map.
if (auto dict = get_if<settings>(&x)) {
......
......@@ -23,57 +23,20 @@
#include "caf/config_value.hpp"
#include "caf/optional.hpp"
#define DEFAULT_META(type, parse_fun) \
config_option::meta_state type##_meta_state{ \
default_config_option_check<type>, default_config_option_store<type>, \
get_impl<type>, parse_fun, \
detail::config_value_access_t<type>::type_name()};
using std::string;
namespace caf {
namespace detail {
expected<config_value> parse_impl(std::string* ptr, string_view str) {
// Parse quoted strings, otherwise consume the entire string.
auto e = str.end();
auto i = std::find_if(str.begin(), e, [](char c) { return !isspace(c); });
if (i == e) {
if (ptr != nullptr)
ptr->assign(i, e);
return config_value{std::string{i, e}};
}
if (*i == '"') {
if (ptr == nullptr) {
std::string tmp;
if (auto err = parse(str, tmp))
return err;
return config_value{std::move(tmp)};
} else {
if (auto err = parse(str, *ptr))
return err;
return config_value{*ptr};
}
}
if (ptr != nullptr)
ptr->assign(str.begin(), str.end());
return config_value{std::string{str.begin(), str.end()}};
}
} // namespace detail
namespace {
using meta_state = config_option::meta_state;
error bool_store_neg(void* ptr, const config_value& x) {
if (holds_alternative<bool>(x)) {
error bool_sync_neg(void* ptr, config_value& x) {
if (auto val = get_as<bool>(x)) {
x = config_value{*val};
if (ptr)
*static_cast<bool*>(ptr) = !get<bool>(x);
*static_cast<bool*>(ptr) = !*val;
return none;
} else {
return make_error(pec::type_mismatch);
return std::move(val.error());
}
}
......@@ -81,20 +44,22 @@ config_value bool_get_neg(const void* ptr) {
return config_value{!*static_cast<const bool*>(ptr)};
}
meta_state bool_neg_meta{bool_store_neg, bool_get_neg, nullptr,
meta_state bool_neg_meta{bool_sync_neg, bool_get_neg,
detail::config_value_access_t<bool>::type_name()};
template <uint64_t Denominator>
error store_timespan(void* ptr, const config_value& x) {
if (holds_alternative<timespan>(x)) {
error sync_timespan(void* ptr, config_value& x) {
if (auto val = get_as<timespan>(x)) {
x = config_value{*val};
if (ptr)
*static_cast<size_t*>(ptr) = static_cast<size_t>(get<timespan>(x).count())
/ Denominator;
return none;
} else {
return make_error(pec::type_mismatch);
return std::move(val.error());
}
}
template <uint64_t Denominator>
config_value get_timespan(const void* ptr) {
auto ival = static_cast<int64_t>(*static_cast<const size_t*>(ptr));
......@@ -102,10 +67,10 @@ config_value get_timespan(const void* ptr) {
return config_value{val};
}
meta_state us_res_meta{store_timespan<1000>, get_timespan<1000>, nullptr,
meta_state us_res_meta{sync_timespan<1000>, get_timespan<1000>,
detail::config_value_access_t<timespan>::type_name()};
meta_state ms_res_meta{store_timespan<1000000>, get_timespan<1000000>, nullptr,
meta_state ms_res_meta{sync_timespan<1000000>, get_timespan<1000000>,
detail::config_value_access_t<timespan>::type_name()};
} // namespace
......
......@@ -48,13 +48,13 @@ constexpr int64_t underflow() {
template <class T>
optional<T> read(string_view arg) {
auto co = make_config_option<T>(category, name, explanation);
auto res = co.parse(arg);
if (res && holds_alternative<T>(*res)) {
if (auto err = co.store(*res); !err)
return get<T>(*res);
}
return none;
auto result = T{};
auto co = make_config_option<T>(result, category, name, explanation);
config_value val{arg};
if (auto err = co.sync(val); !err)
return {std::move(result)};
else
return none;
}
// Unsigned integers.
......
......@@ -999,83 +999,6 @@ CAF_TEST(successful parsing) {
CAF_CHECK_EQUAL(get<di>(parse("{a=1,b=2}")), di({{"a", 1}, {"b", 2}}));
}
#define CHECK_CLI_PARSE(type, str, ...) \
do { \
/* Note: parse_impl from make_config_option.hpp internally dispatches */ \
/* to parse_cli. No need to replicate that wrapping code here. */ \
if (auto res = caf::detail::parse_impl<type>(nullptr, str)) { \
type expected_res{__VA_ARGS__}; \
if (auto unboxed = ::caf::get_if<type>(std::addressof(*res))) { \
if (*unboxed == expected_res) \
CAF_CHECK_PASSED("parse(" << str << ") == " << expected_res); \
else \
CAF_CHECK_FAILED(*unboxed << " != " << expected_res); \
} else { \
CAF_CHECK_FAILED(*res << " != " << expected_res); \
} \
} else { \
CAF_CHECK_FAILED("parse(" << str << ") -> " << res.error()); \
} \
} while (false)
#define CHECK_CLI_PARSE_FAILS(type, str) \
do { \
if (auto res = caf::detail::parse_impl<type>(nullptr, str)) { \
CAF_CHECK_FAILED("unexpected parser result: " << *res); \
} else { \
CAF_CHECK_PASSED("parse(" << str << ") == " << res.error()); \
} \
} while (false)
CAF_TEST(parsing via parse_cli enables shortcut syntax for some types) {
using ls = std::vector<string>; // List-of-strings.
using li = std::vector<int>; // List-of-integers.
using lli = std::vector<li>; // List-of-list-of-integers.
CAF_MESSAGE("lists can omit square brackets");
CHECK_CLI_PARSE(int, "123", 123);
CHECK_CLI_PARSE(li, "[ 1,2 , 3 ,]", 1, 2, 3);
CHECK_CLI_PARSE(li, "[ 1,2 , 3 ]", 1, 2, 3);
CHECK_CLI_PARSE(li, " 1,2 , 3 ,", 1, 2, 3);
CHECK_CLI_PARSE(li, " 1,2 , 3 ", 1, 2, 3);
CHECK_CLI_PARSE(li, " [ ] ", li{});
CHECK_CLI_PARSE(li, " ", li{});
CHECK_CLI_PARSE(li, "", li{});
CHECK_CLI_PARSE(li, "[123]", 123);
CHECK_CLI_PARSE(li, "123", 123);
CAF_MESSAGE("brackets must have matching opening/closing brackets");
CHECK_CLI_PARSE_FAILS(li, " 1,2 , 3 ,]");
CHECK_CLI_PARSE_FAILS(li, " 1,2 , 3 ]");
CHECK_CLI_PARSE_FAILS(li, "123]");
CHECK_CLI_PARSE_FAILS(li, "[ 1,2 , 3 ,");
CHECK_CLI_PARSE_FAILS(li, "[ 1,2 , 3 ");
CHECK_CLI_PARSE_FAILS(li, "[123");
CAF_MESSAGE("string lists can omit quotation marks");
CHECK_CLI_PARSE(string, R"_("123")_", "123");
CHECK_CLI_PARSE(string, R"_(123)_", "123");
CHECK_CLI_PARSE(ls, R"_([ "1 ","2" , "3" ,])_", "1 ", "2", "3");
CHECK_CLI_PARSE(ls, R"_([ 1,2 , 3 ,])_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_([ 1,2 , 3 ])_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( 1,2 , 3 ,)_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( 1,2 , 3 )_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( [ ] )_", ls{});
CHECK_CLI_PARSE(ls, R"_( )_", ls{});
CHECK_CLI_PARSE(ls, R"_(["abc"])_", "abc");
CHECK_CLI_PARSE(ls, R"_([abc])_", "abc");
CHECK_CLI_PARSE(ls, R"_("abc")_", "abc");
CHECK_CLI_PARSE(ls, R"_(abc)_", "abc");
CAF_MESSAGE("nested lists can omit the outer square brackets");
CHECK_CLI_PARSE(lli, "[[1, 2, 3, ], ]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[[1, 2, 3]]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[1, 2, 3, ]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[1, 2, 3]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[[1], [2]]", li({1}), li({2}));
CHECK_CLI_PARSE(lli, "[1], [2]", li({1}), li({2}));
CHECK_CLI_PARSE_FAILS(lli, "1");
CHECK_CLI_PARSE_FAILS(lli, "1, 2");
CHECK_CLI_PARSE_FAILS(lli, "[1, 2]]");
CHECK_CLI_PARSE_FAILS(lli, "[[1, 2]");
}
CAF_TEST(unsuccessful parsing) {
auto parse = [](const string& str) {
auto x = config_value::parse(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