Commit cad4baef authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/json-unsigned'

parents 6bbcebf8 7271c19d
...@@ -5,6 +5,21 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -5,6 +5,21 @@ is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased] ## [Unreleased]
### Added
- The class `json_value` can now hold unsigned 64-bit integer values. This
allows it to store values that would otherwise overflow a signed integer.
Values that can be represented in both integer types will return `true` for
`is_integer()` as well as for the new `is_unsigned()` function. Users can
obtain the stored value as `uint64_t` via `to_unsigned()`.
### Changed
- With the addition of the unsigned type to `json_value`, there is now a new
edge case where `is_number()` returns `true` but neither `is_integer()` nor
`is_double()` return `true`: integer values larger than `INT64_MAX` will only
return true for `is_unsigned()`.
### Fixed ### Fixed
- Fix flow setup for servers that use `web_socket::with`. This bug caused - Fix flow setup for servers that use `web_socket::with`. This bug caused
......
...@@ -10,26 +10,28 @@ ...@@ -10,26 +10,28 @@
namespace caf::detail { namespace caf::detail {
template <class To, bool LargeUnsigned = sizeof(To) >= sizeof(int64_t) template <class To>
&& std::is_unsigned<To>::value>
struct bounds_checker { struct bounds_checker {
static constexpr bool check(int64_t x) noexcept { template <class From>
return x >= std::numeric_limits<To>::min() static constexpr bool check(From x) noexcept {
&& x <= std::numeric_limits<To>::max(); auto converted = static_cast<To>(x);
} if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
}; // If the source and target types have the same signedness, we can simply
// check whether the value is in the range of the target type.
template <> return static_cast<From>(converted) == x;
struct bounds_checker<int64_t, false> { } else if constexpr (std::is_signed_v<From>) {
static constexpr bool check(int64_t) noexcept { // If the source type is signed and the target type is unsigned, we need
return true; // to check that the value is not negative. Otherwise, the conversion
// could yield a positive value, which is out of range for the target
// type.
return x >= 0 && static_cast<From>(converted) == x;
} else {
static_assert(std::is_signed_v<To>);
// If the source type is unsigned and the target type is signed, we need
// to check whether the conversion produced a non-negative value. If the
// value is negative, the conversion is out of range.
return converted >= 0 && static_cast<From>(converted) == x;
} }
};
template <class To>
struct bounds_checker<To, true> {
static constexpr bool check(int64_t x) noexcept {
return x >= 0;
} }
}; };
......
...@@ -102,8 +102,16 @@ public: ...@@ -102,8 +102,16 @@ public:
template <class T> template <class T>
pec value(T&& x) { pec value(T&& x) {
using val_t = std::decay_t<T>;
if constexpr (std::is_same_v<val_t, uint64_t>) {
if (x <= INT64_MAX)
return value_impl(config_value{static_cast<int64_t>(x)});
else
return pec::integer_overflow;
} else {
return value_impl(config_value{std::forward<T>(x)}); return value_impl(config_value{std::forward<T>(x)});
} }
}
const std::string& current_key() { const std::string& current_key() {
return current_key_; return current_key_;
......
...@@ -4,10 +4,15 @@ ...@@ -4,10 +4,15 @@
#pragma once #pragma once
#include "caf/pec.hpp"
#include <optional>
#include <type_traits>
#include <utility> #include <utility>
namespace caf::detail { namespace caf::detail {
/// Consumes a single value.
template <class T> template <class T>
class consumer { class consumer {
public: public:
...@@ -17,17 +22,117 @@ public: ...@@ -17,17 +22,117 @@ public:
// nop // nop
} }
void value(T&& y) { template <class U>
x_ = std::move(y); void value(U&& y) {
x_ = std::forward<U>(y);
} }
private: private:
T& x_; T& x_;
}; };
/// Specializes `consumer` for `int64_t` with a safe conversion from `uint64_t`.
template <>
class consumer<int64_t> {
public:
using value_type = int64_t;
explicit consumer(int64_t& x) : x_(x) {
// nop
}
void value(int64_t y) noexcept {
x_ = y;
}
pec value(uint64_t y) noexcept {
if (y < INT64_MAX) {
value(static_cast<int64_t>(y));
return pec::success;
}
return pec::integer_overflow;
}
private:
int64_t& x_;
};
/// Specializes `consumer` for `uint64_t` with a safe conversion from `int64_t`.
template <>
class consumer<uint64_t> {
public:
using value_type = uint64_t;
explicit consumer(uint64_t& x) : x_(x) {
// nop
}
void value(uint64_t y) noexcept {
x_ = y;
}
pec value(int64_t y) noexcept {
if (y >= 0) {
value(static_cast<uint64_t>(y));
return pec::success;
}
return pec::integer_underflow;
}
private:
uint64_t& x_;
};
template <class... Ts>
class consumer<std::variant<Ts...>> {
public:
explicit consumer(std::variant<Ts...>& x) : x_(x) {
// nop
}
template <class T>
void value(T&& y) {
x_ = std::forward<T>(y);
}
private:
std::variant<Ts...>& x_;
};
template <class T>
class consumer<std::optional<T>> {
public:
using value_type = T;
explicit consumer(std::optional<T>& x) : x_(x) {
// nop
}
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
std::optional<T>& x_;
};
template <class T> template <class T>
consumer<T> make_consumer(T& x) { consumer<T> make_consumer(T& x) {
return consumer<T>{x}; return consumer<T>{x};
} }
/// Applies a consumer to a value and updates the error code if necessary.
template <class Consumer, class T>
static void apply_consumer(Consumer&& consumer, T&& value, pec& code) {
using res_t = decltype(consumer.value(std::forward<T>(value)));
if constexpr (std::is_same_v<res_t, void>) {
consumer.value(std::forward<T>(value));
} else {
auto res = consumer.value(std::forward<T>(value));
if (res != pec::success)
code = res;
}
}
} // namespace caf::detail } // namespace caf::detail
...@@ -333,24 +333,26 @@ public: ...@@ -333,24 +333,26 @@ public:
using object_allocator = object::allocator_type; using object_allocator = object::allocator_type;
using data_type = std::variant<null_t, int64_t, double, bool, using data_type = std::variant<null_t, int64_t, uint64_t, double, bool,
std::string_view, array, object, undefined_t>; std::string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0; static constexpr size_t null_index = 0;
static constexpr size_t integer_index = 1; static constexpr size_t integer_index = 1;
static constexpr size_t double_index = 2; static constexpr size_t unsigned_index = 2;
static constexpr size_t bool_index = 3; static constexpr size_t double_index = 3;
static constexpr size_t string_index = 4; static constexpr size_t bool_index = 4;
static constexpr size_t array_index = 5; static constexpr size_t string_index = 5;
static constexpr size_t object_index = 6; static constexpr size_t array_index = 6;
static constexpr size_t undefined_index = 7; static constexpr size_t object_index = 7;
static constexpr size_t undefined_index = 8;
data_type data; data_type data;
...@@ -362,6 +364,10 @@ public: ...@@ -362,6 +364,10 @@ public:
return data.index() == integer_index; return data.index() == integer_index;
} }
bool is_unsigned() const noexcept {
return data.index() == unsigned_index;
}
bool is_double() const noexcept { bool is_double() const noexcept {
return data.index() == double_index; return data.index() == double_index;
} }
...@@ -470,10 +476,10 @@ bool save(Serializer& sink, const value& val) { ...@@ -470,10 +476,10 @@ bool save(Serializer& sink, const value& val) {
if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>)) if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false; return false;
// Maps our type indexes to their public API counterpart. // Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>, type_id_t mapping[]
type_id_v<double>, type_id_v<bool>, = {type_id_v<unit_t>, type_id_v<int64_t>, type_id_v<uint64_t>,
type_id_v<std::string>, type_id_v<json_array>, type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
type_id_v<json_object>, type_id_v<none_t>}; type_id_v<json_array>, type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types. // Act as-if this type is a variant of the mapped types.
auto type_index = val.data.index(); auto type_index = val.data.index();
if (!sink.begin_field("value", make_span(mapping), type_index)) if (!sink.begin_field("value", make_span(mapping), type_index))
...@@ -484,6 +490,10 @@ bool save(Serializer& sink, const value& val) { ...@@ -484,6 +490,10 @@ bool save(Serializer& sink, const value& val) {
if (!sink.apply(std::get<int64_t>(val.data))) if (!sink.apply(std::get<int64_t>(val.data)))
return false; return false;
break; break;
case value::unsigned_index:
if (!sink.apply(std::get<uint64_t>(val.data)))
return false;
break;
case value::double_index: case value::double_index:
if (!sink.apply(std::get<double>(val.data))) if (!sink.apply(std::get<double>(val.data)))
return false; return false;
...@@ -552,10 +562,10 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) { ...@@ -552,10 +562,10 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>)) if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false; return false;
// Maps our type indexes to their public API counterpart. // Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>, type_id_t mapping[]
type_id_v<double>, type_id_v<bool>, = {type_id_v<unit_t>, type_id_v<int64_t>, type_id_v<uint64_t>,
type_id_v<std::string>, type_id_v<json_array>, type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
type_id_v<json_object>, type_id_v<none_t>}; type_id_v<json_array>, type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types. // Act as-if this type is a variant of the mapped types.
auto type_index = size_t{0}; auto type_index = size_t{0};
if (!source.begin_field("value", make_span(mapping), type_index)) if (!source.begin_field("value", make_span(mapping), type_index))
...@@ -572,6 +582,13 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) { ...@@ -572,6 +582,13 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
val.data = tmp; val.data = tmp;
break; break;
} }
case value::unsigned_index: {
auto tmp = uint64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::double_index: { case value::double_index: {
auto tmp = double{0}; auto tmp = double{0};
if (!source.apply(tmp)) if (!source.apply(tmp))
...@@ -738,6 +755,9 @@ void print_to(Buffer& buf, const value& val, size_t indentation_factor, ...@@ -738,6 +755,9 @@ void print_to(Buffer& buf, const value& val, size_t indentation_factor,
case value::integer_index: case value::integer_index:
print(buf, std::get<int64_t>(val.data)); print(buf, std::get<int64_t>(val.data));
break; break;
case value::unsigned_index:
print(buf, std::get<uint64_t>(val.data));
break;
case value::double_index: case value::double_index:
print(buf, std::get<double>(val.data)); print(buf, std::get<double>(val.data));
break; break;
......
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
#pragma once #pragma once
#include <cstdint>
#include <type_traits>
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/consumer.hpp" #include "caf/detail/consumer.hpp"
#include "caf/detail/parser/add_ascii.hpp" #include "caf/detail/parser/add_ascii.hpp"
...@@ -16,8 +13,12 @@ ...@@ -16,8 +13,12 @@
#include "caf/detail/parser/read_floating_point.hpp" #include "caf/detail/parser/read_floating_point.hpp"
#include "caf/detail/parser/sub_ascii.hpp" #include "caf/detail/parser/sub_ascii.hpp"
#include "caf/detail/scope_guard.hpp" #include "caf/detail/scope_guard.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/pec.hpp" #include "caf/pec.hpp"
#include <cstdint>
#include <type_traits>
CAF_PUSH_UNUSED_LABEL_WARNING CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp" #include "caf/detail/parser/fsm.hpp"
...@@ -32,14 +33,12 @@ namespace caf::detail::parser { ...@@ -32,14 +33,12 @@ namespace caf::detail::parser {
/// foo = [1..2] /// foo = [1..2]
/// ~~~^ /// ~~~^
/// ~~~ /// ~~~
template <class State, class Consumer> template <class State, class Consumer, class ValueType>
void read_number_range(State& ps, Consumer& consumer, int64_t begin); void read_number_range(State& ps, Consumer& consumer, ValueType begin);
/// Reads a number, i.e., on success produces either an `int64_t` or a
/// `double`.
template <class State, class Consumer, class EnableFloat = std::true_type, template <class State, class Consumer, class EnableFloat = std::true_type,
class EnableRange = std::false_type> class EnableRange = std::false_type>
void read_number(State& ps, Consumer& consumer, EnableFloat = {}, void read_negative_number(State& ps, Consumer& consumer, EnableFloat = {},
EnableRange = {}) { EnableRange = {}) {
static constexpr bool enable_float = EnableFloat::value; static constexpr bool enable_float = EnableFloat::value;
static constexpr bool enable_range = EnableRange::value; static constexpr bool enable_range = EnableRange::value;
...@@ -48,36 +47,98 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -48,36 +47,98 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
// Computes the result on success. // Computes the result on success.
auto g = caf::detail::make_scope_guard([&] { auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character) if (ps.code <= pec::trailing_character)
consumer.value(result); apply_consumer(consumer, result, ps.code);
}); });
using odbl = std::optional<double>; using odbl = std::optional<double>;
// clang-format off // clang-format off
// Definition of our parser FSM.
start(); start();
// Initial state.
state(init) { state(init) {
transition(init, " \t")
transition(has_plus, '+')
transition(has_minus, '-')
fsm_epsilon_static_if(enable_float, fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}), read_floating_point(ps, consumer, odbl{0.}, true),
done, '.', g.disable())
transition(neg_zero, '0')
epsilon(neg_dec)
}
// Disambiguate base.
term_state(neg_zero) {
transition(start_neg_bin, "bB")
transition(start_neg_hex, "xX")
transition_static_if(enable_float || enable_range, neg_dot, '.')
epsilon(neg_oct)
}
// Binary integers.
state(start_neg_bin) {
epsilon(neg_bin)
}
term_state(neg_bin) {
transition(neg_bin, "01", sub_ascii<2>(result, ch), pec::integer_underflow)
}
// Octal integers.
state(start_neg_oct) {
epsilon(neg_oct)
}
term_state(neg_oct) {
transition(neg_oct, octal_chars, sub_ascii<8>(result, ch),
pec::integer_underflow)
}
// Hexal integers.
state(start_neg_hex) {
epsilon(neg_hex)
}
term_state(neg_hex) {
transition(neg_hex, hexadecimal_chars, sub_ascii<16>(result, ch),
pec::integer_underflow)
}
// Reads the integer part of the mantissa or a negative decimal integer.
term_state(neg_dec) {
transition(neg_dec, decimal_chars, sub_ascii<10>(result, ch),
pec::integer_underflow)
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, "eE", g.disable())
transition_static_if(enable_float || enable_range, neg_dot, '.')
}
unstable_state(neg_dot) {
fsm_transition_static_if(enable_range,
read_number_range(ps, consumer, result),
done, '.', g.disable()) done, '.', g.disable())
epsilon(has_plus) fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, any_char, g.disable())
epsilon(done)
} }
// "+" or "-" alone aren't numbers. term_state(done) {
state(has_plus) { // nop
}
fin();
// clang-format on
}
template <class State, class Consumer, class EnableFloat = std::true_type,
class EnableRange = std::false_type>
void read_positive_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.
uint64_t result = 0;
// Computes the result on success.
auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
apply_consumer(consumer, result, ps.code);
});
using odbl = std::optional<double>;
// clang-format off
start();
// Initial state.
state(init) {
fsm_epsilon_static_if(enable_float, fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}), 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) {
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}, true),
done, '.', g.disable())
transition(neg_zero, '0')
epsilon(neg_dec)
}
// Disambiguate base. // Disambiguate base.
term_state(pos_zero) { term_state(pos_zero) {
transition(start_pos_bin, "bB") transition(start_pos_bin, "bB")
...@@ -85,12 +146,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -85,12 +146,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition_static_if(enable_float || enable_range, pos_dot, '.') transition_static_if(enable_float || enable_range, pos_dot, '.')
epsilon(pos_oct) epsilon(pos_oct)
} }
term_state(neg_zero) {
transition(start_neg_bin, "bB")
transition(start_neg_hex, "xX")
transition_static_if(enable_float || enable_range, neg_dot, '.')
epsilon(neg_oct)
}
// Binary integers. // Binary integers.
state(start_pos_bin) { state(start_pos_bin) {
epsilon(pos_bin) epsilon(pos_bin)
...@@ -98,12 +153,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -98,12 +153,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
term_state(pos_bin) { term_state(pos_bin) {
transition(pos_bin, "01", add_ascii<2>(result, ch), pec::integer_overflow) transition(pos_bin, "01", add_ascii<2>(result, ch), pec::integer_overflow)
} }
state(start_neg_bin) {
epsilon(neg_bin)
}
term_state(neg_bin) {
transition(neg_bin, "01", sub_ascii<2>(result, ch), pec::integer_underflow)
}
// Octal integers. // Octal integers.
state(start_pos_oct) { state(start_pos_oct) {
epsilon(pos_oct) epsilon(pos_oct)
...@@ -112,13 +161,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -112,13 +161,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition(pos_oct, octal_chars, add_ascii<8>(result, ch), transition(pos_oct, octal_chars, add_ascii<8>(result, ch),
pec::integer_overflow) pec::integer_overflow)
} }
state(start_neg_oct) {
epsilon(neg_oct)
}
term_state(neg_oct) {
transition(neg_oct, octal_chars, sub_ascii<8>(result, ch),
pec::integer_underflow)
}
// Hexal integers. // Hexal integers.
state(start_pos_hex) { state(start_pos_hex) {
epsilon(pos_hex) epsilon(pos_hex)
...@@ -127,13 +169,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -127,13 +169,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition(pos_hex, hexadecimal_chars, add_ascii<16>(result, ch), transition(pos_hex, hexadecimal_chars, add_ascii<16>(result, ch),
pec::integer_overflow) pec::integer_overflow)
} }
state(start_neg_hex) {
epsilon(neg_hex)
}
term_state(neg_hex) {
transition(neg_hex, hexadecimal_chars, sub_ascii<16>(result, ch),
pec::integer_underflow)
}
// Reads the integer part of the mantissa or a positive decimal integer. // Reads the integer part of the mantissa or a positive decimal integer.
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),
...@@ -144,14 +179,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -144,14 +179,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition_static_if(enable_float || enable_range, pos_dot, '.') transition_static_if(enable_float || enable_range, pos_dot, '.')
} }
// 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) {
transition(neg_dec, decimal_chars, sub_ascii<10>(result, ch),
pec::integer_underflow)
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, "eE", g.disable())
transition_static_if(enable_float || enable_range, neg_dot, '.')
}
unstable_state(pos_dot) { unstable_state(pos_dot) {
fsm_transition_static_if(enable_range, fsm_transition_static_if(enable_range,
read_number_range(ps, consumer, result), read_number_range(ps, consumer, result),
...@@ -161,14 +188,34 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -161,14 +188,34 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
done, any_char, g.disable()) done, any_char, g.disable())
epsilon(done) epsilon(done)
} }
unstable_state(neg_dot) { term_state(done) {
fsm_transition_static_if(enable_range, // nop
read_number_range(ps, consumer, result), }
done, '.', g.disable()) fin();
// clang-format on
}
/// Reads a number, i.e., on success produces an `int64_t`, an `uint64_t` or a
/// `double`.
template <class State, class Consumer, class EnableFloat = std::true_type,
class EnableRange = std::false_type>
void read_number(State& ps, Consumer& consumer, EnableFloat fl_token = {},
EnableRange rng_token = {}) {
static constexpr bool enable_float = EnableFloat::value;
using odbl = std::optional<double>;
// clang-format off
// Definition of our parser FSM.
start();
state(init) {
transition(init, " \t")
fsm_transition(read_positive_number(ps, consumer, fl_token, rng_token),
done, '+')
fsm_transition(read_negative_number(ps, consumer, fl_token, rng_token),
done, '-')
fsm_epsilon_static_if(enable_float, fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true), read_floating_point(ps, consumer, odbl{0.}),
done, any_char, g.disable()) done, '.')
epsilon(done) fsm_epsilon(read_positive_number(ps, consumer, fl_token, rng_token), done)
} }
term_state(done) { term_state(done) {
// nop // nop
...@@ -177,45 +224,111 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {}, ...@@ -177,45 +224,111 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
// clang-format on // clang-format on
} }
template <class State, class Consumer> /// Generates a range of numbers and calls `consumer` for each value.
void read_number_range(State& ps, Consumer& consumer, int64_t begin) { template <class Consumer, class T>
std::optional<int64_t> end; void generate_range_impl(pec& code, Consumer& consumer, T min_val, T max_val,
std::optional<int64_t> step; std::optional<int64_t> step) {
auto end_consumer = make_consumer(end); auto do_apply = [&](T x) {
auto step_consumer = make_consumer(step); using consumer_result_type = decltype(consumer.value(x));
auto generate_2 = [&](int64_t n, int64_t m) { if constexpr (std::is_same_v<consumer_result_type, pec>) {
if (n <= m) auto res = consumer.value(x);
while (n <= m) if (res == pec::success)
consumer.value(n++); return true;
else code = res;
while (n >= m) return false;
consumer.value(n--); } else {
static_assert(std::is_same_v<consumer_result_type, void>);
consumer.value(x);
return true;
}
}; };
auto generate_3 = [&](int64_t n, int64_t m, int64_t s) { if (min_val == max_val) {
if (n == m) { do_apply(min_val);
consumer.value(n);
return; return;
} }
if (s == 0 || (n > m && s > 0) || (n < m && s < 0)) { if (min_val < max_val) {
ps.code = pec::invalid_range_expression; auto step_val = step.value_or(1);
if (step_val <= 0) {
code = pec::invalid_range_expression;
return; return;
} }
if (n <= m) auto s = static_cast<T>(step_val);
for (auto i = n; i <= m; i += s) auto i = min_val;
consumer.value(i); while (i < max_val) {
if (!do_apply(i))
return;
if (max_val - i < s) // protect against overflows
return;
i += s;
}
if (i == max_val)
do_apply(i);
return;
}
auto step_val = step.value_or(-1);
if (step_val >= 0) {
code = pec::invalid_range_expression;
return;
}
auto s = static_cast<T>(-step_val);
auto i = min_val;
while (i > max_val) {
if (!do_apply(i))
return;
if (i - max_val < s) // protect against underflows
return;
i -= s;
}
if (i == max_val)
do_apply(i);
}
/// Generates a range of numbers and calls `consumer` for each value.
template <class Consumer, class MinValueT, class MaxValueT>
void generate_range(pec& code, Consumer& consumer, MinValueT min_val,
MaxValueT max_val, std::optional<int64_t> step) {
static_assert(is_64bit_integer_v<MinValueT>);
static_assert(is_64bit_integer_v<MaxValueT>);
// Check whether any of the two types is signed. If so, we'll use signed
// integers for the range.
if constexpr (std::is_signed_v<MinValueT> == std::is_signed_v<MaxValueT>) {
generate_range_impl(code, consumer, min_val, max_val, step);
} else if constexpr (std::is_signed_v<MinValueT>) {
if (max_val > INT64_MAX)
code = pec::integer_overflow;
else else
for (auto i = n; i >= m; i += s) generate_range_impl(code, consumer, min_val,
consumer.value(i); static_cast<int64_t>(max_val), step);
}; } else {
if (min_val > INT64_MAX)
code = pec::integer_overflow;
else
generate_range_impl(code, consumer, static_cast<int64_t>(min_val),
max_val, step);
}
}
template <class State, class Consumer, class ValueType>
void read_number_range(State& ps, Consumer& consumer, ValueType begin) {
// Our final value (inclusive). We don't know yet whether we're dealing with
// a signed or unsigned range.
std::variant<none_t, int64_t, uint64_t> end;
// Note: The step value is always signed, even if the range is unsigned. For
// example, [10..2..-2] is a valid range description for the unsigned values
// [10, 8, 6, 4, 2].
std::optional<int64_t> step;
auto end_consumer = make_consumer(end);
auto step_consumer = make_consumer(step);
auto g = caf::detail::make_scope_guard([&] { auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character) { if (ps.code <= pec::trailing_character) {
if (!end) { auto fn = [&](auto end_val) {
if constexpr (std::is_same_v<decltype(end_val), none_t>) {
ps.code = pec::invalid_range_expression; ps.code = pec::invalid_range_expression;
} else if (!step) {
generate_2(begin, *end);
} else { } else {
generate_3(begin, *end, *step); generate_range(ps.code, consumer, begin, end_val, step);
} }
};
std::visit(fn, end);
} }
}); });
static constexpr std::false_type no_float = std::false_type{}; static constexpr std::false_type no_float = std::false_type{};
......
...@@ -35,6 +35,8 @@ void read_number_or_timespan(State& ps, Consumer& consumer, ...@@ -35,6 +35,8 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
Consumer* outer = nullptr; Consumer* outer = nullptr;
std::variant<none_t, int64_t, double> interim; std::variant<none_t, int64_t, double> interim;
void value(int64_t x) { void value(int64_t x) {
// If we see a second integer, we have a range of integers and forward all
// calls to the outer consumer.
switch (++invocations) { switch (++invocations) {
case 1: case 1:
interim = x; interim = x;
...@@ -48,6 +50,13 @@ void read_number_or_timespan(State& ps, Consumer& consumer, ...@@ -48,6 +50,13 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
outer->value(x); outer->value(x);
} }
} }
pec value(uint64_t x) {
if (x <= INT64_MAX) {
value(static_cast<int64_t>(x));
return pec::success;
}
return pec::integer_overflow;
}
void value(double x) { void value(double x) {
interim = x; interim = x;
} }
......
...@@ -1056,6 +1056,10 @@ constexpr bool is_string_or_cstring_v ...@@ -1056,6 +1056,10 @@ constexpr bool is_string_or_cstring_v
= std::is_convertible_v<T, const char*> = std::is_convertible_v<T, const char*>
|| std::is_same_v<std::string, std::decay_t<T>>; || std::is_same_v<std::string, std::decay_t<T>>;
template <class T>
constexpr bool is_64bit_integer_v = std::is_same_v<T, int64_t>
|| std::is_same_v<T, uint64_t>;
} // namespace caf::detail } // namespace caf::detail
#undef CAF_HAS_MEMBER_TRAIT #undef CAF_HAS_MEMBER_TRAIT
......
...@@ -19,6 +19,7 @@ class CAF_CORE_EXPORT json_value { ...@@ -19,6 +19,7 @@ class CAF_CORE_EXPORT json_value {
public: public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
/// Creates a @c null JSON value.
json_value() noexcept : val_(detail::json::null_value()) { json_value() noexcept : val_(detail::json::null_value()) {
// nop // nop
} }
...@@ -39,6 +40,8 @@ public: ...@@ -39,6 +40,8 @@ public:
// -- factories -------------------------------------------------------------- // -- factories --------------------------------------------------------------
/// Creates an undefined JSON value. This special state usually indicates that
/// a previous key lookup failed.
static json_value undefined() noexcept { static json_value undefined() noexcept {
return json_value{detail::json::undefined_value(), nullptr}; return json_value{detail::json::undefined_value(), nullptr};
} }
...@@ -57,19 +60,19 @@ public: ...@@ -57,19 +60,19 @@ public:
} }
/// Checks whether the value is an @c int64_t. /// Checks whether the value is an @c int64_t.
bool is_integer() const noexcept { bool is_integer() const noexcept;
return val_->is_integer();
} /// Checks whether the value is an @c uint64_t.
bool is_unsigned() const noexcept;
/// Checks whether the value is a @c double. /// Checks whether the value is a @c double.
bool is_double() const noexcept { bool is_double() const noexcept {
return val_->is_double(); return val_->is_double();
} }
/// Checks whether the value is a number, i.e., an @c int64_t or a @c double. /// Checks whether the value is a number, i.e., an @c int64_t, a @c uint64_t
bool is_number() const noexcept { /// or a @c double.
return is_integer() || is_double(); bool is_number() const noexcept;
}
/// Checks whether the value is a @c bool. /// Checks whether the value is a @c bool.
bool is_bool() const noexcept { bool is_bool() const noexcept {
...@@ -93,26 +96,49 @@ public: ...@@ -93,26 +96,49 @@ public:
// -- conversion ------------------------------------------------------------- // -- conversion -------------------------------------------------------------
/// Converts the value to an @c int64_t or returns @p fallback if the value
/// is not a valid number.
int64_t to_integer(int64_t fallback = 0) const; int64_t to_integer(int64_t fallback = 0) const;
/// Converts the value to an @c uint64_t or returns @p fallback if the value
/// is not a valid number.
uint64_t to_unsigned(uint64_t fallback = 0) const;
/// Converts the value to a @c double or returns @p fallback if the value is
/// not a valid number.
double to_double(double fallback = 0.0) const; double to_double(double fallback = 0.0) const;
/// Converts the value to a @c bool or returns @p fallback if the value is
/// not a valid boolean.
bool to_bool(bool fallback = false) const; bool to_bool(bool fallback = false) const;
/// Returns the value as a JSON string or an empty string if the value is not
/// a string.
std::string_view to_string() const; std::string_view to_string() const;
/// Returns the value as a JSON string or @p fallback if the value is not a
/// string.
std::string_view to_string(std::string_view fallback) const; std::string_view to_string(std::string_view fallback) const;
/// Returns the value as a JSON array or an empty array if the value is not
/// an array.
json_array to_array() const; json_array to_array() const;
/// Returns the value as a JSON array or @p fallback if the value is not an
/// array.
json_array to_array(json_array fallback) const; json_array to_array(json_array fallback) const;
/// Returns the value as a JSON object or an empty object if the value is not
/// an object.
json_object to_object() const; json_object to_object() const;
/// Returns the value as a JSON object or @p fallback if the value is not an
/// object.
json_object to_object(json_object fallback) const; json_object to_object(json_object fallback) const;
// -- comparison ------------------------------------------------------------- // -- comparison -------------------------------------------------------------
/// Compares two JSON values for equality.
bool equal_to(const json_value& other) const noexcept; bool equal_to(const json_value& other) const noexcept;
// -- parsing ---------------------------------------------------------------- // -- parsing ----------------------------------------------------------------
...@@ -143,6 +169,7 @@ public: ...@@ -143,6 +169,7 @@ public:
// -- printing --------------------------------------------------------------- // -- printing ---------------------------------------------------------------
/// Prints the JSON value to @p buf.
template <class Buffer> template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const { void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor); detail::json::print_to(buf, *val_, indentation_factor);
...@@ -150,6 +177,7 @@ public: ...@@ -150,6 +177,7 @@ public:
// -- serialization ---------------------------------------------------------- // -- serialization ----------------------------------------------------------
/// Applies the `Inspector` to the JSON value `val`.
template <class Inspector> template <class Inspector>
friend bool inspect(Inspector& inspector, json_value& val) { friend bool inspect(Inspector& inspector, json_value& val) {
if constexpr (Inspector::is_loading) { if constexpr (Inspector::is_loading) {
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include "caf/detail/parser/read_floating_point.hpp" #include "caf/detail/parser/read_floating_point.hpp"
#include "caf/detail/parser/read_ipv4_address.hpp" #include "caf/detail/parser/read_ipv4_address.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp" #include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/detail/parser/read_signed_integer.hpp"
#include "caf/detail/parser/read_string.hpp" #include "caf/detail/parser/read_string.hpp"
#include "caf/detail/parser/read_timespan.hpp" #include "caf/detail/parser/read_timespan.hpp"
#include "caf/detail/parser/read_unsigned_integer.hpp" #include "caf/detail/parser/read_unsigned_integer.hpp"
......
...@@ -56,6 +56,7 @@ std::string type_clash(std::string_view want, ...@@ -56,6 +56,7 @@ std::string type_clash(std::string_view want,
using caf::detail::json::value; using caf::detail::json::value;
switch (got.data.index()) { switch (got.data.index()) {
case value::integer_index: case value::integer_index:
case value::unsigned_index:
return type_clash(want, "json::integer"sv); return type_clash(want, "json::integer"sv);
case value::double_index: case value::double_index:
return type_clash(want, "json::real"sv); return type_clash(want, "json::real"sv);
...@@ -534,11 +535,19 @@ bool json_reader::integer(T& x) { ...@@ -534,11 +535,19 @@ bool json_reader::integer(T& x) {
if (detail::bounds_checker<T>::check(i64)) { if (detail::bounds_checker<T>::check(i64)) {
x = static_cast<T>(i64); x = static_cast<T>(i64);
return true; return true;
} else { }
emplace_error(sec::runtime_error, class_name, fn, emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds"); "integer out of bounds");
return false; return false;
} else if (val.data.index() == detail::json::value::unsigned_index) {
auto u64 = std::get<uint64_t>(val.data);
if (detail::bounds_checker<T>::check(u64)) {
x = static_cast<T>(u64);
return true;
} }
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
return false;
} else { } else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(), emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::integer", val)); type_clash("json::integer", val));
...@@ -592,13 +601,17 @@ bool json_reader::value(float& x) { ...@@ -592,13 +601,17 @@ bool json_reader::value(float& x) {
bool json_reader::value(double& x) { bool json_reader::value(double& x) {
FN_DECL; FN_DECL;
return consume<true>(fn, [this, &x](const detail::json::value& val) { return consume<true>(fn, [this, &x](const detail::json::value& val) {
if (val.data.index() == detail::json::value::double_index) { switch (val.data.index()) {
case detail::json::value::double_index:
x = std::get<double>(val.data); x = std::get<double>(val.data);
return true; return true;
} else if (val.data.index() == detail::json::value::integer_index) { case detail::json::value::integer_index:
x = std::get<int64_t>(val.data); x = std::get<int64_t>(val.data);
return true; return true;
} else { case detail::json::value::unsigned_index:
x = std::get<uint64_t>(val.data);
return true;
default:
emplace_error(sec::runtime_error, class_name, fn, current_field_name(), emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::real", val)); type_clash("json::real", val));
return false; return false;
......
...@@ -10,30 +10,82 @@ ...@@ -10,30 +10,82 @@
#include "caf/make_counted.hpp" #include "caf/make_counted.hpp"
#include "caf/parser_state.hpp" #include "caf/parser_state.hpp"
#include <cstdint>
#include <fstream> #include <fstream>
namespace caf { namespace caf {
// -- properties ---------------------------------------------------------------
bool json_value::is_integer() const noexcept {
return val_->is_integer()
|| (val_->is_unsigned()
&& std::get<uint64_t>(val_->data) <= INT64_MAX);
}
bool json_value::is_unsigned() const noexcept {
return val_->is_unsigned()
|| (val_->is_integer() && std::get<int64_t>(val_->data) >= 0);
}
bool json_value::is_number() const noexcept {
switch (val_->data.index()) {
default:
return false;
case detail::json::value::integer_index:
case detail::json::value::unsigned_index:
case detail::json::value::double_index:
return true;
}
}
// -- conversion --------------------------------------------------------------- // -- conversion ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const { int64_t json_value::to_integer(int64_t fallback) const {
if (is_integer()) { switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
return std::get<int64_t>(val_->data); return std::get<int64_t>(val_->data);
case detail::json::value::unsigned_index: {
auto val = std::get<uint64_t>(val_->data);
if (val <= INT64_MAX)
return static_cast<int64_t>(val);
return fallback;
} }
if (is_double()) { case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data)); return static_cast<int64_t>(std::get<double>(val_->data));
} }
return fallback;
} }
double json_value::to_double(double fallback) const { uint64_t json_value::to_unsigned(uint64_t fallback) const {
if (is_double()) { switch (val_->data.index()) {
return std::get<double>(val_->data); default:
return fallback;
case detail::json::value::integer_index: {
auto val = std::get<int64_t>(val_->data);
if (val >= 0)
return static_cast<uint64_t>(val);
return fallback;
} }
if (is_integer()) { case detail::json::value::unsigned_index:
return static_cast<double>(std::get<int64_t>(val_->data)); return std::get<uint64_t>(val_->data);
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
} }
}
double json_value::to_double(double fallback) const {
switch (val_->data.index()) {
default:
return fallback; return fallback;
case detail::json::value::integer_index:
return static_cast<double>(std::get<int64_t>(val_->data));
case detail::json::value::unsigned_index:
return static_cast<double>(std::get<uint64_t>(val_->data));
case detail::json::value::double_index:
return std::get<double>(val_->data);
};
} }
bool json_value::to_bool(bool fallback) const { bool json_value::to_bool(bool fallback) const {
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
namespace { namespace {
template <class T> template <class T, class U>
bool check(int64_t x) { bool check(U x) {
return caf::detail::bounds_checker<T>::check(x); return caf::detail::bounds_checker<T>::check(x);
} }
...@@ -39,5 +39,7 @@ CAF_TEST(small integers) { ...@@ -39,5 +39,7 @@ CAF_TEST(small integers) {
CAF_TEST(large unsigned integers) { CAF_TEST(large unsigned integers) {
CHECK_EQ(check<uint64_t>(-1), false); CHECK_EQ(check<uint64_t>(-1), false);
CHECK_EQ(check<uint64_t>(0), true); CHECK_EQ(check<uint64_t>(0), true);
CHECK_EQ(check<uint64_t>(0u), true);
CHECK_EQ(check<uint64_t>(std::numeric_limits<int64_t>::max()), true); CHECK_EQ(check<uint64_t>(std::numeric_limits<int64_t>::max()), true);
CHECK_EQ(check<uint64_t>(std::numeric_limits<uint64_t>::max()), true);
} }
...@@ -85,6 +85,10 @@ void stringify(std::string& str, size_t, int64_t val) { ...@@ -85,6 +85,10 @@ void stringify(std::string& str, size_t, int64_t val) {
str += std::to_string(val); str += std::to_string(val);
} }
void stringify(std::string& str, size_t, uint64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, double val) { void stringify(std::string& str, size_t, double val) {
str += std::to_string(val); str += std::to_string(val);
} }
......
...@@ -30,6 +30,12 @@ struct number_consumer { ...@@ -30,6 +30,12 @@ struct number_consumer {
void value(int64_t y) { void value(int64_t y) {
x = y; x = y;
} }
void value(uint64_t y) {
if (y < INT64_MAX)
value(static_cast<int64_t>(y));
else
CAF_FAIL("number consumer called with a uint64_t larger than INT64_MAX");
}
}; };
struct range_consumer { struct range_consumer {
...@@ -40,6 +46,12 @@ struct range_consumer { ...@@ -40,6 +46,12 @@ struct range_consumer {
void value(int64_t y) { void value(int64_t y) {
xs.emplace_back(y); xs.emplace_back(y);
} }
void value(uint64_t y) {
if (y < INT64_MAX)
value(static_cast<int64_t>(y));
else
CAF_FAIL("range consumer called with a uint64_t larger than INT64_MAX");
}
}; };
struct res_t { struct res_t {
...@@ -328,6 +340,7 @@ CAF_TEST(ranges can use signed integers) { ...@@ -328,6 +340,7 @@ CAF_TEST(ranges can use signed integers) {
CAF_TEST(the parser rejects invalid step values) { CAF_TEST(the parser rejects invalid step values) {
CHECK_ERR("-2..+2..-2", pec::invalid_range_expression); CHECK_ERR("-2..+2..-2", pec::invalid_range_expression);
CHECK_ERR("+2..-2..+2", pec::invalid_range_expression); CHECK_ERR("+2..-2..+2", pec::invalid_range_expression);
CHECK_ERR("+2..-2..0", pec::invalid_range_expression);
} }
END_FIXTURE_SCOPE() END_FIXTURE_SCOPE()
...@@ -17,6 +17,7 @@ using namespace std::literals; ...@@ -17,6 +17,7 @@ using namespace std::literals;
namespace { namespace {
struct fixture { struct fixture {
// Adds a test case for a given input and expected output.
template <class T> template <class T>
void add_test_case(std::string_view input, T val) { void add_test_case(std::string_view input, T val) {
auto f = [this, input, obj{std::move(val)}]() -> bool { auto f = [this, input, obj{std::move(val)}]() -> bool {
...@@ -36,6 +37,20 @@ struct fixture { ...@@ -36,6 +37,20 @@ struct fixture {
test_cases.emplace_back(std::move(f)); test_cases.emplace_back(std::move(f));
} }
// Adds a test case that should fail.
template <class T>
void add_neg_test_case(std::string_view input) {
auto f = [this, input]() -> bool {
auto tmp = T{};
auto res = reader.load(input) // parse JSON
&& reader.apply(tmp); // deserialize object
if (res)
MESSAGE("got unexpected output: " << tmp);
return !res;
};
test_cases.emplace_back(std::move(f));
}
template <class T, class... Ts> template <class T, class... Ts>
std::vector<T> ls(Ts... xs) { std::vector<T> ls(Ts... xs) {
std::vector<T> result; std::vector<T> result;
...@@ -67,7 +82,6 @@ fixture::fixture() { ...@@ -67,7 +82,6 @@ fixture::fixture() {
add_test_case(R"_(true)_", true); add_test_case(R"_(true)_", true);
add_test_case(R"_(false)_", false); add_test_case(R"_(false)_", false);
add_test_case(R"_([true, false])_", ls<bool>(true, false)); add_test_case(R"_([true, false])_", ls<bool>(true, false));
add_test_case(R"_(42)_", int32_t{42});
add_test_case(R"_([1, 2, 3])_", ls<int32_t>(1, 2, 3)); add_test_case(R"_([1, 2, 3])_", ls<int32_t>(1, 2, 3));
add_test_case(R"_([[1, 2], [3], []])_", add_test_case(R"_([[1, 2], [3], []])_",
ls<i32_list>(ls<int32_t>(1, 2), ls<int32_t>(3), ls<int32_t>())); ls<i32_list>(ls<int32_t>(1, 2), ls<int32_t>(3), ls<int32_t>()));
...@@ -109,6 +123,42 @@ fixture::fixture() { ...@@ -109,6 +123,42 @@ fixture::fixture() {
R"({"top-left": {"x": 10, "y": 10}, )" R"({"top-left": {"x": 10, "y": 10}, )"
R"("bottom-right": {"x": 20, "y": 20}}})", R"("bottom-right": {"x": 20, "y": 20}}})",
widget{"blue", rectangle{{10, 10}, {20, 20}}}); widget{"blue", rectangle{{10, 10}, {20, 20}}});
// Test cases for integers that are in bound.
add_test_case(R"_(-128)_", int8_t{INT8_MIN});
add_test_case(R"_(127)_", int8_t{INT8_MAX});
add_test_case(R"_(-32768)_", int16_t{INT16_MIN});
add_test_case(R"_(32767)_", int16_t{INT16_MAX});
add_test_case(R"_(-2147483648)_", int32_t{INT32_MIN});
add_test_case(R"_(2147483647)_", int32_t{INT32_MAX});
add_test_case(R"_(-9223372036854775808)_", int64_t{INT64_MIN});
add_test_case(R"_(9223372036854775807)_", int64_t{INT64_MAX});
// Test cases for unsigned integers that are in bound.
add_test_case(R"_(0)_", uint8_t{0});
add_test_case(R"_(255)_", uint8_t{UINT8_MAX});
add_test_case(R"_(0)_", uint16_t{0});
add_test_case(R"_(65535)_", uint16_t{UINT16_MAX});
add_test_case(R"_(0)_", uint32_t{0});
add_test_case(R"_(4294967295)_", uint32_t{UINT32_MAX});
add_test_case(R"_(0)_", uint64_t{0});
add_test_case(R"_(18446744073709551615)_", uint64_t{UINT64_MAX});
// Test cases for integers that are out of bound.
add_neg_test_case<int8_t>(R"_(-129)_");
add_neg_test_case<int8_t>(R"_(128)_");
add_neg_test_case<int16_t>(R"_(-32769)_");
add_neg_test_case<int16_t>(R"_(32768)_");
add_neg_test_case<int32_t>(R"_(-2147483649)_");
add_neg_test_case<int32_t>(R"_(2147483648)_");
add_neg_test_case<int64_t>(R"_(-9223372036854775809)_");
add_neg_test_case<int64_t>(R"_(9223372036854775808)_");
// Test cases for unsigned integers that are out of bound.
add_neg_test_case<uint8_t>(R"_(-1)_");
add_neg_test_case<uint8_t>(R"_(256)_");
add_neg_test_case<uint16_t>(R"_(-1)_");
add_neg_test_case<uint16_t>(R"_(65536)_");
add_neg_test_case<uint32_t>(R"_(-1)_");
add_neg_test_case<uint32_t>(R"_(4294967296)_");
add_neg_test_case<uint64_t>(R"_(-1)_");
add_neg_test_case<uint64_t>(R"_(18446744073709551616)_");
} }
} // namespace } // namespace
......
...@@ -39,6 +39,7 @@ TEST_CASE("default-constructed") { ...@@ -39,6 +39,7 @@ TEST_CASE("default-constructed") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -61,6 +62,7 @@ TEST_CASE("from undefined") { ...@@ -61,6 +62,7 @@ TEST_CASE("from undefined") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -70,11 +72,36 @@ TEST_CASE("from undefined") { ...@@ -70,11 +72,36 @@ TEST_CASE("from undefined") {
CHECK_EQ(deep_copy(val), val); CHECK_EQ(deep_copy(val), val);
} }
TEST_CASE("from integer") { TEST_CASE("from negative integer") {
auto val = unbox(json_value::parse("-1"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(!val.is_unsigned());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), -1);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), -1.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "-1");
CHECK_EQ(printed(val), "-1");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from small integer") {
// A small integer can be represented as both int64_t and uint64_t.
auto val = unbox(json_value::parse("42")); auto val = unbox(json_value::parse("42"));
CHECK(!val.is_null()); CHECK(!val.is_null());
CHECK(!val.is_undefined()); CHECK(!val.is_undefined());
CHECK(val.is_integer()); CHECK(val.is_integer());
CHECK(val.is_unsigned());
CHECK(!val.is_double()); CHECK(!val.is_double());
CHECK(val.is_number()); CHECK(val.is_number());
CHECK(!val.is_bool()); CHECK(!val.is_bool());
...@@ -82,6 +109,7 @@ TEST_CASE("from integer") { ...@@ -82,6 +109,7 @@ TEST_CASE("from integer") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42); CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.0); CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -91,6 +119,29 @@ TEST_CASE("from integer") { ...@@ -91,6 +119,29 @@ TEST_CASE("from integer") {
CHECK_EQ(deep_copy(val), val); CHECK_EQ(deep_copy(val), val);
} }
TEST_CASE("from UINT64_MAX") {
auto val = unbox(json_value::parse("18446744073709551615"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(val.is_unsigned());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), UINT64_MAX);
CHECK_EQ(val.to_double(), static_cast<double>(UINT64_MAX));
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "18446744073709551615");
CHECK_EQ(printed(val), "18446744073709551615");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from double") { TEST_CASE("from double") {
auto val = unbox(json_value::parse("42.0")); auto val = unbox(json_value::parse("42.0"));
CHECK(!val.is_null()); CHECK(!val.is_null());
...@@ -103,6 +154,7 @@ TEST_CASE("from double") { ...@@ -103,6 +154,7 @@ TEST_CASE("from double") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42); CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.0); CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -124,6 +176,7 @@ TEST_CASE("from bool") { ...@@ -124,6 +176,7 @@ TEST_CASE("from bool") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), true); CHECK_EQ(val.to_bool(), true);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -145,6 +198,7 @@ TEST_CASE("from string") { ...@@ -145,6 +198,7 @@ TEST_CASE("from string") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), "Hello, world!"sv); CHECK_EQ(val.to_string(), "Hello, world!"sv);
...@@ -166,6 +220,7 @@ TEST_CASE("from empty array") { ...@@ -166,6 +220,7 @@ TEST_CASE("from empty array") {
CHECK(val.is_array()); CHECK(val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -188,6 +243,7 @@ TEST_CASE("from array of size 1") { ...@@ -188,6 +243,7 @@ TEST_CASE("from array of size 1") {
CHECK(val.is_array()); CHECK(val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -210,6 +266,7 @@ TEST_CASE("from array of size 3") { ...@@ -210,6 +266,7 @@ TEST_CASE("from array of size 3") {
CHECK(val.is_array()); CHECK(val.is_array());
CHECK(!val.is_object()); CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -232,6 +289,7 @@ TEST_CASE("from empty object") { ...@@ -232,6 +289,7 @@ TEST_CASE("from empty object") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(val.is_object()); CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -253,6 +311,7 @@ TEST_CASE("from non-empty object") { ...@@ -253,6 +311,7 @@ TEST_CASE("from non-empty object") {
CHECK(!val.is_array()); CHECK(!val.is_array());
CHECK(val.is_object()); CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0); CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), 0.0); CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false); CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv); CHECK_EQ(val.to_string(), ""sv);
...@@ -261,3 +320,24 @@ TEST_CASE("from non-empty object") { ...@@ -261,3 +320,24 @@ TEST_CASE("from non-empty object") {
CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}"); CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}");
CHECK_EQ(deep_copy(val), val); CHECK_EQ(deep_copy(val), val);
} }
namespace {
std::string_view json_with_boundary_values = R"_({
"min-int64": -9223372036854775808,
"max-int64": 9223372036854775807,
"min-uint64": 0,
"max-uint64": 18446744073709551615
})_";
} // namespace
TEST_CASE("non-empty object with nested values") {
auto val = unbox(json_value::parse(json_with_boundary_values));
CHECK(val.is_object());
auto obj = val.to_object();
CHECK_EQ(obj.value("min-int64").to_integer(), INT64_MIN);
CHECK_EQ(obj.value("max-int64").to_integer(), INT64_MAX);
CHECK_EQ(obj.value("min-uint64").to_unsigned(), 0u);
CHECK_EQ(obj.value("max-uint64").to_unsigned(), UINT64_MAX);
}
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