Commit b901e279 authored by Dominik Charousset's avatar Dominik Charousset

Add a parser for producing numbers or timespans

parent f5109d55
......@@ -33,10 +33,10 @@ enum class ec : uint8_t {
unexpected_eof,
/// Parser stopped after reading an unexpected character.
unexpected_character,
/// Cannot construct a negative caf::duration.
negative_duration,
/// Cannot construct a caf::duration with a value exceeding uint32_t.
duration_overflow,
/// Parsed integer exceeds the number of available bits of a `timespan`.
timespan_overflow,
/// Tried constructing a `timespan` with from a floating point number.
fractional_timespan,
/// Too many characters for an atom.
too_many_characters,
/// Unrecognized character after escaping `\`.
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <chrono>
#include <cstdint>
#include <string>
#include "caf/none.hpp"
#include "caf/optional.hpp"
#include "caf/timestamp.hpp"
#include "caf/variant.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/parser/ec.hpp"
#include "caf/detail/parser/fsm.hpp"
#include "caf/detail/parser/is_char.hpp"
#include "caf/detail/parser/read_number.hpp"
#include "caf/detail/parser/state.hpp"
namespace caf {
namespace detail {
namespace parser {
/// Reads a number or a duration, i.e., on success produces an `int64_t`, a
/// `double`, or a `timespan`.
template <class Iterator, class Sentinel, class Consumer>
void read_number_or_timespan(state<Iterator, Sentinel>& ps,
Consumer& consumer) {
using namespace std::chrono;
struct interim_consumer {
variant<none_t, int64_t, double> interim;
void value(int64_t x) {
interim = x;
}
void value(double x) {
interim = x;
}
};
optional<timespan> res;
interim_consumer ic;
auto has_int = [&] { return holds_alternative<int64_t>(ic.interim); };
auto has_dbl = [&] { return holds_alternative<double>(ic.interim); };
auto get_int = [&] { return get<int64_t>(ic.interim); };
auto g = make_scope_guard([&] {
if (ps.code <= ec::trailing_character) {
if (res != none) {
consumer.value(*res);
} else if (!holds_alternative<none_t>(ic.interim)) {
if (has_int())
consumer.value(get_int());
else
consumer.value(get<double>(ic.interim));
}
}
});
start();
state(init) {
invoke_fsm(read_number(ps, ic), has_number)
}
term_state(has_number) {
checked_epsilon(has_int(), has_integer)
checked_epsilon(has_dbl(), has_double)
}
term_state(has_double) {
invalid_input(is_char<'u'>, ec::fractional_timespan)
invalid_input(is_char<'n'>, ec::fractional_timespan)
invalid_input(is_char<'m'>, ec::fractional_timespan)
invalid_input(is_char<'s'>, ec::fractional_timespan)
}
term_state(has_integer) {
input(is_char<'u'>, have_u)
input(is_char<'n'>, have_n)
input(is_char<'m'>, have_m)
action(is_char<'s'>, done, res = seconds(get_int()))
}
state(have_u) {
action(is_char<'s'>, done, res = microseconds(get_int()))
}
state(have_n) {
action(is_char<'s'>, done, res = nanoseconds(get_int()))
}
state(have_m) {
input(is_char<'i'>, have_mi)
action(is_char<'s'>, done, res = milliseconds(get_int()))
}
state(have_mi) {
action(is_char<'n'>, done, res = minutes(get_int()))
}
term_state(done) {
// nop
}
fin();
}
} // namespace parser
} // namespace detail
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE read_number_or_timespan
#include <string>
#include "caf/test/unit_test.hpp"
#include "caf/variant.hpp"
#include "caf/detail/parser/read_number_or_timespan.hpp"
using namespace caf;
using namespace std::chrono;
using detail::parser::ec;
namespace {
struct number_or_timespan_parser_consumer {
variant<int64_t, double, timespan> x;
template <class T>
void value(T y) {
x = y;
}
};
struct res_t {
variant<ec, double, int64_t, timespan> val;
template <class T>
res_t(T&& x) : val(std::forward<T>(x)) {
// nop
}
};
std::string to_string(const res_t& x) {
return deep_to_string(x.val);
}
bool operator==(const res_t& x, const res_t& y) {
if (x.val.index() != y.val.index())
return false;
// Implements a safe equal comparison for double.
caf::test::equal_to f;
using caf::get;
using caf::holds_alternative;
if (holds_alternative<ec>(x.val))
return f(get<ec>(x.val), get<ec>(y.val));
if (holds_alternative<double>(x.val))
return f(get<double>(x.val), get<double>(y.val));
if (holds_alternative<int64_t>(x.val))
return f(get<int64_t>(x.val), get<int64_t>(y.val));
return f(get<timespan>(x.val), get<timespan>(y.val));
}
struct number_or_timespan_parser {
res_t operator()(std::string str) {
detail::parser::state<std::string::iterator> res;
number_or_timespan_parser_consumer f;
res.i = str.begin();
res.e = str.end();
detail::parser::read_number_or_timespan(res, f);
if (res.code == ec::success)
return f.x;
return res.code;
}
};
struct fixture {
number_or_timespan_parser p;
};
template <class T>
typename std::enable_if<std::is_integral<T>::value, res_t>::type res(T x) {
return {static_cast<int64_t>(x)};
}
template <class T>
typename std::enable_if<std::is_floating_point<T>::value, res_t>::type
res(T x) {
return {static_cast<double>(x)};
}
template <class Rep, class Period>
res_t res(std::chrono::duration<Rep, Period> x) {
return std::chrono::duration_cast<timespan>(x);
}
} // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(read_number_or_timespan_tests, fixture)
CAF_TEST(valid numbers and timespans) {
CAF_CHECK_EQUAL(p("123"), res(123));
CAF_CHECK_EQUAL(p("123.456"), res(123.456));
CAF_CHECK_EQUAL(p("123s"), res(seconds(123)));
CAF_CHECK_EQUAL(p("123ns"), res(nanoseconds(123)));
CAF_CHECK_EQUAL(p("123ms"), res(milliseconds(123)));
CAF_CHECK_EQUAL(p("123us"), res(microseconds(123)));
CAF_CHECK_EQUAL(p("123min"), res(minutes(123)));
}
CAF_TEST(invalid timespans) {
CAF_CHECK_EQUAL(p("12.3s"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3n"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3ns"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3m"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3ms"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3n"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3ns"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3mi"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("12.3min"), ec::fractional_timespan);
CAF_CHECK_EQUAL(p("123ss"), ec::trailing_character);
CAF_CHECK_EQUAL(p("123m"), ec::unexpected_eof);
CAF_CHECK_EQUAL(p("123mi"), ec::unexpected_eof);
CAF_CHECK_EQUAL(p("123u"), ec::unexpected_eof);
CAF_CHECK_EQUAL(p("123n"), ec::unexpected_eof);
}
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