Commit e8444968 authored by Dominik Charousset's avatar Dominik Charousset

Add support for binary literals in INI parser

parent 841d5d89
...@@ -24,16 +24,23 @@ ...@@ -24,16 +24,23 @@
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include "caf/string_algorithms.hpp"
namespace caf { namespace caf {
void detail::parse_ini(std::istream& raw_data, std::ostream& errors, void detail::parse_ini(std::istream& input, std::ostream& errors,
config_consumer consumer) { config_consumer consumer) {
std::string group; std::string group;
std::string line; std::string line;
size_t ln = 0; // line number size_t ln = 0; // line number
while (getline(raw_data, line)) { auto print = [&](const char* category, const char* str) {
errors << category << " in line " << ln << ": " << str << std::endl;
};
auto print_error = [&](const char* str) {
print("error", str);
};
auto print_warning = [&](const char* str) {
print("warning", str);
};
while (std::getline(input, line)) {
++ln; ++ln;
// get begin-of-line (bol) and end-of-line (eol), ignoring whitespaces // get begin-of-line (bol) and end-of-line (eol), ignoring whitespaces
auto eol = find_if_not(line.rbegin(), line.rend(), ::isspace).base(); auto eol = find_if_not(line.rbegin(), line.rend(), ::isspace).base();
...@@ -44,8 +51,7 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors, ...@@ -44,8 +51,7 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors,
// do we read a group name? // do we read a group name?
if (*bol == '[') { if (*bol == '[') {
if (*(eol - 1) != ']') if (*(eol - 1) != ']')
errors << "error in line " << ln << ": missing ] at end of line" print_error("missing ] at end of line");
<< std::endl;
else else
group.assign(bol + 1, eol - 1); group.assign(bol + 1, eol - 1);
// skip further processing of this line // skip further processing of this line
...@@ -53,23 +59,21 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors, ...@@ -53,23 +59,21 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors,
} }
// do we have a group name yet? (prohibit ungrouped values) // do we have a group name yet? (prohibit ungrouped values)
if (group.empty()) { if (group.empty()) {
errors << "error in line " << ln << ": value outside of a group" print_error("value outside of a group");
<< std::endl;
continue; continue;
} }
// position of the equal sign // position of the equal sign
auto eqs = find(bol, eol, '='); auto eqs = find(bol, eol, '=');
if (eqs == eol) { if (eqs == eol) {
errors << "error in line " << ln << ": no '=' found" << std::endl; print_error("no '=' found");
continue; continue;
} }
if (bol == eqs) { if (bol == eqs) {
errors << "error in line " << ln << ": line starting with '='" print_error("line starting with '='");
<< std::endl;
continue; continue;
} }
if ((eqs + 1) == eol) { if ((eqs + 1) == eol) {
errors << "error in line " << ln << ": line ends with '='" << std::endl; print_error("line ends with '='");
continue; continue;
} }
// our keys have the format "<group>.<config-name>" // our keys have the format "<group>.<config-name>"
...@@ -82,7 +86,9 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors, ...@@ -82,7 +86,9 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors,
// auto-detect what we are dealing with // auto-detect what we are dealing with
constexpr const char* true_str = "true"; constexpr const char* true_str = "true";
constexpr const char* false_str = "false"; constexpr const char* false_str = "false";
auto icase_eq = [](char x, char y) { return tolower(x) == tolower(y); }; auto icase_eq = [](char x, char y) {
return tolower(x) == tolower(y);
};
if (std::equal(bov, eol, true_str, icase_eq)) { if (std::equal(bov, eol, true_str, icase_eq)) {
consumer(std::move(key), true); consumer(std::move(key), true);
} else if (std::equal(bov, eol, false_str, icase_eq)) { } else if (std::equal(bov, eol, false_str, icase_eq)) {
...@@ -91,18 +97,18 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors, ...@@ -91,18 +97,18 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors,
// end-of-string iterator // end-of-string iterator
auto eos = eol - 1; auto eos = eol - 1;
if (bov == eos) { if (bov == eos) {
errors << "error in line " << ln << ": stray '\"'" << std::endl; print_error("stray '\"'");
continue; continue;
} }
if (*eos != '"') { if (*eos != '"') {
errors << "error in line " << ln print_error("string not terminated by '\"'");
<< ": string not terminated by '\"'" << std::endl;
continue; continue;
} }
// found a string, remove first and last char from string, // found a string, remove first and last char from string,
// start escaping string sequence // start escaping string sequence
auto last_char_backslash = false; auto last_char_backslash = false;
std::string result; std::string result;
result.reserve(static_cast<size_t>(std::distance(bov, eos)));
// skip leading " and iterate up to the trailing " // skip leading " and iterate up to the trailing "
++bov; ++bov;
for (; bov != eos; ++bov) { for (; bov != eos; ++bov) {
...@@ -124,57 +130,51 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors, ...@@ -124,57 +130,51 @@ void detail::parse_ini(std::istream& raw_data, std::ostream& errors,
result += *bov; result += *bov;
} }
} }
if (last_char_backslash) { if (last_char_backslash)
errors << "warning in line " << ln print_warning("trailing quotation mark escaped");
<< ": trailing quotation mark escaped" << std::endl;
}
consumer(std::move(key), std::move(result)); consumer(std::move(key), std::move(result));
} else { } else {
bool is_neg = *bov == '-'; bool is_neg = *bov == '-';
if (is_neg && ++bov == eol) { if (is_neg && ++bov == eol) {
errors << "error in line " << ln << ": '-' is not a number" print_error("'-' is not a number");
<< std::endl;
continue; continue;
} }
auto set_ival = [&](int base) -> bool { auto set_ival = [&](int base, int prefix_len, const char* err) {
char* e; char* e;
int64_t res = std::strtoll(&*bov, &e, base); int64_t res = std::strtoll(&*(bov + prefix_len), &e, base);
if (e == &*eol) { if (e != &*eol)
print_error(err);
else
consumer(std::move(key), is_neg ? -res : res); consumer(std::move(key), is_neg ? -res : res);
return true;
}
return false;
}; };
// are we dealing with a hex? auto set_dval = [&] {
const char* hex_prefix = "0x";
if (std::equal(hex_prefix, hex_prefix + 2, bov)) {
if (! set_ival(16)) {
errors << "error in line " << ln << ": invalid hex value"
<< std::endl;
}
} else if (all_of(bov, eol, ::isdigit)) {
// check for base 8 and 10
if (*bov == '0') {
if (! set_ival(8)) {
errors << "error in line " << ln << ": invalid oct value"
<< std::endl;
}
} else {
if (! set_ival(10)) {
errors << "error in line " << ln << ": invalid decimal value"
<< std::endl;
}
}
} else {
// try to parse a double value
char* e; char* e;
double res = std::strtod(&*bov, &e); double res = std::strtod(&*bov, &e);
if (e == &*eol) { if (e != &*eol)
print_error("invalid value");
else
consumer(std::move(key), is_neg ? -res : res); consumer(std::move(key), is_neg ? -res : res);
} else { };
errors << "error in line " << ln << ": invalid value" << std::endl; if (*bov == '0' && std::distance(bov, eol) > 1)
switch (*(bov + 1)) {
case 'x':
case 'X':
set_ival(16, 2, "invalid hex value");
break;
case 'b':
case 'B':
set_ival(2, 2, "invalid binary value");
break;
default:
if (all_of(bov, eol, ::isdigit))
set_ival(8, 1, "invalid oct value");
else
set_dval();
} }
} else if (all_of(bov, eol, ::isdigit))
set_ival(10, 0, "invalid decimal value");
else
set_dval();
} }
} }
} }
......
...@@ -56,7 +56,7 @@ foo=-0xff ...@@ -56,7 +56,7 @@ foo=-0xff
bar=034 bar=034
baz=-0.23 baz=-0.23
buzz=1E-34 buzz=1E-34
bazz=0b10101010110011
)__"; )__";
constexpr const char* case3 = R"__(" constexpr const char* case3 = R"__("
...@@ -73,6 +73,7 @@ some-int=42 ...@@ -73,6 +73,7 @@ some-int=42
some-string="hi there!\" some-string="hi there!\"
neg=- neg=-
wtf=0x3733T wtf=0x3733T
not-a-bin=0b101002
hu=0779 hu=0779
hop=--"hiho" hop=--"hiho"
)__"; )__";
...@@ -135,11 +136,12 @@ CAF_TEST(simple_ini) { ...@@ -135,11 +136,12 @@ CAF_TEST(simple_ini) {
CAF_TEST(numbers) { CAF_TEST(numbers) {
load(case2); load(case2);
CAF_CHECK(errors.empty()); CAF_CHECK(join(errors, "\n") == "");
CAF_CHECK(value_is("test.foo", -0xff)); CAF_CHECK(value_is("test.foo", -0xff));
CAF_CHECK(value_is("test.bar", 034)); CAF_CHECK(value_is("test.bar", 034));
CAF_CHECK(value_is("test.baz", -0.23)); CAF_CHECK(value_is("test.baz", -0.23));
CAF_CHECK(value_is("test.buzz", 1E-34)); CAF_CHECK(value_is("test.buzz", 1E-34));
CAF_CHECK(value_is("test.bazz", 10931));
} }
CAF_TEST(errors) { CAF_TEST(errors) {
...@@ -154,8 +156,9 @@ CAF_TEST(errors) { ...@@ -154,8 +156,9 @@ CAF_TEST(errors) {
CAF_CHECK(has_error("warning in line 12: trailing quotation mark escaped")); CAF_CHECK(has_error("warning in line 12: trailing quotation mark escaped"));
CAF_CHECK(has_error("error in line 13: '-' is not a number")); CAF_CHECK(has_error("error in line 13: '-' is not a number"));
CAF_CHECK(has_error("error in line 14: invalid hex value")); CAF_CHECK(has_error("error in line 14: invalid hex value"));
CAF_CHECK(has_error("error in line 15: invalid oct value")); CAF_CHECK(has_error("error in line 15: invalid binary value"));
CAF_CHECK(has_error("error in line 16: invalid value")); CAF_CHECK(has_error("error in line 16: invalid oct value"));
CAF_CHECK(has_error("error in line 17: invalid value"));
CAF_CHECK(values.size() == 2); CAF_CHECK(values.size() == 2);
CAF_CHECK(value_is("test.some-int", 42)); CAF_CHECK(value_is("test.some-int", 42));
CAF_CHECK(value_is("test.some-string", "hi there!")); CAF_CHECK(value_is("test.some-string", "hi there!"));
......
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