Commit bda7b361 authored by Dominik Charousset's avatar Dominik Charousset

Extend number parser with support for ranges

parent 2381afd4
...@@ -38,7 +38,11 @@ constexpr bool in_whitelist(char whitelist, char ch) { ...@@ -38,7 +38,11 @@ constexpr bool in_whitelist(char whitelist, char ch) {
} }
inline bool in_whitelist(const char* whitelist, char ch) { inline bool in_whitelist(const char* whitelist, char ch) {
return strchr(whitelist, ch) != nullptr; // Note: using strchr breaks if `ch == '\0'`.
for (char c = *whitelist++; c != '\0'; c = *whitelist++)
if (c == ch)
return true;
return false;
} }
inline bool in_whitelist(bool (*filter)(char), char ch) { inline bool in_whitelist(bool (*filter)(char), char ch) {
......
...@@ -273,18 +273,30 @@ ...@@ -273,18 +273,30 @@
#endif // CAF_MSVC #endif // CAF_MSVC
/// Makes a transition into another state if the `statement` is true. /// Enables a transition into another state if the `statement` is true.
#define transition_if(statement, ...) \ #define transition_if(statement, ...) \
if (statement) { \ if (statement) { \
transition(__VA_ARGS__) \ transition(__VA_ARGS__) \
} }
/// Makes an epsiolon transition into another state if the `statement` is true. /// Enables a transition if the constexpr `statement` is true.
#define transition_static_if(statement, ...) \
if constexpr (statement) { \
transition(__VA_ARGS__) \
}
/// Enables an epsiolon transition if the `statement` is true.
#define epsilon_if(statement, ...) \ #define epsilon_if(statement, ...) \
if (statement) { \ if (statement) { \
epsilon(__VA_ARGS__) \ epsilon(__VA_ARGS__) \
} }
/// Enables an epsiolon transition if the constexpr `statement` is true.
#define epsilon_static_if(statement, ...) \
if constexpr (statement) { \
epsilon(__VA_ARGS__) \
}
/// Makes an transition transition into another FSM if `statement` is true, /// Makes an transition transition into another FSM if `statement` is true,
/// resuming at state `target`. /// resuming at state `target`.
#define fsm_transition_if(statement, ...) \ #define fsm_transition_if(statement, ...) \
...@@ -292,9 +304,23 @@ ...@@ -292,9 +304,23 @@
fsm_transition(__VA_ARGS__) \ fsm_transition(__VA_ARGS__) \
} }
/// Makes an transition transition into another FSM if `statement` is true,
/// resuming at state `target`.
#define fsm_transition_static_if(statement, ...) \
if constexpr (statement) { \
fsm_transition(__VA_ARGS__) \
}
/// Makes an epsilon transition into another FSM if `statement` is true, /// Makes an epsilon transition into another FSM if `statement` is true,
/// resuming at state `target`. /// resuming at state `target`.
#define fsm_epsilon_if(statement, ...) \ #define fsm_epsilon_if(statement, ...) \
if (statement) { \ if (statement) { \
fsm_epsilon(__VA_ARGS__) \ fsm_epsilon(__VA_ARGS__) \
} }
/// Makes an epsilon transition into another FSM if `statement` is true,
/// resuming at state `target`.
#define fsm_epsilon_static_if(statement, ...) \
if constexpr (statement) { \
fsm_epsilon(__VA_ARGS__) \
}
...@@ -112,7 +112,8 @@ void read_floating_point(State& ps, Consumer&& consumer, ...@@ -112,7 +112,8 @@ void read_floating_point(State& ps, Consumer&& consumer,
start(); start();
unstable_state(init) { unstable_state(init) {
epsilon_if(start_value == none, regular_init) epsilon_if(start_value == none, regular_init)
epsilon(after_dec) epsilon(after_dec, "eE.")
epsilon(after_dot, any_char)
} }
state(regular_init) { state(regular_init) {
transition(regular_init, " \t") transition(regular_init, " \t")
......
...@@ -19,8 +19,10 @@ ...@@ -19,8 +19,10 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <type_traits>
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/consumer.hpp"
#include "caf/detail/parser/add_ascii.hpp" #include "caf/detail/parser/add_ascii.hpp"
#include "caf/detail/parser/chars.hpp" #include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/is_char.hpp" #include "caf/detail/parser/is_char.hpp"
...@@ -36,10 +38,25 @@ CAF_PUSH_UNUSED_LABEL_WARNING ...@@ -36,10 +38,25 @@ CAF_PUSH_UNUSED_LABEL_WARNING
namespace caf::detail::parser { namespace caf::detail::parser {
/// Reads the second half of 'n..m' range statement.
///
/// Expect the current position to point at the number *after* the dots:
///
/// ~~~
/// foo = [1..2]
/// ~~~^
/// ~~~
template <class State, class Consumer>
void read_number_range(State& ps, Consumer& consumer, int64_t begin);
/// Reads a number, i.e., on success produces either an `int64_t` or a /// Reads a number, i.e., on success produces either an `int64_t` or a
/// `double`. /// `double`.
template <class State, class Consumer> template <class State, class Consumer, class EnableFloat = std::true_type,
void read_number(State& ps, Consumer& consumer) { class EnableRange = std::false_type>
void read_number(State& ps, Consumer& consumer, EnableFloat = {},
EnableRange = {}) {
static constexpr bool enable_float = EnableFloat::value;
static constexpr bool enable_range = EnableRange::value;
// Our result when reading an integer number. // Our result when reading an integer number.
int64_t result = 0; int64_t result = 0;
// Computes the result on success. // Computes the result on success.
...@@ -47,6 +64,7 @@ void read_number(State& ps, Consumer& consumer) { ...@@ -47,6 +64,7 @@ void read_number(State& ps, Consumer& consumer) {
if (ps.code <= pec::trailing_character) if (ps.code <= pec::trailing_character)
consumer.value(result); consumer.value(result);
}); });
using odbl = optional<double>;
// clang-format off // clang-format off
// Definition of our parser FSM. // Definition of our parser FSM.
start(); start();
...@@ -54,17 +72,22 @@ void read_number(State& ps, Consumer& consumer) { ...@@ -54,17 +72,22 @@ void read_number(State& ps, Consumer& consumer) {
transition(init, " \t") transition(init, " \t")
transition(has_plus, '+') transition(has_plus, '+')
transition(has_minus, '-') transition(has_minus, '-')
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}),
done, '.', g.disable())
epsilon(has_plus) epsilon(has_plus)
} }
// "+" or "-" alone aren't numbers. // "+" or "-" alone aren't numbers.
state(has_plus) { state(has_plus) {
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{0.}), fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}),
done, '.', g.disable()) done, '.', g.disable())
transition(pos_zero, '0') transition(pos_zero, '0')
epsilon(pos_dec) epsilon(pos_dec)
} }
state(has_minus) { state(has_minus) {
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{0.}, true), fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}, true),
done, '.', g.disable()) done, '.', g.disable())
transition(neg_zero, '0') transition(neg_zero, '0')
epsilon(neg_dec) epsilon(neg_dec)
...@@ -73,15 +96,13 @@ void read_number(State& ps, Consumer& consumer) { ...@@ -73,15 +96,13 @@ void read_number(State& ps, Consumer& consumer) {
term_state(pos_zero) { term_state(pos_zero) {
transition(start_pos_bin, "bB") transition(start_pos_bin, "bB")
transition(start_pos_hex, "xX") transition(start_pos_hex, "xX")
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{0.}), transition_static_if(enable_float || enable_range, pos_dot, '.')
done, '.', g.disable())
epsilon(pos_oct) epsilon(pos_oct)
} }
term_state(neg_zero) { term_state(neg_zero) {
transition(start_neg_bin, "bB") transition(start_neg_bin, "bB")
transition(start_neg_hex, "xX") transition(start_neg_hex, "xX")
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{0.}, true), transition_static_if(enable_float || enable_range, neg_dot, '.')
done, '.', g.disable())
epsilon(neg_oct) epsilon(neg_oct)
} }
// Binary integers. // Binary integers.
...@@ -131,19 +152,101 @@ void read_number(State& ps, Consumer& consumer) { ...@@ -131,19 +152,101 @@ void read_number(State& ps, Consumer& consumer) {
term_state(pos_dec) { term_state(pos_dec) {
transition(pos_dec, decimal_chars, add_ascii<10>(result, ch), transition(pos_dec, decimal_chars, add_ascii<10>(result, ch),
pec::integer_overflow) pec::integer_overflow)
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{result}), fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}),
done, "eE", g.disable()) done, "eE", g.disable())
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{result}), transition_static_if(enable_float || enable_range, pos_dot, '.')
done, '.', g.disable())
} }
// Reads the integer part of the mantissa or a negative decimal integer. // Reads the integer part of the mantissa or a negative decimal integer.
term_state(neg_dec) { term_state(neg_dec) {
transition(neg_dec, decimal_chars, sub_ascii<10>(result, ch), transition(neg_dec, decimal_chars, sub_ascii<10>(result, ch),
pec::integer_underflow) pec::integer_underflow)
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{result}, true), fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, "eE", g.disable()) done, "eE", g.disable())
fsm_epsilon(read_floating_point(ps, consumer, optional<double>{result}, true), transition_static_if(enable_float || enable_range, neg_dot, '.')
}
unstable_state(pos_dot) {
fsm_transition_static_if(enable_range,
read_number_range(ps, consumer, result),
done, '.', g.disable()) done, '.', g.disable())
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}),
done, any_char, g.disable())
epsilon(done)
}
unstable_state(neg_dot) {
fsm_transition_static_if(enable_range,
read_number_range(ps, consumer, result),
done, '.', g.disable())
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, any_char, g.disable())
epsilon(done)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_number_range(State& ps, Consumer& consumer, int64_t begin) {
optional<int64_t> end;
optional<int64_t> step;
auto end_consumer = make_consumer(end);
auto step_consumer = make_consumer(step);
auto generate_2 = [&](int64_t n, int64_t m) {
if (n <= m)
while (n <= m)
consumer.value(n++);
else
while (n >= m)
consumer.value(n--);
};
auto generate_3 = [&](int64_t n, int64_t m, int64_t s) {
if (n == m) {
consumer.value(n);
return;
}
if (s == 0 || (n > m && s > 0) || (n < m && s < 0)) {
ps.code = pec::invalid_range_expression;
return;
}
if (n <= m)
for (auto i = n; i <= m; i += s)
consumer.value(i);
else
for (auto i = n; i >= m; i += s)
consumer.value(i);
};
auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
if (!end) {
ps.code = pec::invalid_range_expression;
} else if (!step) {
generate_2(begin, *end);
} else {
generate_3(begin, *end, *step);
}
}
});
static constexpr std::false_type no_float = std::false_type{};
// clang-format off
// Definition of our parser FSM.
start();
state(init) {
fsm_epsilon(read_number(ps, end_consumer, no_float), after_end_num)
}
term_state(after_end_num) {
transition(first_dot, '.')
}
state(first_dot) {
transition(second_dot, '.')
}
state(second_dot) {
fsm_epsilon(read_number(ps, step_consumer, no_float), done)
} }
term_state(done) { term_state(done) {
// nop // nop
......
...@@ -72,7 +72,9 @@ enum class pec : uint8_t { ...@@ -72,7 +72,9 @@ enum class pec : uint8_t {
repeated_field_name, repeated_field_name,
/// Stopped while reading a user-defined type with one or more missing /// Stopped while reading a user-defined type with one or more missing
/// mandatory fields. /// mandatory fields.
missing_field, missing_field = 20,
/// Parsing a range statement ('n..m' or 'n..m..step') failed.
invalid_range_expression,
}; };
/// @relates pec /// @relates pec
......
...@@ -36,16 +36,26 @@ using namespace caf; ...@@ -36,16 +36,26 @@ using namespace caf;
namespace { namespace {
struct numbers_parser_consumer { struct number_consumer {
variant<int64_t, double> x; variant<int64_t, double> x;
inline void value(double y) { void value(double y) {
x = y; x = y;
} }
inline void value(int64_t y) { void value(int64_t y) {
x = y; x = y;
} }
}; };
struct range_consumer {
std::vector<int64_t> xs;
void value(double) {
CAF_FAIL("range consumer called with a double");
}
void value(int64_t y) {
xs.emplace_back(y);
}
};
struct res_t { struct res_t {
variant<pec, double, int64_t> val; variant<pec, double, int64_t> val;
template <class T> template <class T>
...@@ -54,6 +64,10 @@ struct res_t { ...@@ -54,6 +64,10 @@ struct res_t {
} }
}; };
std::string to_string(const res_t& x) {
return caf::visit([](auto& y) { return deep_to_string(y); }, x.val);
}
bool operator==(const res_t& x, const res_t& y) { bool operator==(const res_t& x, const res_t& y) {
if (x.val.index() != y.val.index()) if (x.val.index() != y.val.index())
return false; return false;
...@@ -70,7 +84,7 @@ bool operator==(const res_t& x, const res_t& y) { ...@@ -70,7 +84,7 @@ bool operator==(const res_t& x, const res_t& y) {
struct numbers_parser { struct numbers_parser {
res_t operator()(string_view str) { res_t operator()(string_view str) {
numbers_parser_consumer f; number_consumer f;
string_parser_state res{str.begin(), str.end()}; string_parser_state res{str.begin(), str.end()};
detail::parser::read_number(res, f); detail::parser::read_number(res, f);
if (res.code == pec::success) if (res.code == pec::success)
...@@ -79,6 +93,17 @@ struct numbers_parser { ...@@ -79,6 +93,17 @@ struct numbers_parser {
} }
}; };
struct range_parser {
expected<std::vector<int64_t>> operator()(string_view str) {
range_consumer f;
string_parser_state res{str.begin(), str.end()};
detail::parser::read_number(res, f, std::true_type{}, std::true_type{});
if (res.code == pec::success)
return std::move(f.xs);
return make_error(res);
}
};
template <class T> template <class T>
typename std::enable_if<std::is_integral<T>::value, res_t>::type res(T x) { typename std::enable_if<std::is_integral<T>::value, res_t>::type res(T x) {
return {static_cast<int64_t>(x)}; return {static_cast<int64_t>(x)};
...@@ -92,6 +117,7 @@ res(T x) { ...@@ -92,6 +117,7 @@ res(T x) {
struct fixture { struct fixture {
numbers_parser p; numbers_parser p;
range_parser r;
}; };
} // namespace } // namespace
...@@ -205,12 +231,18 @@ CAF_TEST(floating point numbers) { ...@@ -205,12 +231,18 @@ CAF_TEST(floating point numbers) {
CHECK_NUMBER(0.0); CHECK_NUMBER(0.0);
CHECK_NUMBER(.0); CHECK_NUMBER(.0);
CHECK_NUMBER(0.); CHECK_NUMBER(0.);
CHECK_NUMBER(1.1);
CHECK_NUMBER(.1);
CHECK_NUMBER(1.);
CHECK_NUMBER(0.123); CHECK_NUMBER(0.123);
CHECK_NUMBER(.123); CHECK_NUMBER(.123);
CHECK_NUMBER(123.456); CHECK_NUMBER(123.456);
CHECK_NUMBER(-0.0); CHECK_NUMBER(-0.0);
CHECK_NUMBER(-.0); CHECK_NUMBER(-.0);
CHECK_NUMBER(-0.); CHECK_NUMBER(-0.);
CHECK_NUMBER(-1.1);
CHECK_NUMBER(-.1);
CHECK_NUMBER(-1.);
CHECK_NUMBER(-0.123); CHECK_NUMBER(-0.123);
CHECK_NUMBER(-.123); CHECK_NUMBER(-.123);
CHECK_NUMBER(-123.456); CHECK_NUMBER(-123.456);
...@@ -269,4 +301,44 @@ CAF_TEST(fractional mantissa with negative exponent) { ...@@ -269,4 +301,44 @@ CAF_TEST(fractional mantissa with negative exponent) {
CHECK_NUMBER(-42.0001e-5); CHECK_NUMBER(-42.0001e-5);
} }
#define CHECK_RANGE(expr, ...) \
CAF_CHECK_EQUAL(r(expr), std::vector<int64_t>({__VA_ARGS__}))
CAF_TEST(a range from n to n is just n) {
CHECK_RANGE("0..0", 0);
CHECK_RANGE("1..1", 1);
CHECK_RANGE("2..2", 2);
CHECK_RANGE("101..101", 101);
CHECK_RANGE("101..101..1", 101);
CHECK_RANGE("101..101..2", 101);
CHECK_RANGE("101..101..-1", 101);
CHECK_RANGE("101..101..-2", 101);
}
CAF_TEST(ranges are either ascending or descending) {
CHECK_RANGE("0..1", 0, 1);
CHECK_RANGE("0..2", 0, 1, 2);
CHECK_RANGE("0..3", 0, 1, 2, 3);
CHECK_RANGE("3..0", 3, 2, 1, 0);
CHECK_RANGE("3..1", 3, 2, 1);
CHECK_RANGE("3..2", 3, 2);
}
CAF_TEST(ranges can use positive step values) {
CHECK_RANGE("2..6..2", 2, 4, 6);
CHECK_RANGE("3..8..3", 3, 6);
}
CAF_TEST(ranges can use negative step values) {
CHECK_RANGE("6..2..-2", 6, 4, 2);
CHECK_RANGE("8..3..-3", 8, 5);
}
CAF_TEST(ranges can use signed integers) {
CHECK_RANGE("+2..+6..+2", 2, 4, 6);
CHECK_RANGE("+6..+2..-2", 6, 4, 2);
CHECK_RANGE("+2..-2..-2", 2, 0, -2);
CHECK_RANGE("-2..+2..+2", -2, 0, 2);
}
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