Commit 7362662d authored by Dominik Charousset's avatar Dominik Charousset

Modularize INI parser

parent ecb249a6
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include <ctype.h> #include <ctype.h>
#include <stack>
#include "caf/detail/parser/ec.hpp" #include "caf/detail/parser/ec.hpp"
#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"
...@@ -33,7 +35,97 @@ namespace caf { ...@@ -33,7 +35,97 @@ namespace caf {
namespace detail { namespace detail {
namespace parser { namespace parser {
/// Reads an INI formateed input and produces a series of key/value pairs. // Example input:
//
// [section1]
// value1 = 123
// value2 = "string"
// subsection1 = {
// value3 = 1.23
// value4 = 4e20
// }
// [section2]
// value5 = 'atom'
// value6 = [1, 'two', "three", {
// a = "b",
// b = "c",
// }]
//
template <class Iterator, class Sentinel, class Consumer>
void read_ini_comment(state<Iterator, Sentinel>& ps, Consumer&) {
start();
state(init) {
input(is_char<';'>, await_newline)
}
term_state(await_newline) {
input(is_char<'\n'>, done)
any_input(await_newline)
}
term_state(done) {
// nop
}
fin();
}
template <class Iterator, class Sentinel, class Consumer>
void read_ini_value(state<Iterator, Sentinel>& ps, Consumer& consumer);
template <class Iterator, class Sentinel, class Consumer>
void read_ini_list(state<Iterator, Sentinel>& ps, Consumer& consumer) {
start();
state(init) {
action(is_char<'['>, before_value, consumer.begin_list())
}
state(before_value) {
input(is_char<' '>, before_value)
input(is_char<'\t'>, before_value)
input(is_char<'\n'>, before_value)
action(is_char<']'>, done, consumer.end_list())
invoke_fsm_if(is_char<';'>, read_ini_comment(ps, consumer), before_value)
invoke_fsm(read_ini_value(ps, consumer), after_value)
}
state(after_value) {
input(is_char<' '>, after_value)
input(is_char<'\t'>, after_value)
input(is_char<'\n'>, after_value)
input(is_char<','>, before_value)
action(is_char<']'>, done, consumer.end_list())
invoke_fsm_if(is_char<';'>, read_ini_comment(ps, consumer), after_value)
}
term_state(done) {
// nop
}
fin();
}
template <class Iterator, class Sentinel, class Consumer>
void read_ini_map(state<Iterator, Sentinel>& ps, Consumer& consumer) {
// TODO: implement me
}
template <class Iterator, class Sentinel, class Consumer>
void read_ini_value(state<Iterator, Sentinel>& ps, Consumer& consumer) {
auto is_f_or_t = [](char x) {
return x == 'f' || x == 'F' || x == 't' || x == 'T';
};
start();
state(init) {
invoke_fsm_if(is_char<'"'>, read_string(ps, consumer), done)
invoke_fsm_if(is_char<'\''>, read_atom(ps, consumer), done)
invoke_fsm_if(is_char<'.'>, read_number(ps, consumer), done)
invoke_fsm_if(is_f_or_t, read_bool(ps, consumer), done)
invoke_fsm_if(isdigit, read_number_or_timespan(ps, consumer), done)
invoke_fsm_if(is_char<'['>, read_ini_list(ps, consumer), done)
invoke_fsm_if(is_char<'{'>, read_ini_map(ps, consumer), done)
}
term_state(done) {
// nop
}
fin();
}
/// Reads an INI formatted input.
template <class Iterator, class Sentinel, class Consumer> template <class Iterator, class Sentinel, class Consumer>
void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
using std::swap; using std::swap;
...@@ -41,42 +133,33 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -41,42 +133,33 @@ 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 = [&] {
if (in_section)
consumer.end_section();
else
in_section = true;
std::string section_key;
swap(tmp, section_key);
consumer.begin_section(std::move(section_key));
};
auto emit_key = [&] { auto emit_key = [&] {
std::string key; std::string key;
swap(tmp, key); swap(tmp, key);
consumer.key(std::move(key)); consumer.key(std::move(key));
}; };
auto begin_section = [&] {
if (in_section)
consumer.end_map();
else
in_section = true;
emit_key();
consumer.begin_map();
};
auto g = make_scope_guard([&] { auto g = make_scope_guard([&] {
if (ps.code <= ec::trailing_character && in_section) if (ps.code <= ec::trailing_character && in_section)
consumer.end_section(); consumer.end_map();
}); });
start(); start();
// Scanning for first section.
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<'\n'>, init)
input(is_char<';'>, leading_comment) invoke_fsm_if(is_char<';'>, read_ini_comment(ps, consumer), init)
input(is_char<'['>, start_section) input(is_char<'['>, start_section)
} }
// A comment before the first section starts. Jumps back to init after
// hitting newline.
term_state(leading_comment) {
input(is_char<'\n'>, init)
any_input(leading_comment)
}
// Read the section key after reading an '['. // Read the section key after reading an '['.
state(start_section) { state(start_section) {
input(is_char<' '>, start_section) input(is_char<' '>, start_section)
...@@ -100,14 +183,9 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -100,14 +183,9 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
input(is_char<'\t'>, dispatch) input(is_char<'\t'>, dispatch)
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) invoke_fsm_if(is_char<';'>, read_ini_comment(ps, consumer), dispatch)
action(isalnum, read_key_name, tmp = ch) action(isalnum, read_key_name, tmp = ch)
} }
// Comment after section or value.
term_state(comment) {
input(is_char<'\n'>, dispatch)
any_input(comment)
}
// Reads a key of a "key=value" line. // Reads a key of a "key=value" line.
state(read_key_name) { state(read_key_name) {
action(is_alnum_or_dash, read_key_name, tmp += ch) action(is_alnum_or_dash, read_key_name, tmp += ch)
...@@ -123,48 +201,13 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) { ...@@ -123,48 +201,13 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer& consumer) {
state(await_value) { state(await_value) {
input(is_char<' '>, await_value) input(is_char<' '>, await_value)
input(is_char<'\t'>, await_value) input(is_char<'\t'>, await_value)
action(is_char<'['>, read_list_value, consumer.begin_list()) invoke_fsm(read_ini_value(ps, consumer), await_eol)
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 // Waits for end-of-line after reading a value
term_state(await_eol) { term_state(await_eol) {
input(is_char<' '>, await_eol) input(is_char<' '>, await_eol)
input(is_char<'\t'>, await_eol) input(is_char<'\t'>, await_eol)
input(is_char<';'>, comment) invoke_fsm_if(is_char<';'>, read_ini_comment(ps, consumer), dispatch)
input(is_char<'\n'>, dispatch) input(is_char<'\n'>, dispatch)
} }
fin(); fin();
......
...@@ -35,20 +35,20 @@ using log_type = std::vector<std::string>; ...@@ -35,20 +35,20 @@ using log_type = std::vector<std::string>;
struct test_consumer { struct test_consumer {
log_type log; log_type log;
void begin_section(std::string name) { void begin_map() {
add_entry("sec: ", std::move(name)); log.emplace_back("{");
} }
void end_section() { void end_map() {
log.emplace_back("eos"); log.emplace_back("}");
} }
void begin_list() { void begin_list() {
log.emplace_back("lst"); log.emplace_back("[");
} }
void end_list() { void end_list() {
log.emplace_back("eol"); log.emplace_back("]");
} }
void key(std::string name) { void key(std::string name) {
...@@ -106,12 +106,12 @@ some-list=[ ...@@ -106,12 +106,12 @@ some-list=[
)"; )";
const auto ini0_log = make_log( const auto ini0_log = make_log(
"sec: logger", "key: padding", "value: 10", "key: file-name", "key: logger", "{", "key: padding", "value: 10", "key: file-name",
"value: \"foobar.ini\"", "eos", "sec: scheduler", "key: timing", "value: \"foobar.ini\"", "}", "key: scheduler", "{", "key: timing",
"value: 2000ns", "key: impl", "value: 'foo'", "key: x_", "value: 2000ns", "key: impl", "value: 'foo'", "key: x_",
"value: " + deep_to_string(.123), "key: some-bool", "value: true", "value: " + deep_to_string(.123), "key: some-bool", "value: true",
"key: some-other-bool", "value: false", "key: some-list", "lst", "value: 123", "key: some-other-bool", "value: false", "key: some-list", "[", "value: 123",
"value: 23", "value: \"abc\"", "value: 'def'", "eol", "eos"); "value: 23", "value: \"abc\"", "value: 'def'", "]", "}");
} // namespace <anonymous> } // namespace <anonymous>
...@@ -126,11 +126,11 @@ CAF_TEST(empty inis) { ...@@ -126,11 +126,11 @@ CAF_TEST(empty inis) {
} }
CAF_TEST(section with valid key-value pairs) { 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("key: foo", "{", "}"));
CAF_CHECK_EQUAL(parse(" [foo]"), make_log("sec: foo", "eos")); CAF_CHECK_EQUAL(parse(" [foo]"), make_log("key: foo", "{", "}"));
CAF_CHECK_EQUAL(parse(" [ foo] "), make_log("sec: foo", "eos")); CAF_CHECK_EQUAL(parse(" [ foo] "), make_log("key: foo", "{", "}"));
CAF_CHECK_EQUAL(parse(" [ foo ] "), make_log("sec: foo", "eos")); CAF_CHECK_EQUAL(parse(" [ foo ] "), make_log("key: foo", "{", "}"));
CAF_CHECK_EQUAL(parse("\n[a-b];foo\n;bar"), make_log("sec: a-b", "eos")); CAF_CHECK_EQUAL(parse("\n[a-b];foo\n;bar"), make_log("key: a-b", "{", "}"));
CAF_CHECK_EQUAL(parse(ini0), ini0_log); CAF_CHECK_EQUAL(parse(ini0), ini0_log);
} }
......
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