Unverified Commit 4f186927 authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #873

Support [a.b] nesting and relax key name parsing
parents 04ba5ea6 6a41eed0
......@@ -201,8 +201,9 @@ template <class Iterator, class Sentinel, class Consumer>
void read_ini_section(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
using std::swap;
std::string tmp;
auto alnum = [](char x) { return isalnum(x) || x == '_'; };
auto alnum_or_dash = [](char x) {
return isalnum(x) || x == '-' || x == '_';
return isalnum(x) || x == '_' || x == '-';
};
auto emit_key = [&] {
std::string key;
......@@ -211,14 +212,15 @@ void read_ini_section(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
};
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consumer.end_map();
consumer.end_map();
});
// clang-format off
start();
// Dispatches to read sections, comments, or key/value pairs.
term_state(init) {
transition(init, " \t\n")
fsm_epsilon(read_ini_comment(ps, consumer), init, ';')
transition(read_key_name, alphanumeric_chars, tmp = ch)
transition(read_key_name, alnum, tmp = ch)
}
// Reads a key of a "key=value" line.
state(read_key_name) {
......@@ -244,6 +246,44 @@ void read_ini_section(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
transition(init, '\n')
}
fin();
// clang-format on
}
/// Reads a nested group, e.g., "[foo.bar]" would consume "[foo.]" in read_ini
/// and then delegate to this function for parsing "bar]".
template <class Iterator, class Sentinel, class Consumer>
void read_nested_group(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
using std::swap;
std::string key;
auto alnum = [](char x) { return isalnum(x) || x == '_'; };
auto alnum_or_dash = [](char x) {
return isalnum(x) || x == '_' || x == '-';
};
auto begin_section = [&]() -> decltype(consumer.begin_map()) {
consumer.key(std::move(key));
return consumer.begin_map();
};
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consumer.end_map();
});
// clang-format off
start();
// Scanning for first section.
state(init) {
epsilon(read_sub_section, alnum)
}
// Read the sub section key after reading an '[xxx.'.
state(read_sub_section) {
transition(read_sub_section, alnum_or_dash, key += ch)
fsm_transition(read_nested_group(ps, begin_section()), done, '.')
fsm_transition(read_ini_section(ps, begin_section()), done, ']')
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
/// Reads an INI formatted input.
......@@ -251,8 +291,9 @@ template <class Iterator, class Sentinel, class Consumer>
void read_ini(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
using std::swap;
std::string tmp{"global"};
auto alnum = [](char x) { return isalnum(x) || x == '_'; };
auto alnum_or_dash = [](char x) {
return isalnum(x) || x == '-' || x == '_';
return isalnum(x) || x == '_' || x == '-';
};
auto begin_section = [&]() -> decltype(consumer.begin_map()) {
std::string key;
......@@ -260,6 +301,7 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
consumer.key(std::move(key));
return consumer.begin_map();
};
// clang-format off
start();
// Scanning for first section.
term_state(init) {
......@@ -271,11 +313,12 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
// Read the section key after reading an '['.
state(start_section) {
transition(start_section, " \t")
transition(read_section_name, alphabetic_chars, tmp = ch)
transition(read_section_name, alnum, tmp = ch)
}
// Reads a section name such as "[foo]".
state(read_section_name) {
transition(read_section_name, alnum_or_dash, tmp += ch)
fsm_transition(read_nested_group(ps, begin_section()), return_to_global, '.')
epsilon(close_section)
}
// Wait for the closing ']', preceded by any number of whitespaces.
......@@ -287,6 +330,7 @@ void read_ini(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
epsilon(init, any_char, tmp = "global")
}
fin();
// clang-format on
}
} // namespace parser
......
......@@ -105,7 +105,12 @@ log_type make_log(Ts&&... xs) {
return log_type{std::forward<Ts>(xs)...};
}
// Tests basic functionality.
const char* ini0 = R"(
[1group]
1value=321
[_foo]
_bar=11
[logger]
padding= 10
file-name = "foobar.ini" ; our file name
......@@ -138,7 +143,18 @@ tcp://localhost:8080
>,<udp://remotehost?trust=false>]
)";
// clang-format off
const auto ini0_log = make_log(
"key: 1group",
"{",
"key: 1value",
"value (integer): 321",
"}",
"key: _foo",
"{",
"key: _bar",
"value (integer): 11",
"}",
"key: logger",
"{",
"key: padding",
......@@ -186,6 +202,45 @@ const auto ini0_log = make_log(
"]",
"}"
);
// clang-format on
// Tests nested parameters.
const char* ini1 = R"(
foo {
bar = {
value1 = 1
}
value2 = 2
}
[bar.foo]
value3 = 3
)";
// clang-format off
const auto ini1_log = make_log(
"key: global",
"{",
"key: foo",
"{",
"key: bar",
"{",
"key: value1",
"value (integer): 1",
"}",
"key: value2",
"value (integer): 2",
"}",
"}",
"key: bar",
"{",
"key: foo",
"{",
"key: value3",
"value (integer): 3",
"}",
"}"
);
// clang-format on
} // namespace <anonymous>
......@@ -206,6 +261,7 @@ CAF_TEST(section with valid key-value pairs) {
CAF_CHECK_EQUAL(parse(" [ foo ] "), make_log("key: foo", "{", "}"));
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(ini1), ini1_log);
}
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