Commit e8f069e8 authored by Dominik Charousset's avatar Dominik Charousset

Add parse function to config_value

parent 43da85a5
......@@ -98,6 +98,11 @@ public:
~config_value();
// -- parsing ----------------------------------------------------------------
/// Tries to parse a value from `str`.
static expected<config_value> parse(const std::string& str);
// -- properties -------------------------------------------------------------
/// Converts the value to a list with one element. Does nothing if the value
......
......@@ -19,7 +19,10 @@
#include "caf/config_value.hpp"
#include "caf/detail/parser/ec.hpp"
#include "caf/detail/parser/read_ini.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/expected.hpp"
namespace caf {
......@@ -38,10 +41,159 @@ const char* type_names[] {
} // namespace <anonymous>
// -- constructors, destructors, and assignment operators ----------------------
config_value::~config_value() {
// nop
}
// -- parsing ------------------------------------------------------------------
namespace {
struct abstract_consumer {
virtual ~abstract_consumer() {
// nop
}
template <class T>
void value(T&& x) {
value_impl(config_value{std::forward<T>(x)});
}
virtual void value_impl(config_value&& x) = 0;
};
struct list_consumer;
struct map_consumer : abstract_consumer {
using map_type = config_value::dictionary;
using iterator = map_type::iterator;
map_consumer begin_map() {
return {this};
}
void end_map() {
parent->value_impl(config_value{std::move(xs)});
}
list_consumer begin_list();
void key(std::string name) {
i = xs.emplace_hint(xs.end(), std::make_pair(std::move(name), config_value{}));
}
void value_impl(config_value&& x) override {
i->second = std::move(x);
}
map_consumer(abstract_consumer* ptr) : i(xs.end()), parent(ptr) {
// nop
}
map_consumer(map_consumer&& other)
: xs(std::move(other.xs)),
i(xs.end()),
parent(other.parent) {
// nop
}
map_type xs;
iterator i;
abstract_consumer* parent;
};
struct list_consumer : abstract_consumer {
void end_list() {
parent->value_impl(config_value{std::move(xs)});
}
map_consumer begin_map() {
return {this};
}
list_consumer begin_list() {
return {this};
}
void value_impl(config_value&& x) override {
xs.emplace_back(std::move(x));
}
list_consumer(abstract_consumer* ptr) : parent(ptr) {
// nop
}
list_consumer(list_consumer&& other)
: xs(std::move(other.xs)),
parent(other.parent) {
// nop
}
config_value::list xs;
abstract_consumer* parent;
};
list_consumer map_consumer::begin_list() {
return {this};
}
struct consumer : abstract_consumer {
config_value result;
map_consumer begin_map() {
return {this};
}
list_consumer begin_list() {
return {this};
}
void value_impl(config_value&& x) override {
result = std::move(x);
}
};
} // namespace <anonymous>
expected<config_value> config_value::parse(const std::string& str) {
using namespace detail;
auto i = str.begin();
auto e = str.end();
// Sanity check.
if (i == e)
return make_error(parser::ec::unexpected_eof);
// Skip to beginning of the argument.
while (isspace(*i))
if (++i == e)
return make_error(parser::ec::unexpected_eof);
// Dispatch to parser.
parser::state<std::string::const_iterator> res;
consumer f;
res.i = i;
res.e = e;
parser::read_ini_value(res, f);
if (res.code == detail::parser::ec::success)
return std::move(f.result);
// Assume an unescaped string unless the first character clearly indicates
// otherwise.
switch (*i) {
case '[':
case '{':
case '"':
case '\'':
return make_error(res.code);
default:
if (isdigit(*i))
return make_error(res.code);
return config_value{str};
}
}
// -- properties ---------------------------------------------------------------
void config_value::convert_to_list() {
if (holds_alternative<list>(data_))
return;
......
......@@ -189,3 +189,45 @@ CAF_TEST(heterogeneous dictionary) {
string_list nodes{"sun", "venus", "mercury", "earth", "mars"};
CAF_CHECK_EQUAL(get<string_list>(xs, "nodes.preload"), nodes);
}
CAF_TEST(successful parsing) {
auto parse = [](const std::string& str) {
auto x = config_value::parse(str);
if (!x)
CAF_FAIL("cannot parse" << str << ": assumed a result but got an error");
return std::move(*x);
};
using di = std::map<string, int>; // Dictionary-of-integers.
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.
using std::chrono::milliseconds;
CAF_CHECK_EQUAL(get<int64_t>(parse("123")), 123);
CAF_CHECK_EQUAL(get<int64_t>(parse("+123")), 123);
CAF_CHECK_EQUAL(get<int64_t>(parse("-1")), -1);
CAF_CHECK_EQUAL(get<double>(parse("1.")), 1.);
CAF_CHECK_EQUAL(get<atom_value>(parse("'abc'")), atom("abc"));
CAF_CHECK_EQUAL(get<string>(parse("\"abc\"")), "abc");
CAF_CHECK_EQUAL(get<string>(parse("abc")), "abc");
CAF_CHECK_EQUAL(get<li>(parse("[1, 2, 3]")), li({1, 2, 3}));
CAF_CHECK_EQUAL(get<ls>(parse("[abc, def, ghi]")), ls({"abc", "def", "ghi"}));
CAF_CHECK_EQUAL(get<lli>(parse("[[1, 2], [3]]")), lli({li{1, 2}, li{3}}));
CAF_CHECK_EQUAL(get<timespan>(parse("10ms")), milliseconds(10));
CAF_CHECK_EQUAL(get<di>(parse("{a=1,b=2}")), di({{"a", 1}, {"b", 2}}));
}
CAF_TEST(unsuccessful parsing) {
auto parse = [](const std::string& str) {
auto x = config_value::parse(str);
if (x)
CAF_FAIL("assumed an error but got a result");
return std::move(x.error());
};
using detail::parser::ec;
CAF_CHECK_EQUAL(parse("10msb"), ec::trailing_character);
CAF_CHECK_EQUAL(parse("10foo"), ec::trailing_character);
CAF_CHECK_EQUAL(parse("[1,"), ec::unexpected_eof);
CAF_CHECK_EQUAL(parse("{a=,"), ec::unexpected_character);
CAF_CHECK_EQUAL(parse("{a=1,"), ec::unexpected_eof);
CAF_CHECK_EQUAL(parse("{a=1 b=2}"), ec::unexpected_character);
}
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