Commit ace0227f authored by Dominik Charousset's avatar Dominik Charousset

Add generic parser for floating point only

parent c86a6cf6
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 <cstdint>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/add_ascii.hpp"
#include "caf/detail/parser/is_char.hpp"
#include "caf/detail/parser/is_digit.hpp"
#include "caf/detail/parser/state.hpp"
#include "caf/detail/parser/sub_ascii.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace caf {
namespace detail {
namespace parser {
/// Reads a floating point number (`float` or `double`).
/// @param ps The parser state.
/// @param consumer Sink for generated values.
/// @param start_value Allows another parser to pre-initialize this parser with
/// the pre-decimal value.
template <class Iterator, class Sentinel, class Consumer, class ValueType>
void read_floating_point(state<Iterator, Sentinel>& ps, Consumer& consumer,
optional<ValueType> start_value) {
// Any exponent larger than 511 always overflows.
static constexpr int max_double_exponent = 511;
// We assume a simple integer until proven wrong.
enum sign_t { plus, minus };
sign_t sign;
ValueType result;
if (start_value == none) {
sign = plus;
result = 0;
} else if (*start_value < 0) {
sign = minus;
result = -*start_value;
} else {
sign = plus;
result = *start_value;
}
// Adjusts our mantissa, e.g., 1.23 becomes 123 with a dec_exp of -2.
auto dec_exp = 0;
// Exponent part of a floating point literal.
auto exp = 0;
// Computes the result on success.
auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
// Compute final floating point number.
// 1) Fix the exponent.
exp += dec_exp;
// 2) Check whether exponent is in valid range.
if (exp < -max_double_exponent) {
ps.code = pec::exponent_underflow;
return;
}
if (exp > max_double_exponent) {
ps.code = pec::exponent_overflow;
return;
}
// 3) Scale result.
// Pre-computed powers of 10 for the scaling loop.
static double powerTable[] = {1e1, 1e2, 1e4, 1e8, 1e16,
1e32, 1e64, 1e128, 1e256};
auto i = 0;
if (exp < 0) {
for (auto n = -exp; n != 0; n >>= 1, ++i)
if (n & 0x01)
result /= powerTable[i];
} else {
for (auto n = exp; n != 0; n >>= 1, ++i)
if (n & 0x01)
result *= powerTable[i];
}
// 4) Fix sign and call consumer.
consumer.value(sign == plus ? result : -result);
}
});
// Reads the a decimal place.
auto rd_decimal = [&](char c) {
--dec_exp;
return add_ascii<10>(result, c);
};
// clang-format off
// Definition of our parser FSM.
start();
unstable_state(init) {
epsilon_if(start_value == none, regular_init)
epsilon(after_dec)
}
state(regular_init) {
transition(regular_init, " \t")
transition(has_sign, '+')
transition(has_sign, '-', sign = minus)
epsilon(has_sign)
}
// "+" or "-" alone aren't numbers.
state(has_sign) {
transition(leading_dot, '.')
transition(zero, '0')
epsilon(dec)
}
term_state(zero) {
transition(trailing_dot, '.')
}
// Reads the integer part of the mantissa or a positive decimal integer.
term_state(dec) {
transition(dec, decimal_chars, add_ascii<10>(result, ch),
pec::integer_overflow)
epsilon(after_dec)
}
state(after_dec) {
transition(has_e, "eE")
transition(trailing_dot, '.')
}
// ".", "+.", etc. aren't valid numbers, so this state isn't terminal.
state(leading_dot) {
transition(after_dot, decimal_chars, rd_decimal(ch), pec::exponent_underflow)
}
// "1." is a valid number, so a trailing dot is a terminal state.
term_state(trailing_dot) {
epsilon(after_dot)
}
// Read the decimal part of a mantissa.
term_state(after_dot) {
transition(after_dot, decimal_chars, rd_decimal(ch), pec::exponent_underflow)
transition(has_e, "eE")
}
// "...e", "...e+", and "...e-" aren't valid numbers, so these states are not
// terminal.
state(has_e) {
transition(has_plus_after_e, '+')
transition(has_minus_after_e, '-')
transition(pos_exp, decimal_chars, add_ascii<10>(exp, ch),
pec::exponent_overflow)
}
state(has_plus_after_e) {
transition(pos_exp, decimal_chars, add_ascii<10>(exp, ch),
pec::exponent_overflow)
}
state(has_minus_after_e) {
transition(neg_exp, decimal_chars, sub_ascii<10>(exp, ch),
pec::exponent_underflow)
}
// Read a positive exponent.
term_state(pos_exp) {
transition(pos_exp, decimal_chars, add_ascii<10>(exp, ch),
pec::exponent_overflow)
}
// Read a negative exponent.
term_state(neg_exp) {
transition(neg_exp, decimal_chars, sub_ascii<10>(exp, ch),
pec::exponent_underflow)
}
fin();
// clang-format on
}
template <class Iterator, class Sentinel, class Consumer>
void read_floating_point(state<Iterator, Sentinel>& ps, Consumer& consumer) {
using value_type = typename Consumer::value_type;
return read_floating_point(ps, consumer, optional<value_type>{});
}
} // namespace parser
} // namespace detail
} // namespace caf
#include "caf/detail/parser/fsm_undef.hpp"
CAF_POP_WARNINGS
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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_floating_point
#include "caf/detail/parser/read_floating_point.hpp"
#include "caf/test/dsl.hpp"
#include <string>
#include "caf/detail/parser/state.hpp"
#include "caf/pec.hpp"
using namespace caf;
namespace {
struct double_consumer {
using value_type = double;
void value(double y) {
x = y;
}
double x;
};
optional<double> read(string_view str) {
double_consumer consumer;
detail::parser::state<string_view::iterator> ps{str.begin(), str.end()};
detail::parser::read_floating_point(ps, consumer);
if (ps.code != pec::success)
return none;
return consumer.x;
}
} // namespace
CAF_TEST(predecimal only) {
CAF_CHECK_EQUAL(read("0"), 0.);
CAF_CHECK_EQUAL(read("+0"), 0.);
CAF_CHECK_EQUAL(read("-0"), 0.);
CAF_CHECK_EQUAL(read("1"), 1.);
CAF_CHECK_EQUAL(read("+1"), 1.);
CAF_CHECK_EQUAL(read("-1"), -1.);
CAF_CHECK_EQUAL(read("12"), 12.);
CAF_CHECK_EQUAL(read("+12"), 12.);
CAF_CHECK_EQUAL(read("-12"), -12.);
}
CAF_TEST(trailing dot) {
CAF_CHECK_EQUAL(read("0."), 0.);
CAF_CHECK_EQUAL(read("1."), 1.);
CAF_CHECK_EQUAL(read("+1."), 1.);
CAF_CHECK_EQUAL(read("-1."), -1.);
CAF_CHECK_EQUAL(read("12."), 12.);
CAF_CHECK_EQUAL(read("+12."), 12.);
CAF_CHECK_EQUAL(read("-12."), -12.);
}
CAF_TEST(leading dot) {
CAF_CHECK_EQUAL(read(".0"), .0);
CAF_CHECK_EQUAL(read(".1"), .1);
CAF_CHECK_EQUAL(read("+.1"), .1);
CAF_CHECK_EQUAL(read("-.1"), -.1);
CAF_CHECK_EQUAL(read(".12"), .12);
CAF_CHECK_EQUAL(read("+.12"), .12);
CAF_CHECK_EQUAL(read("-.12"), -.12);
}
CAF_TEST(regular noation) {
CAF_CHECK_EQUAL(read("0.0"), .0);
CAF_CHECK_EQUAL(read("1.2"), 1.2);
CAF_CHECK_EQUAL(read("1.23"), 1.23);
CAF_CHECK_EQUAL(read("12.34"), 12.34);
}
CAF_TEST(scientific noation) {
CAF_CHECK_EQUAL(read("1e2"), 1e2);
CAF_CHECK_EQUAL(read("+1e2"), 1e2);
CAF_CHECK_EQUAL(read("+1e+2"), 1e2);
CAF_CHECK_EQUAL(read("-1e2"), -1e2);
CAF_CHECK_EQUAL(read("-1e+2"), -1e2);
CAF_CHECK_EQUAL(read("12e-3"), 12e-3);
CAF_CHECK_EQUAL(read("+12e-3"), 12e-3);
CAF_CHECK_EQUAL(read("-12e-3"), -12e-3);
}
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