Commit ecb249a6 authored by Dominik Charousset's avatar Dominik Charousset

Extend new ini parser

parent 08b9088f
...@@ -121,3 +121,12 @@ ...@@ -121,3 +121,12 @@
ch = ps.current(); \ ch = ps.current(); \
goto s_##target; goto s_##target;
#define invoke_fsm_if(predicate, fsm_call, target) \
if (predicate(ch)) { \
fsm_call; \
if (ps.code > ec::trailing_character) \
return; \
ch = ps.current(); \
goto s_##target; \
}
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "caf/detail/parser/fsm.hpp" #include "caf/detail/parser/fsm.hpp"
#include "caf/detail/parser/is_char.hpp" #include "caf/detail/parser/is_char.hpp"
#include "caf/detail/parser/read_atom.hpp" #include "caf/detail/parser/read_atom.hpp"
#include "caf/detail/parser/read_bool.hpp"
#include "caf/detail/parser/read_number_or_timespan.hpp" #include "caf/detail/parser/read_number_or_timespan.hpp"
#include "caf/detail/parser/read_string.hpp" #include "caf/detail/parser/read_string.hpp"
#include "caf/detail/scope_guard.hpp" #include "caf/detail/scope_guard.hpp"
...@@ -40,6 +41,9 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -40,6 +41,9 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
auto is_alnum_or_dash = [](char x) { auto is_alnum_or_dash = [](char x) {
return isalnum(x) || x == '-' || x == '_'; return isalnum(x) || x == '-' || x == '_';
}; };
auto is_f_or_t = [](char x) {
return x == 'f' || x == 'F' || x == 't' || x == 'T';
};
bool in_section = false; bool in_section = false;
auto begin_section = [&] { auto begin_section = [&] {
if (in_section) if (in_section)
...@@ -50,14 +54,20 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -50,14 +54,20 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
swap(tmp, section_key); swap(tmp, section_key);
consumer.begin_section(std::move(section_key)); consumer.begin_section(std::move(section_key));
}; };
auto emit_key = [&] {
std::string key;
swap(tmp, key);
consumer.key(std::move(key));
};
auto g = make_scope_guard([&] { auto g = make_scope_guard([&] {
if (in_section) if (ps.code <= ec::trailing_character && in_section)
consumer.end_section(); consumer.end_section();
}); });
start(); start();
term_state(init) { term_state(init) {
input(is_char<' '>, init) input(is_char<' '>, init)
input(is_char<'\t'>, init) input(is_char<'\t'>, init)
input(is_char<'\n'>, init)
input(is_char<';'>, leading_comment) input(is_char<';'>, leading_comment)
input(is_char<'['>, start_section) input(is_char<'['>, start_section)
} }
...@@ -73,6 +83,7 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -73,6 +83,7 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
input(is_char<'\t'>, start_section) input(is_char<'\t'>, start_section)
action(isalpha, read_section_name, tmp = ch) action(isalpha, read_section_name, tmp = ch)
} }
// Reads a section name such as "[foo]".
state(read_section_name) { state(read_section_name) {
action(is_alnum_or_dash, read_section_name, tmp += ch) action(is_alnum_or_dash, read_section_name, tmp += ch)
epsilon(close_section) epsilon(close_section)
...@@ -90,11 +101,72 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -90,11 +101,72 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
input(is_char<'\n'>, dispatch) input(is_char<'\n'>, dispatch)
input(is_char<'['>, read_section_name) input(is_char<'['>, read_section_name)
input(is_char<';'>, comment) input(is_char<';'>, comment)
action(isalnum, read_key_name, tmp = ch)
} }
// Comment after section or value.
term_state(comment) { term_state(comment) {
input(is_char<'\n'>, dispatch) input(is_char<'\n'>, dispatch)
any_input(comment) any_input(comment)
} }
// Reads a key of a "key=value" line.
state(read_key_name) {
action(is_alnum_or_dash, read_key_name, tmp += ch)
epsilon(await_assignment)
}
// Reads the assignment operator in a "key=value" line.
state(await_assignment) {
input(is_char<' '>, await_assignment)
input(is_char<'\t'>, await_assignment)
action(is_char<'='>, await_value, emit_key())
}
// Reads the value in a "key=value" line.
state(await_value) {
input(is_char<' '>, await_value)
input(is_char<'\t'>, await_value)
action(is_char<'['>, read_list_value, consumer.begin_list())
invoke_fsm_if(is_char<'"'>, read_string(ps, consumer), await_eol)
invoke_fsm_if(is_char<'\''>, read_atom(ps, consumer), await_eol)
invoke_fsm_if(is_char<'.'>, read_number(ps, consumer), await_eol)
invoke_fsm_if(is_f_or_t, read_bool(ps, consumer), await_eol)
invoke_fsm_if(isdigit, read_number_or_timespan(ps, consumer), await_eol)
}
// Reads a list entry in a "key=[value1, value2, ...]" statement.
state(read_list_value) {
input(is_char<' '>, read_list_value)
input(is_char<'\t'>, read_list_value)
input(is_char<'\n'>, read_list_value)
action(is_char<']'>, await_eol, consumer.end_list())
invoke_fsm_if(is_char<'"'>, read_string(ps, consumer), after_list_value)
invoke_fsm_if(is_char<'\''>, read_atom(ps, consumer), after_list_value)
invoke_fsm_if(is_char<'.'>, read_number(ps, consumer), after_list_value)
invoke_fsm_if(is_f_or_t, read_bool(ps, consumer), after_list_value)
invoke_fsm_if(isdigit, read_number_or_timespan(ps, consumer), after_list_value)
input(is_char<';'>, comment_in_list)
}
state(comment_in_list) {
input(is_char<'\n'>, read_list_value)
any_input(comment_in_list)
}
// Scans for the next entry after reading a "," while parsing a list.
state(after_list_value) {
input(is_char<' '>, after_list_value)
input(is_char<'\t'>, after_list_value)
input(is_char<'\n'>, after_list_value)
input(is_char<','>, read_list_value)
action(is_char<']'>, await_eol, consumer.end_list())
input(is_char<';'>, comment_after_list_value)
}
state(comment_after_list_value) {
input(is_char<'\n'>, after_list_value)
any_input(comment_after_list_value)
}
// Waits for end-of-line after reading a value
term_state(await_eol) {
input(is_char<' '>, await_eol)
input(is_char<'\t'>, await_eol)
input(is_char<';'>, comment)
input(is_char<'\n'>, dispatch)
}
fin(); fin();
} }
......
...@@ -36,12 +36,33 @@ struct test_consumer { ...@@ -36,12 +36,33 @@ struct test_consumer {
log_type log; log_type log;
void begin_section(std::string name) { void begin_section(std::string name) {
name.insert(0, "begin section: "); add_entry("sec: ", std::move(name));
log.emplace_back(std::move(name));
} }
void end_section() { void end_section() {
log.emplace_back("end section"); log.emplace_back("eos");
}
void begin_list() {
log.emplace_back("lst");
}
void end_list() {
log.emplace_back("eol");
}
void key(std::string name) {
add_entry("key: ", std::move(name));
}
template <class T>
void value(T x) {
add_entry("value: ", deep_to_string(x));
}
void add_entry(const char* prefix, std::string str) {
str.insert(0, prefix);
log.emplace_back(std::move(str));
} }
}; };
...@@ -53,7 +74,7 @@ struct fixture { ...@@ -53,7 +74,7 @@ struct fixture {
res.e = str.end(); res.e = str.end();
detail::parser::read_ini(res, f); detail::parser::read_ini(res, f);
if (res.code == detail::parser::ec::success != expect_success) if (res.code == detail::parser::ec::success != expect_success)
CAF_FAIL("unexpected parser result state"); CAF_MESSAGE("unexpected parser result state: " << res.code);
return std::move(f.log); return std::move(f.log);
} }
}; };
...@@ -63,13 +84,54 @@ log_type make_log(Ts&&... xs) { ...@@ -63,13 +84,54 @@ log_type make_log(Ts&&... xs) {
return log_type{std::forward<Ts>(xs)...}; return log_type{std::forward<Ts>(xs)...};
} }
const char* ini0 = R"(
[logger]
padding= 10
file-name = "foobar.ini" ; our file name
[scheduler] ; more settings
timing = 2us ; using microsecond resolution
impl = 'foo';some atom
x_ =.123
some-bool=true
some-other-bool=false
some-list=[
; here we have some list entries
123,
23 ; twenty-three!
,
"abc",
'def', ; some comment and a trailing comma
]
)";
const auto ini0_log = make_log(
"sec: logger", "key: padding", "value: 10", "key: file-name",
"value: \"foobar.ini\"", "eos", "sec: scheduler", "key: timing",
"value: 2000ns", "key: impl", "value: 'foo'", "key: x_",
"value: " + deep_to_string(.123), "key: some-bool", "value: true",
"key: some-other-bool", "value: false", "key: some-list", "lst", "value: 123",
"value: 23", "value: \"abc\"", "value: 'def'", "eol", "eos");
} // namespace <anonymous> } // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(read_ini_tests, fixture) CAF_TEST_FIXTURE_SCOPE(read_ini_tests, fixture)
CAF_TEST(empty inits) { CAF_TEST(empty inis) {
CAF_CHECK_EQUAL(parse(";foo"), make_log()); CAF_CHECK_EQUAL(parse(";foo"), make_log());
CAF_CHECK_EQUAL(parse("[foo]"), make_log("begin section: foo", "end section")); CAF_CHECK_EQUAL(parse(""), make_log());
CAF_CHECK_EQUAL(parse(" "), make_log());
CAF_CHECK_EQUAL(parse(" \n "), make_log());
CAF_CHECK_EQUAL(parse(";hello\n;world"), make_log());
}
CAF_TEST(section with valid key-value pairs) {
CAF_CHECK_EQUAL(parse("[foo]"), make_log("sec: foo", "eos"));
CAF_CHECK_EQUAL(parse(" [foo]"), make_log("sec: foo", "eos"));
CAF_CHECK_EQUAL(parse(" [ foo] "), make_log("sec: foo", "eos"));
CAF_CHECK_EQUAL(parse(" [ foo ] "), make_log("sec: foo", "eos"));
CAF_CHECK_EQUAL(parse("\n[a-b];foo\n;bar"), make_log("sec: a-b", "eos"));
CAF_CHECK_EQUAL(parse(ini0), ini0_log);
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
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