Commit e0b41065 authored by Dominik Charousset's avatar Dominik Charousset

Backport support for uint64_t in JSON parsing

parent e4b1bb30
......@@ -162,7 +162,11 @@ caf_add_component(
src/ipv6_address.cpp
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_array.cpp
src/json_builder.cpp
src/json_object.cpp
src/json_reader.cpp
src/json_value.cpp
src/json_writer.cpp
src/load_inspector.cpp
src/local_actor.cpp
......@@ -304,7 +308,11 @@ caf_add_component(
ipv6_address
ipv6_endpoint
ipv6_subnet
json_array
json_builder
json_object
json_reader
json_value
json_writer
load_inspector
logger
......
......@@ -10,26 +10,28 @@
namespace caf::detail {
template <class To, bool LargeUnsigned = sizeof(To) >= sizeof(int64_t)
&& std::is_unsigned<To>::value>
template <class To>
struct bounds_checker {
static constexpr bool check(int64_t x) noexcept {
return x >= std::numeric_limits<To>::min()
&& x <= std::numeric_limits<To>::max();
}
};
template <>
struct bounds_checker<int64_t, false> {
static constexpr bool check(int64_t) noexcept {
return true;
template <class From>
static constexpr bool check(From x) noexcept {
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.
return static_cast<From>(converted) == x;
} else if constexpr (std::is_signed_v<From>) {
// If the source type is signed and the target type is unsigned, we need
// 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;
}
};
......
......@@ -59,7 +59,7 @@ private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<none_t, config_consumer*, config_list_consumer*,
std::variant<none_t, config_consumer*, config_list_consumer*,
config_value_consumer*>
parent_;
};
......@@ -102,8 +102,16 @@ public:
template <class T>
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)});
}
}
const std::string& current_key() {
return current_key_;
......@@ -119,7 +127,7 @@ private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<none_t, config_consumer*, config_list_consumer*> parent_;
std::variant<none_t, config_consumer*, config_list_consumer*> parent_;
settings* cfg_ = nullptr;
std::string current_key_;
std::string category_;
......
......@@ -4,10 +4,15 @@
#pragma once
#include "caf/pec.hpp"
#include <optional>
#include <type_traits>
#include <utility>
namespace caf::detail {
/// Consumes a single value.
template <class T>
class consumer {
public:
......@@ -17,17 +22,117 @@ public:
// nop
}
void value(T&& y) {
x_ = std::move(y);
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
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>
consumer<T> make_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
......@@ -4,13 +4,20 @@
#pragma once
#include <cstdint>
#include <variant>
#include <vector>
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/detail/print.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/parser_state.hpp"
#include "caf/ref_counted.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/type_id.hpp"
#include <cstdint>
#include <cstring>
#include <iterator>
#include <new>
#include <variant>
// This JSON abstraction is designed to allocate its entire state in a monotonic
// buffer resource. This minimizes memory allocations and also enables us to
......@@ -20,55 +27,824 @@
namespace caf::detail::json {
// -- utility classes ----------------------------------------------------------
// Wraps a buffer resource with a reference count.
struct CAF_CORE_EXPORT storage : public ref_counted {
/// Provides the memory for all of our parsed JSON entities.
detail::monotonic_buffer_resource buf;
};
// -- helper for modeling the JSON type system ---------------------------------
using storage_ptr = intrusive_ptr<storage>;
struct null_t {};
class value {
constexpr bool operator==(null_t, null_t) {
return true;
}
constexpr bool operator!=(null_t, null_t) {
return false;
}
struct undefined_t {};
constexpr bool operator==(undefined_t, undefined_t) {
return true;
}
constexpr bool operator!=(undefined_t, undefined_t) {
return false;
}
template <class T>
struct linked_list_node {
T value;
linked_list_node* next;
};
template <class T>
class linked_list_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
using node_pointer
= std::conditional_t<std::is_const_v<T>,
const linked_list_node<std::remove_const_t<T>>*,
linked_list_node<value_type>*>;
constexpr linked_list_iterator() noexcept = default;
constexpr explicit linked_list_iterator(node_pointer ptr) noexcept
: ptr_(ptr) {
// nop
}
constexpr linked_list_iterator(const linked_list_iterator&) noexcept
= default;
constexpr linked_list_iterator&
operator=(const linked_list_iterator&) noexcept
= default;
constexpr node_pointer get() const noexcept {
return ptr_;
}
linked_list_iterator& operator++() noexcept {
ptr_ = ptr_->next;
return *this;
}
linked_list_iterator operator++(int) noexcept {
return linked_list_iterator{ptr_->next};
}
T& operator*() const noexcept {
return ptr_->value;
}
T* operator->() const noexcept {
return std::addressof(ptr_->value);
}
private:
node_pointer ptr_ = nullptr;
};
template <class T>
constexpr bool
operator==(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return lhs.get() == rhs.get();
}
template <class T>
constexpr bool
operator!=(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return !(lhs == rhs);
}
// A minimal version of a linked list that has constexpr constructor and an
// iterator type where the default-constructed iterator is the past-the-end
// iterator. Properties that std::list unfortunately lacks.
//
// The default-constructed list object is an empty list that does not allow
// push_back.
template <class T>
class linked_list {
public:
using value_type = T;
using node_type = linked_list_node<value_type>;
using allocator_type = monotonic_buffer_resource::allocator<node_type>;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using node_pointer = node_type*;
using iterator = linked_list_iterator<value_type>;
using const_iterator = linked_list_iterator<const value_type>;
linked_list() noexcept {
// nop
}
~linked_list() {
auto* ptr = head_;
while (ptr != nullptr) {
auto* next = ptr->next;
ptr->~node_type();
allocator_.deallocate(ptr, 1);
ptr = next;
}
}
explicit linked_list(allocator_type allocator) noexcept
: allocator_(allocator) {
// nop
}
linked_list(const linked_list&) = delete;
linked_list(linked_list&& other)
: size_(other.size_),
head_(other.head_),
tail_(other.tail_),
allocator_(other.allocator_) {
other.size_ = 0;
other.head_ = nullptr;
other.tail_ = nullptr;
}
linked_list& operator=(const linked_list&) = delete;
linked_list& operator=(linked_list&& other) {
using std::swap;
swap(size_, other.size_);
swap(head_, other.head_);
swap(tail_, other.tail_);
swap(allocator_, other.allocator_);
return *this;
}
[[nodiscard]] bool empty() const noexcept {
return size_ == 0;
}
[[nodiscard]] size_t size() const noexcept {
return size_;
}
[[nodiscard]] iterator begin() noexcept {
return iterator{head_};
}
[[nodiscard]] const_iterator begin() const noexcept {
return const_iterator{head_};
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return {};
}
[[nodiscard]] const_iterator end() const noexcept {
return {};
}
[[nodiscard]] const_iterator cend() const noexcept {
return {};
}
[[nodiscard]] reference front() noexcept {
return head_->value;
}
[[nodiscard]] const_reference front() const noexcept {
return head_->value;
}
[[nodiscard]] reference back() noexcept {
return tail_->value;
}
[[nodiscard]] const_reference back() const noexcept {
return tail_->value;
}
[[nodiscard]] allocator_type get_allocator() const noexcept {
return allocator_;
}
void push_back(T value) {
++size_;
auto new_node = allocator_->allocate(1);
new (new_node) node_type{std::move(value), nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
}
template <class... Ts>
reference emplace_back(Ts&&... args) {
++size_;
auto new_node = allocator_.allocate(1);
new (new_node) node_type{T{std::forward<Ts>(args)...}, nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
return new_node->value;
}
private:
size_t size_ = 0;
node_pointer head_ = nullptr;
node_pointer tail_ = nullptr;
allocator_type allocator_;
};
template <class T>
bool operator==(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <class T>
bool operator!=(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return !(lhs == rhs);
}
/// Re-allocates the given string at the buffer resource.
CAF_CORE_EXPORT string_view realloc(string_view str,
monotonic_buffer_resource* res);
inline string_view realloc(string_view str, const storage_ptr& ptr) {
return realloc(str, &ptr->buf);
}
/// Concatenates all strings and allocates a single new string for the result.
CAF_CORE_EXPORT string_view concat(std::initializer_list<string_view> xs,
monotonic_buffer_resource* res);
inline string_view concat(std::initializer_list<string_view> xs,
const storage_ptr& ptr) {
return concat(xs, &ptr->buf);
}
class CAF_CORE_EXPORT value {
public:
using array_allocator = monotonic_buffer_resource::allocator<value>;
using array = linked_list<value>;
using array = std::vector<value, array_allocator>;
using array_allocator = array::allocator_type;
struct member {
string_view key;
value* val = nullptr;
};
using member_allocator = monotonic_buffer_resource::allocator<member>;
using object = linked_list<member>;
using object = std::vector<member, member_allocator>;
using object_allocator = object::allocator_type;
using data_type
= std::variant<null_t, int64_t, double, bool, string_view, array, object>;
using data_type = std::variant<null_t, int64_t, uint64_t, double, bool,
string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0;
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 object_index = 7;
static constexpr size_t undefined_index = 8;
data_type data;
bool is_null() const noexcept {
return data.index() == null_index;
}
bool is_integer() const noexcept {
return data.index() == integer_index;
}
bool is_unsigned() const noexcept {
return data.index() == unsigned_index;
}
bool is_double() const noexcept {
return data.index() == double_index;
}
bool is_bool() const noexcept {
return data.index() == bool_index;
}
bool is_string() const noexcept {
return data.index() == string_index;
}
bool is_array() const noexcept {
return data.index() == array_index;
}
bool is_object() const noexcept {
return data.index() == object_index;
}
bool is_undefined() const noexcept {
return data.index() == undefined_index;
}
void assign_string(string_view str, monotonic_buffer_resource* res) {
data = realloc(str, res);
}
void assign_string(string_view str, const storage_ptr& ptr) {
data = realloc(str, &ptr->buf);
}
void assign_object(monotonic_buffer_resource* res) {
data = object{object_allocator{res}};
}
void assign_object(const storage_ptr& ptr) {
assign_object(&ptr->buf);
}
void assign_array(monotonic_buffer_resource* res) {
data = array{array_allocator{res}};
}
void assign_array(const storage_ptr& ptr) {
assign_array(&ptr->buf);
}
};
inline bool operator==(const value& lhs, const value& rhs) {
return lhs.data == rhs.data;
}
inline bool operator!=(const value& lhs, const value& rhs) {
return !(lhs == rhs);
}
inline bool operator==(const value::member& lhs, const value::member& rhs) {
if (lhs.key == rhs.key && lhs.val != nullptr && rhs.val != nullptr) {
return *lhs.val == *rhs.val;
}
return false;
}
inline bool operator!=(const value::member& lhs, const value::member& rhs) {
return !(lhs == rhs);
}
using array = value::array;
using member = value::member;
using object = value::object;
// -- factory functions --------------------------------------------------------
value* make_value(monotonic_buffer_resource* storage);
inline value* make_value(const storage_ptr& ptr) {
return make_value(&ptr->buf);
}
array* make_array(monotonic_buffer_resource* storage);
inline array* make_array(const storage_ptr& ptr) {
return make_array(&ptr->buf);
}
object* make_object(monotonic_buffer_resource* storage);
inline object* make_object(const storage_ptr& ptr) {
return make_object(&ptr->buf);
}
// -- saving -------------------------------------------------------------------
template <class Serializer>
bool save(Serializer& sink, const object& obj);
template <class Serializer>
bool save(Serializer& sink, const array& arr);
template <class Serializer>
bool save(Serializer& sink, const value& val) {
// On the "wire", we only use the public types.
if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// 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_v<uint64_t>,
type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
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.
auto type_index = val.data.index();
if (!sink.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::integer_index:
if (!sink.apply(std::get<int64_t>(val.data)))
return false;
break;
case value::unsigned_index:
if (!sink.apply(std::get<uint64_t>(val.data)))
return false;
break;
case value::double_index:
if (!sink.apply(std::get<double>(val.data)))
return false;
break;
case value::bool_index:
if (!sink.apply(std::get<bool>(val.data)))
return false;
break;
case value::string_index:
if (!sink.apply(std::get<string_view>(val.data)))
return false;
break;
case value::array_index:
if (!save(sink, std::get<array>(val.data)))
return false;
break;
case value::object_index:
if (!save(sink, std::get<object>(val.data)))
return false;
break;
default:
// null and undefined both have no data
break;
}
// Wrap up.
return sink.end_field() && sink.end_object();
}
template <class Serializer>
bool save(Serializer& sink, const object& obj) {
if (!sink.begin_associative_array(obj.size()))
return false;
for (const auto& kvp : obj) {
if (kvp.val != nullptr) {
if (!sink.begin_key_value_pair() // <key-value-pair>
|| !sink.value(kvp.key) // <key ...>
|| !save(sink, *kvp.val) // <value ...>
|| !sink.end_key_value_pair()) // </key-value-pair>
return false;
}
}
return sink.end_associative_array();
}
template <class Serializer>
bool save(Serializer& sink, const array& arr) {
if (!sink.begin_sequence(arr.size()))
return false;
for (const auto& val : arr)
if (!save(sink, val))
return false;
return sink.end_sequence();
}
// -- loading ------------------------------------------------------------------
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
// On the "wire", we only use the public types.
if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// 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_v<uint64_t>,
type_id_v<double>, type_id_v<bool>, type_id_v<std::string>,
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.
auto type_index = size_t{0};
if (!source.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::null_index:
val.data = null_t{};
break;
case value::integer_index: {
auto tmp = int64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::unsigned_index: {
auto tmp = uint64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::double_index: {
auto tmp = double{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::bool_index: {
auto tmp = false;
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::string_index: {
auto tmp = std::string{};
if (!source.apply(tmp))
return false;
if (tmp.empty()) {
val.data = string_view{};
} else {
val.assign_string(tmp, res);
}
break;
}
case value::array_index:
val.data = array{array::allocator_type{res}};
if (!load(source, std::get<array>(val.data), res))
return false;
break;
case value::object_index:
val.data = object{object::allocator_type{res}};
if (!load(source, std::get<object>(val.data), res))
return false;
break;
default: // undefined
val.data = undefined_t{};
break;
}
// Wrap up.
return source.end_field() && source.end_object();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_associative_array(size))
return false;
for (size_t i = 0; i < size; ++i) {
if (!source.begin_key_value_pair())
return false;
auto& kvp = obj.emplace_back();
// Deserialize the key.
auto key = std::string{};
if (!source.apply(key))
return false;
if (key.empty()) {
kvp.key = string_view{};
} else {
kvp.key = realloc(key, res);
}
// Deserialize the value.
kvp.val = make_value(res);
if (!load(source, *kvp.val, res))
return false;
if (!source.end_key_value_pair())
return false;
}
return source.end_associative_array();
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_sequence(size))
return false;
for (size_t i = 0; i < size; ++i) {
auto& val = arr.emplace_back();
if (!load(source, val, res))
return false;
}
return source.end_sequence();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, const storage_ptr& ptr) {
return load(source, obj, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, const storage_ptr& ptr) {
return load(source, arr, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, value& val, const storage_ptr& ptr) {
return load(source, val, std::addressof(ptr->buf));
}
// -- singletons ---------------------------------------------------------------
const value* null_value() noexcept;
const value* undefined_value() noexcept;
const object* empty_object() noexcept;
const array* empty_array() noexcept;
// -- parsing ------------------------------------------------------------------
// Specialization for parsers operating on mutable character sequences.
using mutable_string_parser_state = parser_state<char*>;
// Specialization for parsers operating on files.
using file_parser_state = parser_state<std::istreambuf_iterator<char>>;
// Parses the input string and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input string and makes a deep copy of all strings.
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input file and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of strings whenever possible.
// Strings that do not have escaped characters are not copied, other strings
// will be copied.
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of all strings. Strings with
// escaped characters are decoded in place.
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage);
// -- printing -----------------------------------------------------------------
template <class Buffer>
static void print_to(Buffer& buf, string_view str) {
buf.insert(buf.end(), str.begin(), str.end());
}
template <class Buffer>
static void print_nl_to(Buffer& buf, size_t indentation) {
buf.push_back('\n');
buf.insert(buf.end(), indentation, ' ');
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const value& val, size_t indentation_factor,
size_t offset = 0) {
using namespace std::literals;
switch (val.data.index()) {
case value::integer_index:
print(buf, std::get<int64_t>(val.data));
break;
case value::unsigned_index:
print(buf, std::get<uint64_t>(val.data));
break;
case value::double_index:
print(buf, std::get<double>(val.data));
break;
case value::bool_index:
print(buf, std::get<bool>(val.data));
break;
case value::string_index:
print_escaped(buf, std::get<string_view>(val.data));
break;
case value::array_index:
print_to(buf, std::get<array>(val.data), indentation_factor, offset);
break;
case value::object_index:
print_to(buf, std::get<object>(val.data), indentation_factor, offset);
break;
default:
print_to(buf, "null"sv);
}
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
if (arr.empty()) {
print_to(buf, "[]"sv);
} else if (indentation_factor == 0) {
buf.push_back('[');
auto i = arr.begin();
print_to(buf, *i, 0);
auto e = arr.end();
while (++i != e) {
print_to(buf, ", "sv);
print_to(buf, *i, 0);
}
buf.push_back(']');
} else {
buf.push_back('[');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = arr.begin();
print_to(buf, *i, indentation_factor, new_offset);
auto e = arr.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_to(buf, *i, indentation_factor, new_offset);
}
print_nl_to(buf, offset);
buf.push_back(']');
}
}
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
auto print_member = [&](const value::member& kvp, size_t new_offset) {
print_escaped(buf, kvp.key);
print_to(buf, ": "sv);
print_to(buf, *kvp.val, indentation_factor, new_offset);
};
if (obj.empty()) {
print_to(buf, "{}"sv);
} else if (indentation_factor == 0) {
buf.push_back('{');
auto i = obj.begin();
print_member(*i, offset);
auto e = obj.end();
while (++i != e) {
print_to(buf, ", "sv);
print_member(*i, offset);
}
buf.push_back('}');
} else {
buf.push_back('{');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = obj.begin();
print_member(*i, new_offset);
auto e = obj.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_member(*i, new_offset);
}
print_nl_to(buf, offset);
buf.push_back('}');
}
}
} // namespace caf::detail::json
......@@ -5,6 +5,7 @@
#pragma once
#include <cstdint>
#include <optional>
#include <type_traits>
#include "caf/config.hpp"
......@@ -14,7 +15,6 @@
#include "caf/detail/parser/is_digit.hpp"
#include "caf/detail/parser/sub_ascii.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -30,7 +30,7 @@ namespace caf::detail::parser {
/// the pre-decimal value.
template <class State, class Consumer, class ValueType>
void read_floating_point(State& ps, Consumer&& consumer,
optional<ValueType> start_value,
std::optional<ValueType> start_value,
bool negative = false) {
// Any exponent larger than 511 always overflows.
static constexpr int max_double_exponent = 511;
......@@ -38,7 +38,7 @@ void read_floating_point(State& ps, Consumer&& consumer,
enum sign_t { plus, minus };
sign_t sign;
ValueType result;
if (start_value == none) {
if (!start_value) {
sign = plus;
result = 0;
} else if (*start_value < 0) {
......@@ -72,8 +72,8 @@ void read_floating_point(State& ps, Consumer&& consumer,
}
// 3) Scale result.
// Pre-computed powers of 10 for the scaling loop.
static double powerTable[]
= {1e1, 1e2, 1e4, 1e8, 1e16, 1e32, 1e64, 1e128, 1e256};
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)
......@@ -97,7 +97,7 @@ void read_floating_point(State& ps, Consumer&& consumer,
// Definition of our parser FSM.
start();
unstable_state(init) {
epsilon_if(start_value == none, regular_init)
epsilon_if(!start_value, regular_init)
epsilon(after_dec, "eE.")
epsilon(after_dot, any_char)
}
......@@ -173,7 +173,7 @@ template <class State, class Consumer>
void read_floating_point(State& ps, Consumer&& consumer) {
using consumer_type = typename std::decay<Consumer>::type;
using value_type = typename consumer_type::value_type;
return read_floating_point(ps, consumer, optional<value_type>{});
return read_floating_point(ps, consumer, std::optional<value_type>{});
}
} // namespace caf::detail::parser
......
......@@ -4,9 +4,6 @@
#pragma once
#include <cstdint>
#include <type_traits>
#include "caf/config.hpp"
#include "caf/detail/consumer.hpp"
#include "caf/detail/parser/add_ascii.hpp"
......@@ -16,8 +13,12 @@
#include "caf/detail/parser/read_floating_point.hpp"
#include "caf/detail/parser/sub_ascii.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/pec.hpp"
#include <cstdint>
#include <type_traits>
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
......@@ -32,14 +33,12 @@ namespace caf::detail::parser {
/// foo = [1..2]
/// ~~~^
/// ~~~
template <class State, class Consumer>
void read_number_range(State& ps, Consumer& consumer, int64_t begin);
template <class State, class Consumer, class ValueType>
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,
class EnableRange = std::false_type>
void read_number(State& ps, Consumer& consumer, EnableFloat = {},
void read_negative_number(State& ps, Consumer& consumer, EnableFloat = {},
EnableRange = {}) {
static constexpr bool enable_float = EnableFloat::value;
static constexpr bool enable_range = EnableRange::value;
......@@ -48,35 +47,97 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
// Computes the result on success.
auto g = caf::detail::make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consumer.value(result);
apply_consumer(consumer, result, ps.code);
});
using odbl = optional<double>;
using odbl = std::optional<double>;
// clang-format off
// Definition of our parser FSM.
start();
// Initial state.
state(init) {
transition(init, " \t")
transition(has_plus, '+')
transition(has_minus, '-')
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{0.}),
read_floating_point(ps, consumer, odbl{0.}, true),
done, '.', g.disable())
epsilon(has_plus)
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)
}
// "+" or "-" alone aren't numbers.
state(has_plus) {
// 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{0.}),
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())
transition(pos_zero, '0')
epsilon(pos_dec)
fsm_epsilon_static_if(enable_float,
read_floating_point(ps, consumer, odbl{result}, true),
done, any_char, g.disable())
epsilon(done)
}
state(has_minus) {
term_state(done) {
// 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,
read_floating_point(ps, consumer, odbl{0.}, true),
read_floating_point(ps, consumer, odbl{0.}),
done, '.', g.disable())
transition(neg_zero, '0')
epsilon(neg_dec)
transition(pos_zero, '0')
epsilon(pos_dec)
}
// Disambiguate base.
term_state(pos_zero) {
......@@ -85,12 +146,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition_static_if(enable_float || enable_range, pos_dot, '.')
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.
state(start_pos_bin) {
epsilon(pos_bin)
......@@ -98,12 +153,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
term_state(pos_bin) {
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.
state(start_pos_oct) {
epsilon(pos_oct)
......@@ -112,13 +161,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition(pos_oct, octal_chars, add_ascii<8>(result, ch),
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.
state(start_pos_hex) {
epsilon(pos_hex)
......@@ -127,13 +169,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition(pos_hex, hexadecimal_chars, add_ascii<16>(result, ch),
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.
term_state(pos_dec) {
transition(pos_dec, decimal_chars, add_ascii<10>(result, ch),
......@@ -144,14 +179,6 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
transition_static_if(enable_float || enable_range, pos_dot, '.')
}
// 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) {
fsm_transition_static_if(enable_range,
read_number_range(ps, consumer, result),
......@@ -161,14 +188,34 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
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())
term_state(done) {
// nop
}
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,
read_floating_point(ps, consumer, odbl{result}, true),
done, any_char, g.disable())
epsilon(done)
read_floating_point(ps, consumer, odbl{0.}),
done, '.')
fsm_epsilon(read_positive_number(ps, consumer, fl_token, rng_token), done)
}
term_state(done) {
// nop
......@@ -177,45 +224,111 @@ void read_number(State& ps, Consumer& consumer, EnableFloat = {},
// 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--);
/// Generates a range of numbers and calls `consumer` for each value.
template <class Consumer, class T>
void generate_range_impl(pec& code, Consumer& consumer, T min_val, T max_val,
std::optional<int64_t> step) {
auto do_apply = [&](T x) {
using consumer_result_type = decltype(consumer.value(x));
if constexpr (std::is_same_v<consumer_result_type, pec>) {
auto res = consumer.value(x);
if (res == pec::success)
return true;
code = res;
return false;
} 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 (n == m) {
consumer.value(n);
if (min_val == max_val) {
do_apply(min_val);
return;
}
if (s == 0 || (n > m && s > 0) || (n < m && s < 0)) {
ps.code = pec::invalid_range_expression;
if (min_val < max_val) {
auto step_val = step.value_or(1);
if (step_val <= 0) {
code = pec::invalid_range_expression;
return;
}
if (n <= m)
for (auto i = n; i <= m; i += s)
consumer.value(i);
auto s = static_cast<T>(step_val);
auto i = min_val;
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
for (auto i = n; i >= m; i += s)
consumer.value(i);
};
generate_range_impl(code, consumer, min_val,
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([&] {
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;
} else if (!step) {
generate_2(begin, *end);
} 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{};
......
......@@ -15,10 +15,8 @@
#include "caf/detail/parser/read_timespan.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/none.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
#include "caf/timestamp.hpp"
#include "caf/variant.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -35,34 +33,43 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
struct interim_consumer {
size_t invocations = 0;
Consumer* outer = nullptr;
variant<none_t, int64_t, double> interim;
std::variant<none_t, int64_t, double> interim;
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) {
case 1:
interim = x;
break;
case 2:
CAF_ASSERT(holds_alternative<int64_t>(interim));
outer->value(get<int64_t>(interim));
CAF_ASSERT(std::holds_alternative<int64_t>(interim));
outer->value(std::get<int64_t>(interim));
interim = none;
[[fallthrough]];
default:
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) {
interim = x;
}
};
interim_consumer ic;
ic.outer = &consumer;
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 has_int = [&] { return std::holds_alternative<int64_t>(ic.interim); };
auto has_dbl = [&] { return std::holds_alternative<double>(ic.interim); };
auto get_int = [&] { return std::get<int64_t>(ic.interim); };
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
if (has_dbl())
consumer.value(get<double>(ic.interim));
consumer.value(std::get<double>(ic.interim));
else if (has_int())
consumer.value(get_int());
}
......
......@@ -6,12 +6,12 @@
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include "caf/config.hpp"
#include "caf/detail/parser/read_signed_integer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
#include "caf/timestamp.hpp"
......@@ -24,7 +24,7 @@ namespace caf::detail::parser {
/// Reads a timespan.
template <class State, class Consumer>
void read_timespan(State& ps, Consumer&& consumer,
optional<int64_t> num = none) {
std::optional<int64_t> num = std::nullopt) {
using namespace std::chrono;
struct interim_consumer {
using value_type = int64_t;
......
......@@ -1050,6 +1050,10 @@ struct unboxed_oracle<std::optional<T>> {
template <class T>
using unboxed_t = typename unboxed_oracle<T>::type;
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
#undef CAF_HAS_MEMBER_TRAIT
......
......@@ -115,6 +115,11 @@ class ipv4_subnet;
class ipv6_address;
class ipv6_endpoint;
class ipv6_subnet;
class json_array;
class json_object;
class json_reader;
class json_value;
class json_writer;
class local_actor;
class mailbox_element;
class message;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/json_array.hpp"
#include "caf/json_builder.hpp"
#include "caf/json_object.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.hpp"
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
#include <memory>
namespace caf {
/// Represents a JSON array.
class CAF_CORE_EXPORT json_array {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = json_value;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::array::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept : storage_(nullptr) {
// nop
}
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
json_value value() const noexcept {
return json_value{std::addressof(*iter_), storage_};
}
json_value operator*() const noexcept {
return value();
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::array::const_iterator iter_;
detail::json::storage* storage_;
};
// -- constructors, destructors, and assignment operators --------------------
json_array() noexcept : arr_(detail::json::empty_array()) {
// nop
}
json_array(json_array&&) noexcept = default;
json_array(const json_array&) noexcept = default;
json_array& operator=(json_array&&) noexcept = default;
json_array& operator=(const json_array&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the array has no members.
bool empty() const noexcept {
return arr_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this array.
size_t size() const noexcept {
return arr_->size();
}
const_iterator begin() const noexcept {
return {arr_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {arr_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *arr_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_array& arr) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_arr = detail::json::make_array(storage);
if (!detail::json::load(inspector, *internal_arr, storage))
return false;
arr = json_array{internal_arr, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *arr.arr_);
}
}
private:
json_array(const detail::json::value::array* obj,
detail::json::storage_ptr sptr) noexcept
: arr_(obj), storage_(sptr) {
// nop
}
const detail::json::value::array* arr_ = nullptr;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_array& lhs, const json_array& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_array& lhs, const json_array& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_array
CAF_CORE_EXPORT std::string to_string(const json_array& arr);
} // namespace caf
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.hpp"
#include "caf/serializer.hpp"
namespace caf {
/// Serializes an inspectable object to a @ref json_value.
class json_builder : public serializer {
public:
// -- member types -----------------------------------------------------------
using super = serializer;
using type = json_writer::type;
// -- constructors, destructors, and assignment operators --------------------
json_builder();
explicit json_builder(actor_system& sys);
explicit json_builder(execution_unit* ctx);
json_builder(const json_builder&) = delete;
json_builder& operator=(const json_builder&) = delete;
~json_builder() override;
// -- properties -------------------------------------------------------------
/// Returns whether the writer omits empty fields entirely (true) or renders
/// empty fields as `$field: null` (false).
[[nodiscard]] bool skip_empty_fields() const noexcept {
return skip_empty_fields_;
}
/// Configures whether the writer omits empty fields.
void skip_empty_fields(bool value) noexcept {
skip_empty_fields_ = value;
}
/// Returns whether the writer omits '@type' annotations for JSON objects.
[[nodiscard]] bool skip_object_type_annotation() const noexcept {
return skip_object_type_annotation_;
}
/// Configures whether the writer omits '@type' annotations for JSON objects.
void skip_object_type_annotation(bool value) noexcept {
skip_object_type_annotation_ = value;
}
/// Returns the suffix for generating type annotation fields for variant
/// fields. For example, CAF inserts field called "@foo${field_type_suffix}"
/// for a variant field called "foo".
[[nodiscard]] string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers --------------------------------------------------------------
/// Restores the writer to its initial state.
void reset();
/// Seals the JSON value, i.e., rendering it immutable, and returns it. After
/// calling this member function, the @ref json_builder is in a moved-from
/// state and users may only call @c reset to start a new building process or
/// destroy this instance.
json_value seal();
// -- overrides --------------------------------------------------------------
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
bool begin_field(string_view) override;
bool begin_field(string_view name, bool is_present) override;
bool begin_field(string_view name, span<const type_id_t> types,
size_t index) override;
bool begin_field(string_view name, bool is_present,
span<const type_id_t> types, size_t index) override;
bool end_field() override;
bool begin_tuple(size_t size) override;
bool end_tuple() override;
bool begin_key_value_pair() override;
bool end_key_value_pair() override;
bool begin_sequence(size_t size) override;
bool end_sequence() override;
bool begin_associative_array(size_t size) override;
bool end_associative_array() override;
bool value(byte x) override;
bool value(bool x) override;
bool value(int8_t x) override;
bool value(uint8_t x) override;
bool value(int16_t x) override;
bool value(uint16_t x) override;
bool value(int32_t x) override;
bool value(uint32_t x) override;
bool value(int64_t x) override;
bool value(uint64_t x) override;
bool value(float x) override;
bool value(double x) override;
bool value(long double x) override;
bool value(string_view x) override;
bool value(const std::u16string& x) override;
bool value(const std::u32string& x) override;
bool value(span<const byte> x) override;
private:
// -- implementation details -------------------------------------------------
template <class T>
bool number(T);
using key_type = string_view;
// -- state management -------------------------------------------------------
void init();
// Returns the current top of the stack or `null` if empty.
type top();
// Returns the current top of the stack or `null` if empty.
template <class T = detail::json::value>
T* top_ptr();
// Returns the current top-level object.
detail::json::object* top_obj();
// Enters a new level of nesting.
void push(detail::json::value*, type);
// Enters a new level of nesting with type member.
void push(detail::json::value::member*);
// Enters a new level of nesting with type key.
void push(key_type*);
// Backs up one level of nesting.
bool pop();
// Backs up one level of nesting but checks that current top is `t` before.
bool pop_if(type t);
// Sets an error reason that the inspector failed to write a t.
void fail(type t);
// Checks whether any element in the stack has the type `object`.
bool inside_object() const noexcept;
// -- member variables -------------------------------------------------------
// Our output.
detail::json::value* val_;
// Storage for the assembled output.
detail::json::storage_ptr storage_;
struct entry {
union {
detail::json::value* val_ptr;
detail::json::member* mem_ptr;
key_type* key_ptr;
};
type t;
entry(detail::json::value* ptr, type ptr_type) noexcept {
val_ptr = ptr;
t = ptr_type;
}
explicit entry(detail::json::member* ptr) noexcept {
mem_ptr = ptr;
t = type::member;
}
explicit entry(key_type* ptr) noexcept {
key_ptr = ptr;
t = type::key;
}
entry(const entry&) noexcept = default;
entry& operator=(const entry&) noexcept = default;
};
// Bookkeeping for where we are in the current object.
std::vector<entry> stack_;
// Configures whether we omit empty fields entirely (true) or render empty
// fields as `$field: null` (false).
bool skip_empty_fields_ = json_writer::skip_empty_fields_default;
// Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false;
string_view field_type_suffix_ = json_writer::field_type_suffix_default;
};
} // namespace caf
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
namespace caf {
/// Represents a JSON object.
class CAF_CORE_EXPORT json_object {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = std::pair<string_view, json_value>;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::object::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept = default;
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
string_view key() const noexcept {
return iter_->key;
}
json_value value() const noexcept {
return json_value{iter_->val, storage_};
}
value_type operator*() const noexcept {
return {key(), value()};
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::object::const_iterator iter_;
detail::json::storage* storage_ = nullptr;
};
// -- constructors, destructors, and assignment operators --------------------
json_object() noexcept : obj_(detail::json::empty_object()) {
// nop
}
json_object(json_object&&) noexcept = default;
json_object(const json_object&) noexcept = default;
json_object& operator=(json_object&&) noexcept = default;
json_object& operator=(const json_object&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the object has no members.
bool empty() const noexcept {
return !obj_ || obj_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this object.
size_t size() const noexcept {
return obj_ ? obj_->size() : 0u;
}
/// Returns the value for @p key or an @c undefined value if the object does
/// not contain a value for @p key.
json_value value(string_view key) const;
const_iterator begin() const noexcept {
return {obj_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {obj_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *obj_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_object& obj) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_obj = detail::json::make_object(storage);
if (!detail::json::load(inspector, *internal_obj, storage))
return false;
obj = json_object{internal_obj, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *obj.obj_);
}
}
private:
json_object(const detail::json::value::object* obj,
detail::json::storage_ptr sptr) noexcept
: obj_(obj), storage_(sptr) {
// nop
}
const detail::json::value::object* obj_ = nullptr;
detail::json::storage_ptr storage_;
};
inline bool operator==(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_object& lhs,
const json_object& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_object& lhs,
const json_object& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_object
CAF_CORE_EXPORT std::string to_string(const json_object& val);
} // namespace caf
......@@ -133,6 +133,17 @@ public:
/// @note Implicitly calls `reset`.
bool load(string_view json_text);
/// Parses the content of the file under the given @p path. After loading the
/// content of the JSON file, the reader is ready for attempting to
/// deserialize inspectable objects.
/// @note Implicitly calls `reset`.
bool load_file(const char* path);
/// @copydoc load_file
bool load_file(const std::string& path) {
return load_file(path.c_str());
}
/// Reverts the state of the reader back to where it was after calling `load`.
/// @post The reader is ready for attempting to deserialize another
/// inspectable object.
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/make_counted.hpp"
#include "caf/string_view.hpp"
#include <string>
namespace caf {
/// Represents an immutable JSON value.
class CAF_CORE_EXPORT json_value {
public:
// -- constructors, destructors, and assignment operators --------------------
/// Creates a @c null JSON value.
json_value() noexcept : val_(detail::json::null_value()) {
// nop
}
json_value(const detail::json::value* val,
detail::json::storage_ptr sptr) noexcept
: val_(val), storage_(sptr) {
// nop
}
json_value(json_value&&) noexcept = default;
json_value(const json_value&) noexcept = default;
json_value& operator=(json_value&&) noexcept = default;
json_value& operator=(const json_value&) noexcept = default;
// -- factories --------------------------------------------------------------
/// Creates an undefined JSON value. This special state usually indicates that
/// a previous key lookup failed.
static json_value undefined() noexcept {
return json_value{detail::json::undefined_value(), nullptr};
}
// -- properties -------------------------------------------------------------
/// Checks whether the value is @c null.
bool is_null() const noexcept {
return val_->is_null();
}
/// Checks whether the value is undefined. This special state indicates that a
/// previous key lookup failed.
bool is_undefined() const noexcept {
return val_->is_undefined();
}
/// Checks whether the value is an @c int64_t.
bool is_integer() const noexcept;
/// Checks whether the value is an @c uint64_t.
bool is_unsigned() const noexcept;
/// Checks whether the value is a @c double.
bool is_double() const noexcept {
return val_->is_double();
}
/// Checks whether the value is a number, i.e., an @c int64_t, a @c uint64_t
/// or a @c double.
bool is_number() const noexcept;
/// Checks whether the value is a @c bool.
bool is_bool() const noexcept {
return val_->is_bool();
}
/// Checks whether the value is a JSON string (@c string_view).
bool is_string() const noexcept {
return val_->is_string();
}
/// Checks whether the value is an JSON array.
bool is_array() const noexcept {
return val_->is_array();
}
/// Checks whether the value is a JSON object.
bool is_object() const noexcept {
return val_->is_object();
}
// -- 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;
/// 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;
/// 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;
/// Returns the value as a JSON string or an empty string if the value is not
/// a string.
string_view to_string() const;
/// Returns the value as a JSON string or @p fallback if the value is not a
/// string.
string_view to_string(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;
/// 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;
/// Returns the value as a JSON object or an empty object if the value is not
/// an object.
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;
// -- comparison -------------------------------------------------------------
/// Compares two JSON values for equality.
bool equal_to(const json_value& other) const noexcept;
// -- parsing ----------------------------------------------------------------
/// Attempts to parse @p str as JSON input into a self-contained value.
static expected<json_value> parse(string_view str);
/// Attempts to parse @p str as JSON input into a value that avoids copies
/// where possible by pointing into @p str.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_shallow(string_view str);
/// Attempts to parse @p str as JSON input. Decodes JSON in place and points
/// back into the @p str for all strings in the JSON input.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_in_situ(std::string& str);
/// Attempts to parse the content of the file at @p path as JSON input into a
/// self-contained value.
static expected<json_value> parse_file(const char* path);
/// @copydoc parse_file
static expected<json_value> parse_file(const std::string& path);
// -- printing ---------------------------------------------------------------
/// Prints the JSON value to @p buf.
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
/// Applies the `Inspector` to the JSON value `val`.
template <class Inspector>
friend bool inspect(Inspector& inspector, json_value& val) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_val = detail::json::make_value(storage);
if (!detail::json::load(inspector, *internal_val, storage))
return false;
val = json_value{internal_val, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *val.val_);
}
}
private:
const detail::json::value* val_;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_value& lhs, const json_value& rhs) {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_value& lhs, const json_value& rhs) {
return !(lhs == rhs);
}
/// @relates json_value
CAF_CORE_EXPORT std::string to_string(const json_value& val);
} // namespace caf
......@@ -210,7 +210,7 @@ private:
void init();
// Returns the current top of the stack or `null_literal` if empty.
// Returns the current top of the stack or `null` if empty.
type top();
// Enters a new level of nesting.
......@@ -261,11 +261,6 @@ private:
// followed by a newline.
void sep();
// Closes a nested structure like lists or objects. Traces back to see if only
// whitespaces were added between the open character and the current position.
// If so, compress the output to produce "[]" instead of "[\n \n]".
void close_nested(char open, char close);
// -- member variables -------------------------------------------------------
// The current level of indentation.
......@@ -305,4 +300,12 @@ private:
const type_id_mapper* mapper_ = &default_mapper_;
};
/// @relates json_writer::type
CAF_CORE_EXPORT std::string_view as_json_type_name(json_writer::type t);
/// @relates json_writer::type
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
} // namespace caf
......@@ -6,16 +6,18 @@
#include <cctype>
#include <cstdint>
#include <string_view>
#include "caf/fwd.hpp"
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Stores all information necessary for implementing an FSM-based parser.
template <class Iterator, class Sentinel>
struct parser_state {
using iterator_type = Iterator;
/// Current position of the parser.
Iterator i;
......
......@@ -66,6 +66,8 @@ enum class pec : uint8_t {
/// Stopped after running into an invalid parser state. Should never happen
/// and most likely indicates a bug in the implementation.
invalid_state,
/// Parser stopped after exceeding its maximum supported level of nesting.
nested_too_deeply,
};
/// @relates pec
......
......@@ -417,6 +417,9 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_address))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_endpoint))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_subnet))
CAF_ADD_TYPE_ID(core_module, (caf::json_array))
CAF_ADD_TYPE_ID(core_module, (caf::json_object))
CAF_ADD_TYPE_ID(core_module, (caf::json_value))
CAF_ADD_TYPE_ID(core_module, (caf::message))
CAF_ADD_TYPE_ID(core_module, (caf::message_id))
CAF_ADD_TYPE_ID(core_module, (caf::node_down_msg))
......
......@@ -4,8 +4,11 @@
#include "caf/detail/json.hpp"
#include <cstring>
#include <iterator>
#include <memory>
#include <numeric>
#include <streambuf>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
......@@ -14,11 +17,128 @@
#include "caf/detail/parser/read_number.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace {
constexpr size_t max_nesting_level = 128;
size_t do_unescape(const char* i, const char* e, char* out) {
size_t new_size = 0;
while (i != e) {
switch (*i) {
default:
if (i != out) {
*out++ = *i++;
} else {
++out;
++i;
}
++new_size;
break;
case '\\':
if (++i != e) {
switch (*i) {
case '"':
*out++ = '"';
break;
case '\\':
*out++ = '\\';
break;
case 'b':
*out++ = '\b';
break;
case 'f':
*out++ = '\f';
break;
case 'n':
*out++ = '\n';
break;
case 'r':
*out++ = '\r';
break;
case 't':
*out++ = '\t';
break;
case 'v':
*out++ = '\v';
break;
default:
// TODO: support control characters in \uXXXX notation.
*out++ = '?';
}
++i;
++new_size;
}
}
}
return new_size;
}
caf::string_view as_str_view(const char* first, const char* last) {
return {first, static_cast<size_t>(last - first)};
}
struct regular_unescaper {
caf::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
auto len = static_cast<size_t>(last - first);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(len);
if (!is_escaped) {
strncpy(str_buf, first, len);
return caf::string_view{str_buf, len};
}
auto unescaped_size = do_unescape(first, last, str_buf);
return caf::string_view{str_buf, unescaped_size};
}
};
struct shallow_unescaper {
caf::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(static_cast<size_t>(last - first));
auto unescaped_size = do_unescape(first, last, str_buf);
return caf::string_view{str_buf, unescaped_size};
}
};
struct in_situ_unescaper {
caf::string_view operator()(caf::detail::monotonic_buffer_resource*,
char* first, char* last, bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
auto unescaped_size = do_unescape(first, last, first);
return caf::string_view{first, unescaped_size};
}
};
template <class Escaper, class Consumer, class Iterator>
void assign_value(Escaper escaper, Consumer& consumer, Iterator first,
Iterator last, bool is_escaped) {
auto iter2ptr = [](Iterator iter) {
if constexpr (std::is_pointer_v<Iterator>)
return iter;
else
return std::addressof(*iter);
};
auto str = escaper(consumer.storage, iter2ptr(first), iter2ptr(last),
is_escaped);
consumer.value(str);
}
} // namespace
namespace caf::detail::parser {
struct obj_consumer;
......@@ -31,6 +151,7 @@ struct val_consumer {
template <class T>
void value(T x) {
if constexpr (std::is_same_v<T,uint64_t>) puts("GOT AN uint64_t");
ptr->data = x;
}
......@@ -40,6 +161,7 @@ struct val_consumer {
};
struct key_consumer {
monotonic_buffer_resource* storage;
string_view* ptr;
void value(string_view str) {
......@@ -52,7 +174,7 @@ struct member_consumer {
json::member* ptr;
key_consumer begin_key() {
return {std::addressof(ptr->key)};
return {storage, std::addressof(ptr->key)};
}
val_consumer begin_val() {
......@@ -65,8 +187,8 @@ struct obj_consumer {
json::object* ptr;
member_consumer begin_member() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_member = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_member};
}
};
......@@ -74,29 +196,25 @@ struct arr_consumer {
json::array* ptr;
val_consumer begin_value() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_element = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_element};
}
};
arr_consumer val_consumer::begin_array() {
ptr->data = json::array(json::value::array_allocator{storage});
auto& arr = std::get<json::array>(ptr->data);
arr.reserve(16);
return {&arr};
}
obj_consumer val_consumer::begin_object() {
ptr->data = json::object(json::value::member_allocator{storage});
ptr->data = json::object(json::value::object_allocator{storage});
auto& obj = std::get<json::object>(ptr->data);
obj.reserve(16);
return {&obj};
}
void read_value(string_parser_state& ps, val_consumer consumer);
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
template <class ParserState, class Consumer>
void read_json_null_or_nan(ParserState& ps, Consumer consumer) {
enum { nil, is_null, is_nan };
auto res_type = nil;
auto g = make_scope_guard([&] {
......@@ -128,15 +246,22 @@ void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
transition(done, 'n', res_type = is_nan)
}
term_state(done) {
transition(init, " \t\n")
transition(done, " \t\n")
}
fin();
// clang-format on
}
template <class Consumer>
void read_json_string(string_parser_state& ps, Consumer consumer) {
auto first = string_view::iterator{};
// If we have an iterator into a contiguous memory block, we simply store the
// iterator position and use the escaper to decide whether we make regular,
// shallow or in-situ copies. Otherwise, we use the scratch-space and decode the
// string while parsing.
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, unit_t, Unescaper escaper,
Consumer consumer) {
using iterator_t = typename ParserState::iterator_type;
iterator_t first;
// clang-format off
start();
state(init) {
......@@ -145,12 +270,52 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"', consumer.value(string_view{first, ps.i}))
transition(done, '"', assign_value(escaper, consumer, first, ps.i, false))
transition(read_chars, any_char)
}
state(read_chars_after_escape) {
transition(escape, '\\')
transition(done, '"', assign_value(escaper, consumer, first, ps.i, true))
transition(read_chars_after_escape, any_char)
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars_after_escape, "\"\\/bfnrtv")
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, std::vector<char>& scratch_space,
Unescaper escaper, Consumer consumer) {
scratch_space.clear();
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(read_chars, '"')
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"',
assign_value(escaper, consumer, scratch_space.begin(),
scratch_space.end(), false))
transition(read_chars, any_char, scratch_space.push_back(ch))
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, "\"\\/bfnrt")
transition(read_chars, '"', scratch_space.push_back('"'))
transition(read_chars, '\\', scratch_space.push_back('\\'))
transition(read_chars, 'b', scratch_space.push_back('\b'))
transition(read_chars, 'f', scratch_space.push_back('\f'))
transition(read_chars, 'n', scratch_space.push_back('\n'))
transition(read_chars, 'r', scratch_space.push_back('\r'))
transition(read_chars, 't', scratch_space.push_back('\t'))
transition(read_chars, 'v', scratch_space.push_back('\v'))
}
term_state(done) {
transition(done, " \t\n")
......@@ -159,16 +324,23 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
void read_member(string_parser_state& ps, member_consumer consumer) {
template <class ParserState, class ScratchSpace, class Unescaper>
void read_member(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
member_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer.begin_key()), after_key, '"')
fsm_epsilon(read_json_string(ps, scratch_space, unescaper,
consumer.begin_key()),
after_key, '"')
}
state(after_key) {
transition(after_key, " \t\n")
fsm_transition(read_value(ps, consumer.begin_val()), done, ':')
fsm_transition(read_value(ps, scratch_space, unescaper, nesting_level,
consumer.begin_val()),
done, ':')
}
term_state(done) {
transition(done, " \t\n")
......@@ -177,7 +349,14 @@ void read_member(string_parser_state& ps, member_consumer consumer) {
// clang-format on
}
void read_json_object(string_parser_state& ps, obj_consumer consumer) {
template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_object(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
obj_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
return;
}
// clang-format off
start();
state(init) {
......@@ -186,7 +365,9 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
transition(done, '}')
}
state(after_member) {
......@@ -196,7 +377,9 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
}
term_state(done) {
transition(done, " \t\n")
......@@ -205,7 +388,14 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
// clang-format on
}
void read_json_array(string_parser_state& ps, arr_consumer consumer) {
template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_array(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
arr_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
return;
}
// clang-format off
start();
state(init) {
......@@ -215,7 +405,9 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
state(has_open_brace) {
transition(has_open_brace, " \t\n")
transition(done, ']')
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
state(after_value) {
transition(after_value, " \t\n")
......@@ -224,7 +416,9 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
term_state(done) {
transition(done, " \t\n")
......@@ -233,17 +427,25 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
// clang-format on
}
void read_value(string_parser_state& ps, val_consumer consumer) {
template <class ParserState, class ScratchSpace, class Unescaper>
void read_value(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
val_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer), done, '"')
fsm_epsilon(read_json_string(ps, scratch_space, unescaper, consumer),
done, '"')
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n")
fsm_epsilon(read_number(ps, consumer), done, "+-.0123456789")
fsm_epsilon(read_json_object(ps, consumer.begin_object()), done, '{')
fsm_epsilon(read_json_array(ps, consumer.begin_array()), done, '[')
fsm_epsilon(read_json_object(ps, scratch_space, unescaper, nesting_level,
consumer.begin_object()),
done, '{')
fsm_epsilon(read_json_array(ps, scratch_space, unescaper, nesting_level,
consumer.begin_array()),
done, '[')
}
term_state(done) {
transition(done, " \t\n")
......@@ -260,9 +462,10 @@ namespace caf::detail::json {
namespace {
template <class T, class Allocator>
void init(std::vector<T, Allocator>* ptr, monotonic_buffer_resource* storage) {
new (ptr) std::vector<T, Allocator>(Allocator{storage});
template <class T>
void init(linked_list<T>* ptr, monotonic_buffer_resource* storage) {
using allocator_type = typename linked_list<T>::allocator_type;
new (ptr) linked_list<T>(allocator_type{storage});
}
void init(value* ptr, monotonic_buffer_resource*) {
......@@ -277,28 +480,103 @@ T* make_impl(monotonic_buffer_resource* storage) {
return result;
}
const value null_value_instance;
const value undefined_value_instance = value{undefined_t{}};
const object empty_object_instance;
const array empty_array_instance;
} // namespace
string_view realloc(string_view str, monotonic_buffer_resource* res) {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(str.size());
strncpy(buf, str.data(), str.size());
return string_view{buf, str.size()};
}
string_view concat(std::initializer_list<string_view> xs,
monotonic_buffer_resource* res) {
auto get_size = [](size_t x, string_view str) { return x + str.size(); };
auto total_size = std::accumulate(xs.begin(), xs.end(), size_t{0}, get_size);
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto* buf = alloc_t{res}.allocate(total_size);
auto* pos = buf;
for (auto str : xs) {
strncpy(pos, str.data(), str.size());
pos += str.size();
}
return string_view{buf, total_size};
}
value* make_value(monotonic_buffer_resource* storage) {
return make_impl<value>(storage);
}
array* make_array(monotonic_buffer_resource* storage) {
auto result = make_impl<array>(storage);
result->reserve(16);
return result;
}
object* make_object(monotonic_buffer_resource* storage) {
auto result = make_impl<object>(storage);
result->reserve(16);
return result;
}
const value* null_value() noexcept {
return &null_value_instance;
}
const value* undefined_value() noexcept {
return &undefined_value_instance;
}
const object* empty_object() noexcept {
return &empty_object_instance;
}
const array* empty_array() noexcept {
return &empty_array_instance;
}
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) {
unit_t scratch_space;
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage) {
std::vector<char> scratch_space;
scratch_space.reserve(64);
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
shallow_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
in_situ_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, {storage, result});
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
......
......@@ -21,6 +21,9 @@
#include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/ipv6_subnet.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/json_value.hpp"
#include "caf/message.hpp"
#include "caf/message_id.hpp"
#include "caf/node_id.hpp"
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_array.hpp"
namespace caf {
std::string to_string(const json_array& arr) {
std::string result;
arr.print_to(result);
return result;
}
} // namespace caf
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_builder.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include "caf/json_value.hpp"
#include <type_traits>
using namespace std::literals;
namespace caf {
namespace {
static constexpr const char class_name[] = "caf::json_builder";
} // namespace
// -- implementation details ---------------------------------------------------
template <class T>
bool json_builder::number(T x) {
switch (top()) {
case type::element: {
if constexpr (std::is_floating_point_v<T>) {
top_ptr()->data = static_cast<double>(x);
} else {
top_ptr()->data = static_cast<int64_t>(x);
}
pop();
return true;
}
case type::key: {
std::string str;
detail::print(str, x);
*top_ptr<key_type>() = detail::json::realloc(str, &storage_->buf);
return true;
}
case type::array: {
auto& new_entry = top_ptr<detail::json::array>()->emplace_back();
if constexpr (std::is_floating_point_v<T>) {
new_entry.data = static_cast<double>(x);
} else {
new_entry.data = static_cast<int64_t>(x);
}
return true;
}
default:
fail(type::number);
return false;
}
}
// -- constructors, destructors, and assignment operators ----------------------
json_builder::json_builder() : json_builder(nullptr) {
init();
}
json_builder::json_builder(actor_system& sys) : super(sys) {
init();
}
json_builder::json_builder(execution_unit* ctx) : super(ctx) {
init();
}
json_builder::~json_builder() {
// nop
}
// -- modifiers ----------------------------------------------------------------
void json_builder::reset() {
stack_.clear();
if (!storage_) {
storage_ = make_counted<detail::json::storage>();
} else {
storage_->buf.reclaim();
}
val_ = detail::json::make_value(storage_);
push(val_, type::element);
}
json_value json_builder::seal() {
return json_value{val_, std::move(storage_)};
}
// -- overrides ----------------------------------------------------------------
bool json_builder::begin_object(type_id_t id, string_view name) {
auto add_type_annotation = [this, id, name] {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = "@type"sv;
pop();
CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) {
top_ptr()->data = tname;
} else {
top_ptr()->data = name;
}
pop();
return true;
};
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
&& begin_key_value_pair() // ... add implicit @type member, ..
&& add_type_annotation() // ... write content ...
&& end_key_value_pair(); // ... and wait for next field.
}
bool json_builder::end_object() {
return end_associative_array();
}
bool json_builder::begin_field(string_view name) {
if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(string_view name, bool is_present) {
if (skip_empty_fields_ && !is_present) {
auto t = top();
switch (t) {
case type::object:
push(static_cast<detail::json::member*>(nullptr));
return true;
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
} else if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
if (!is_present) {
// We don't need to assign nullptr explicitly since it's the default.
pop();
}
return true;
} else {
return false;
}
}
bool json_builder::begin_field(string_view name,
span<const type_id_t> types, size_t index) {
if (index >= types.size()) {
emplace_error(sec::runtime_error, "index >= types.size()");
return false;
}
if (begin_key_value_pair()) {
if (auto tname = query_type_name(types[index]); !tname.empty()) {
auto& annotation = top_obj()->emplace_back();
annotation.key = detail::json::concat({"@"sv, name, field_type_suffix_},
&storage_->buf);
annotation.val = detail::json::make_value(storage_);
annotation.val->data = tname;
} else {
emplace_error(sec::runtime_error, "query_type_name failed");
return false;
}
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(string_view name, bool is_present,
span<const type_id_t> types, size_t index) {
if (is_present)
return begin_field(name, types, index);
else
return begin_field(name, is_present);
}
bool json_builder::end_field() {
return end_key_value_pair();
}
bool json_builder::begin_tuple(size_t size) {
return begin_sequence(size);
}
bool json_builder::end_tuple() {
return end_sequence();
}
bool json_builder::begin_key_value_pair() {
auto t = top();
switch (t) {
case type::object: {
auto* obj = top_ptr<detail::json::object>();
auto& new_member = obj->emplace_back();
new_member.val = detail::json::make_value(storage_);
push(&new_member);
push(new_member.val, type::element);
push(&new_member.key);
return true;
}
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
}
bool json_builder::end_key_value_pair() {
return pop_if(type::member);
}
bool json_builder::begin_sequence(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, "unexpected begin_sequence");
return false;
case type::element: {
auto* val = top_ptr();
val->assign_array(storage_);
push(val, type::array);
return true;
}
case type::array: {
auto* arr = top_ptr<detail::json::array>();
auto& new_val = arr->emplace_back();
new_val.assign_array(storage_);
push(&new_val, type::array);
return true;
}
}
}
bool json_builder::end_sequence() {
return pop_if(type::array);
}
bool json_builder::begin_associative_array(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, class_name, __func__,
"unexpected begin_object or begin_associative_array");
return false;
case type::element:
top_ptr()->assign_object(storage_);
stack_.back().t = type::object;
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_object(storage_);
push(&new_val, type::object);
return true;
}
}
}
bool json_builder::end_associative_array() {
return pop_if(type::object);
}
bool json_builder::value(byte x) {
return number(to_integer<uint8_t>(x));
}
bool json_builder::value(bool x) {
switch (top()) {
case type::element:
top_ptr()->data = x;
pop();
return true;
case type::key:
*top_ptr<key_type>() = x ? "true"sv : "false"sv;
return true;
case type::array: {
auto* arr = top_ptr<detail::json::array>();
arr->emplace_back().data = x;
return true;
}
default:
fail(type::boolean);
return false;
}
}
bool json_builder::value(int8_t x) {
return number(x);
}
bool json_builder::value(uint8_t x) {
return number(x);
}
bool json_builder::value(int16_t x) {
return number(x);
}
bool json_builder::value(uint16_t x) {
return number(x);
}
bool json_builder::value(int32_t x) {
return number(x);
}
bool json_builder::value(uint32_t x) {
return number(x);
}
bool json_builder::value(int64_t x) {
return number(x);
}
bool json_builder::value(uint64_t x) {
return number(x);
}
bool json_builder::value(float x) {
return number(x);
}
bool json_builder::value(double x) {
return number(x);
}
bool json_builder::value(long double x) {
return number(x);
}
bool json_builder::value(string_view x) {
switch (top()) {
case type::element:
top_ptr()->assign_string(x, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(x, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(x, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
bool json_builder::value(const std::u16string&) {
emplace_error(sec::unsupported_operation,
"u16string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(const std::u32string&) {
emplace_error(sec::unsupported_operation,
"u32string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(span<const byte> x) {
std::vector<char> buf;
buf.reserve(x.size() * 2);
detail::append_hex(buf, reinterpret_cast<const void*>(x.data()), x.size());
auto hex_str = string_view{buf.data(), buf.size()};
switch (top()) {
case type::element:
top_ptr()->assign_string(hex_str, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(hex_str, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(hex_str, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
// -- state management ---------------------------------------------------------
void json_builder::init() {
has_human_readable_format_ = true;
storage_ = make_counted<detail::json::storage>();
val_ = detail::json::make_value(storage_);
stack_.reserve(32);
push(val_, type::element);
}
json_builder::type json_builder::top() {
if (!stack_.empty())
return stack_.back().t;
else
return type::null;
}
template <class T>
T* json_builder::top_ptr() {
if constexpr (std::is_same_v<T, key_type>) {
return stack_.back().key_ptr;
} else if constexpr (std::is_same_v<T, detail::json::member>) {
return stack_.back().mem_ptr;
} else if constexpr (std::is_same_v<T, detail::json::object>) {
return &std::get<detail::json::object>(stack_.back().val_ptr->data);
} else if constexpr (std::is_same_v<T, detail::json::array>) {
return &std::get<detail::json::array>(stack_.back().val_ptr->data);
} else {
static_assert(std::is_same_v<T, detail::json::value>);
return stack_.back().val_ptr;
}
}
detail::json::object* json_builder::top_obj() {
auto is_obj = [](const entry& x) { return x.t == type::object; };
auto i = std::find_if(stack_.rbegin(), stack_.rend(), is_obj);
if (i != stack_.rend())
return &std::get<detail::json::object>(i->val_ptr->data);
CAF_RAISE_ERROR("json_builder::top_obj was unable to find an object");
}
void json_builder::push(detail::json::value* ptr, type t) {
stack_.emplace_back(ptr, t);
}
void json_builder::push(detail::json::value::member* ptr) {
stack_.emplace_back(ptr);
}
void json_builder::push(key_type* ptr) {
stack_.emplace_back(ptr);
}
// Backs up one level of nesting.
bool json_builder::pop() {
if (!stack_.empty()) {
stack_.pop_back();
return true;
} else {
std::string str = "pop() called with an empty stack: begin/end mismatch";
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
bool json_builder::pop_if(type t) {
if (!stack_.empty() && stack_.back().t == t) {
stack_.pop_back();
return true;
} else {
std::string str = "pop_if failed: expected ";
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
void json_builder::fail(type t) {
std::string str = "failed to write a ";
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
bool json_builder::inside_object() const noexcept {
auto is_object = [](const entry& x) { return x.t == type::object; };
return std::any_of(stack_.begin(), stack_.end(), is_object);
}
} // namespace caf
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_object.hpp"
namespace caf {
// -- properties ---------------------------------------------------------------
json_value json_object::value(string_view key) const {
auto pred = [key](const auto& member) { return member.key == key; };
auto i = std::find_if(obj_->begin(), obj_->end(), pred);
if (i != obj_->end()) {
return {i->val, storage_};
}
return json_value::undefined();
}
std::string to_string(const json_object& obj) {
std::string result;
obj.print_to(result);
return result;
}
} // namespace caf
......@@ -8,6 +8,8 @@
#include "caf/detail/print.hpp"
#include "caf/string_algorithms.hpp"
#include <fstream>
namespace {
static constexpr const char class_name[] = "caf::json_reader";
......@@ -50,23 +52,24 @@ std::string type_clash(caf::json_reader::position want,
std::string type_clash(caf::string_view want,
const caf::detail::json::value& got) {
using namespace caf::literals;
using namespace std::literals;
using caf::detail::json::value;
switch (got.data.index()) {
case value::integer_index:
return type_clash(want, "json::integer"_sv);
case value::unsigned_index:
return type_clash(want, "json::integer"sv);
case value::double_index:
return type_clash(want, "json::real"_sv);
return type_clash(want, "json::real"sv);
case value::bool_index:
return type_clash(want, "json::boolean"_sv);
return type_clash(want, "json::boolean"sv);
case value::string_index:
return type_clash(want, "json::string"_sv);
return type_clash(want, "json::string"sv);
case value::array_index:
return type_clash(want, "json::array"_sv);
return type_clash(want, "json::array"sv);
case value::object_index:
return type_clash(want, "json::object"_sv);
return type_clash(want, "json::object"sv);
default:
return type_clash(want, "json::null"_sv);
return type_clash(want, "json::null"sv);
}
}
......@@ -145,19 +148,41 @@ json_reader::~json_reader() {
bool json_reader::load(string_view json_text) {
reset();
string_parser_state ps{json_text.begin(), json_text.end()};
root_ = detail::json::parse(ps, &buf_);
root_ = detail::json::parse_shallow(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
return false;
} else {
}
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
bool json_reader::load_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
reset();
std::ifstream input{path};
if (!input.is_open()) {
emplace_error(sec::cannot_open_file);
return false;
}
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
root_ = detail::json::parse(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
return false;
}
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
void json_reader::revert() {
......@@ -166,6 +191,7 @@ void json_reader::revert() {
err_.reset();
st_->clear();
st_->emplace_back(root_);
field_.clear();
}
}
......@@ -173,6 +199,7 @@ void json_reader::reset() {
buf_.reclaim();
st_ = nullptr;
err_.reset();
field_.clear();
}
// -- interface functions ------------------------------------------------------
......@@ -199,7 +226,7 @@ bool json_reader::fetch_next_object_name(string_view& type_name) {
FN_DECL;
return consume<false>(fn, [this, &type_name](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
auto& obj = get<detail::json::object>(val.data);
auto& obj = std::get<detail::json::object>(val.data);
if (auto mem_ptr = find_member(&obj, "@type")) {
if (mem_ptr->val->data.index() == detail::json::value::string_index) {
type_name = std::get<string_view>(mem_ptr->val->data);
......@@ -227,7 +254,7 @@ bool json_reader::begin_object(type_id_t, string_view) {
FN_DECL;
return consume<false>(fn, [this](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
push(&get<detail::json::object>(val.data));
push(&std::get<detail::json::object>(val.data));
return true;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
......@@ -260,8 +287,8 @@ bool json_reader::end_object() {
bool json_reader::begin_field(string_view name) {
SCOPE(position::object);
if (auto member = find_member(top<position::object>(), name)) {
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name)) {
push(member->val);
return true;
} else {
......@@ -273,10 +300,10 @@ bool json_reader::begin_field(string_view name) {
bool json_reader::begin_field(string_view name, bool& is_present) {
SCOPE(position::object);
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name);
member != nullptr
&& member->val->data.index() != detail::json::value::null_index) {
field_.push_back(name);
push(member->val);
is_present = true;
} else {
......@@ -285,8 +312,8 @@ bool json_reader::begin_field(string_view name, bool& is_present) {
return true;
}
bool json_reader::begin_field(string_view name, span<const type_id_t> types,
size_t& index) {
bool json_reader::begin_field(string_view name,
span<const type_id_t> types, size_t& index) {
bool is_present = false;
if (begin_field(name, is_present, types, index)) {
if (is_present) {
......@@ -304,6 +331,7 @@ bool json_reader::begin_field(string_view name, span<const type_id_t> types,
bool json_reader::begin_field(string_view name, bool& is_present,
span<const type_id_t> types, size_t& index) {
SCOPE(position::object);
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name);
member != nullptr
&& member->val->data.index() != detail::json::value::null_index) {
......@@ -312,7 +340,6 @@ bool json_reader::begin_field(string_view name, bool& is_present,
if (auto i = std::find(types.begin(), types.end(), id);
i != types.end()) {
index = static_cast<size_t>(std::distance(types.begin(), i));
field_.push_back(name);
push(member->val);
is_present = true;
return true;
......@@ -327,6 +354,7 @@ bool json_reader::end_field() {
SCOPE(position::object);
// Note: no pop() here, because the value(s) were already consumed. Only
// update the field_ for debugging.
if (!field_.empty())
field_.pop_back();
return true;
}
......@@ -379,7 +407,7 @@ bool json_reader::begin_sequence(size_t& size) {
FN_DECL;
return consume<false>(fn, [this, &size](const detail::json::value& val) {
if (val.data.index() == detail::json::value::array_index) {
auto& ls = get<detail::json::array>(val.data);
auto& ls = std::get<detail::json::array>(val.data);
size = ls.size();
push(sequence{ls.begin(), ls.end()});
return true;
......@@ -410,7 +438,7 @@ bool json_reader::begin_associative_array(size_t& size) {
FN_DECL;
return consume<false>(fn, [this, &size](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
auto* obj = std::addressof(get<detail::json::object>(val.data));
auto* obj = std::addressof(std::get<detail::json::object>(val.data));
pop();
size = obj->size();
push(members{obj->begin(), obj->end()});
......@@ -507,11 +535,19 @@ bool json_reader::integer(T& x) {
if (detail::bounds_checker<T>::check(i64)) {
x = static_cast<T>(i64);
return true;
} else {
}
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
"signed integer out of bounds");
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,
"unsigned integer out of bounds");
return false;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::integer", val));
......@@ -565,10 +601,17 @@ bool json_reader::value(float& x) {
bool json_reader::value(double& x) {
FN_DECL;
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);
return true;
} else {
case detail::json::value::integer_index:
x = std::get<int64_t>(val.data);
return true;
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(),
type_clash("json::real", val));
return false;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_value.hpp"
#include "caf/expected.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/make_counted.hpp"
#include "caf/parser_state.hpp"
#include <cstdint>
#include <fstream>
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 ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const {
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
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;
}
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
}
}
uint64_t json_value::to_unsigned(uint64_t fallback) const {
switch (val_->data.index()) {
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;
}
case detail::json::value::unsigned_index:
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;
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 {
if (is_bool()) {
return std::get<bool>(val_->data);
}
return fallback;
}
string_view json_value::to_string() const {
return to_string(string_view{});
}
string_view json_value::to_string(string_view fallback) const {
if (is_string()) {
return std::get<string_view>(val_->data);
}
return fallback;
}
json_object json_value::to_object() const {
return to_object(json_object{});
}
json_object json_value::to_object(json_object fallback) const {
if (is_object()) {
return json_object{&std::get<detail::json::object>(val_->data), storage_};
}
return fallback;
}
json_array json_value::to_array() const {
return to_array(json_array{});
}
json_array json_value::to_array(json_array fallback) const {
if (is_array()) {
return json_array{&std::get<detail::json::array>(val_->data), storage_};
}
return fallback;
}
// -- comparison ---------------------------------------------------------------
bool json_value::equal_to(const json_value& other) const noexcept {
if (val_ == other.val_) {
return true;
}
if (val_ != nullptr && other.val_ != nullptr) {
return *val_ == *other.val_;
}
return false;
}
// -- parsing ------------------------------------------------------------------
expected<json_value> json_value::parse(string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_shallow(string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse_shallow(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_in_situ(std::string& str) {
auto storage = make_counted<detail::json::storage>();
detail::json::mutable_string_parser_state ps{str.data(),
str.data() + str.size()};
auto root = detail::json::parse_in_situ(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
std::ifstream input{path};
if (!input.is_open())
return make_error(sec::cannot_open_file);
auto storage = make_counted<detail::json::storage>();
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const std::string& path) {
return parse_file(path.c_str());
}
// -- free functions -----------------------------------------------------------
std::string to_string(const json_value& val) {
std::string result;
val.print_to(result);
return result;
}
} // namespace caf
......@@ -7,24 +7,21 @@
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include <cctype>
namespace caf {
namespace {
static constexpr const char class_name[] = "caf::json_writer";
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
constexpr const char* json_type_names[] = {"element", "object", "member",
constexpr std::string_view json_type_names[] = {"element", "object", "member",
"array", "string", "number",
"bool", "null"};
constexpr const char* json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
char last_non_ws_char(const std::vector<char>& buf) {
auto not_ws = [](char c) { return !std::isspace(c); };
auto last = buf.rend();
auto i = std::find_if(buf.rbegin(), last, not_ws);
return (i != last) ? *i : '\0';
}
} // namespace
......@@ -99,7 +96,7 @@ bool json_writer::begin_object(type_id_t id, string_view name) {
pop();
return true;
};
if (inside_object() || skip_object_type_annotation_)
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
......@@ -135,7 +132,7 @@ bool json_writer::begin_field(string_view name, bool is_present) {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -157,8 +154,8 @@ bool json_writer::begin_field(string_view name, bool is_present) {
}
}
bool json_writer::begin_field(string_view name, span<const type_id_t> types,
size_t index) {
bool json_writer::begin_field(string_view name,
span<const type_id_t> types, size_t index) {
if (index >= types.size()) {
emplace_error(sec::runtime_error, "index >= types.size()");
return false;
......@@ -217,7 +214,7 @@ bool json_writer::begin_key_value_pair() {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -249,7 +246,14 @@ bool json_writer::begin_sequence(size_t) {
bool json_writer::end_sequence() {
if (pop_if(type::array)) {
--indentation_level_;
close_nested('[', ']');
// Check whether the array was empty and compress the output in that case.
if (last_non_ws_char(buf_) == '[') {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add(']');
return true;
} else {
return false;
......@@ -279,7 +283,14 @@ bool json_writer::begin_associative_array(size_t) {
bool json_writer::end_associative_array() {
if (pop_if(type::object)) {
--indentation_level_;
close_nested('{', '}');
// Check whether the array was empty and compress the output in that case.
if (last_non_ws_char(buf_) == '{') {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add('}');
if (!stack_.empty())
stack_.back().filled = true;
return true;
......@@ -470,12 +481,12 @@ bool json_writer::pop_if(type t) {
return true;
} else {
std::string str = "pop_if failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -490,13 +501,13 @@ bool json_writer::pop_if_next(type t) {
return true;
} else {
std::string str = "pop_if_next failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.size() < 2) {
str += ", found a stack of size ";
detail::print(str, stack_.size());
} else {
str += ", found ";
str += json_type_name(stack_[stack_.size() - 2].t);
str += as_json_type_name(stack_[stack_.size() - 2].t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -517,9 +528,9 @@ bool json_writer::morph(type t, type& prev) {
return true;
} else {
std::string str = "cannot convert ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
str += " to ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, std::move(str));
return false;
}
......@@ -536,7 +547,7 @@ void json_writer::unsafe_morph(type t) {
void json_writer::fail(type t) {
std::string str = "failed to write a ";
str += json_type_name(t);
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
......@@ -570,16 +581,10 @@ void json_writer::sep() {
}
}
void json_writer::close_nested(char open, char close) {
auto not_ws = [](char c) { return !std::isspace(c); };
auto i = std::find_if(buf_.rbegin(), buf_.rend(), not_ws);
if (*i == open) {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add(close);
// -- free functions -----------------------------------------------------------
std::string_view as_json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
} // namespace caf
......@@ -85,6 +85,10 @@ void stringify(std::string& str, size_t, int64_t 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) {
str += std::to_string(val);
}
......@@ -106,6 +110,12 @@ void stringify(std::string& str, size_t, detail::json::null_t) {
str += "null";
}
void stringify(std::string& str, size_t, detail::json::undefined_t) {
// The parser never emits undefined objects, but we still need to provide an
// overload for this type.
str += "null";
}
void stringify(std::string& str, size_t indent, const detail::json::array& xs) {
if (xs.empty()) {
str += "[]";
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_array
#include "caf/json_array.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_array& arr) {
std::string result;
arr.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto arr = json_array{};
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from empty array") {
auto arr = json_value::parse("[]")->to_array();
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from non-empty array") {
auto arr = json_value::parse(R"_([1, "two", 3.0])_")->to_array();
CHECK(!arr.empty());
CHECK(!arr.is_empty());
CHECK(arr.begin() != arr.end());
REQUIRE_EQ(arr.size(), 3u);
CHECK_EQ((*arr.begin()).to_integer(), 1);
std::vector<json_value> vals;
for (const auto& val : arr) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 3u);
CHECK_EQ(vals[0].to_integer(), 1);
CHECK_EQ(vals[1].to_string(), "two");
CHECK_EQ(vals[2].to_double(), 3.0);
CHECK_EQ(to_string(arr), R"_([1, "two", 3])_");
CHECK_EQ(printed(arr), "[\n 1,\n \"two\",\n 3\n]");
CHECK_EQ(deep_copy(arr), arr);
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_builder
#include "caf/json_builder.hpp"
#include "core-test.hpp"
#include "caf/json_value.hpp"
#include <string_view>
using namespace caf;
using namespace std::literals;
namespace {
struct fixture {
fixture() {
builder.skip_object_type_annotation(true);
}
std::string printed(const json_value& val, size_t indentation_factor = 0) {
std::string result;
val.print_to(result, indentation_factor);
return result;
}
json_builder builder;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
TEST_CASE("empty JSON value") {
auto val = builder.seal();
CHECK(val.is_null());
}
TEST_CASE("integer") {
CHECK(builder.value(int32_t{42}));
auto val = builder.seal();
CHECK(val.is_integer());
CHECK_EQ(val.to_integer(), 42);
}
TEST_CASE("floating point") {
CHECK(builder.value(4.2));
auto val = builder.seal();
CHECK(val.is_double());
CHECK_EQ(val.to_double(), 4.2);
}
TEST_CASE("boolean") {
CHECK(builder.value(true));
auto val = builder.seal();
CHECK(val.is_bool());
CHECK_EQ(val.to_bool(), true);
}
TEST_CASE("string") {
CHECK(builder.value("Hello, world!"sv));
auto val = builder.seal();
CHECK(val.is_string());
CHECK_EQ(val.to_string(), "Hello, world!"sv);
}
TEST_CASE("array") {
auto xs = std::vector{1, 2, 3};
CHECK(builder.apply(xs));
auto val = builder.seal();
CHECK(val.is_array());
CHECK_EQ(printed(val), "[1, 2, 3]"sv);
}
TEST_CASE("flat object") {
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"a": 10, "b": 20})_");
}
TEST_CASE("flat object with type annotation") {
builder.skip_object_type_annotation(false);
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"@type": "my_request", "a": 10, "b": 20})_");
}
namespace {
constexpr string_view rect_str = R"_({
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object") {
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), rect_str);
}
namespace {
constexpr string_view annotated_rect_str = R"_({
"@type": "rectangle",
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object with type annotation") {
builder.skip_object_type_annotation(false);
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), annotated_rect_str);
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_object
#include "caf/json_object.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_object& obj) {
std::string result;
obj.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto obj = json_object{};
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from empty object") {
auto obj = json_value::parse("{}")->to_object();
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from non-empty object") {
auto obj = json_value::parse(R"_({"a": "one", "b": 2})_")->to_object();
CHECK(!obj.empty());
CHECK(!obj.is_empty());
CHECK(obj.begin() != obj.end());
REQUIRE_EQ(obj.size(), 2u);
CHECK_EQ(obj.begin().key(), "a");
CHECK_EQ(obj.begin().value().to_string(), "one");
CHECK_EQ(obj.value("a").to_string(), "one");
CHECK_EQ(obj.value("b").to_integer(), 2);
CHECK(obj.value("c").is_undefined());
std::vector<std::pair<string_view, json_value>> vals;
for (const auto& val : obj) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 2u);
CHECK_EQ(vals[0].first, "a");
CHECK_EQ(vals[0].second.to_string(), "one");
CHECK_EQ(vals[1].first, "b");
CHECK_EQ(vals[1].second.to_integer(), 2);
CHECK_EQ(to_string(obj), R"_({"a": "one", "b": 2})_");
CHECK_EQ(printed(obj), "{\n \"a\": \"one\",\n \"b\": 2\n}");
CHECK_EQ(deep_copy(obj), obj);
}
......@@ -113,7 +113,7 @@ fixture::fixture() {
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_reader_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(json baselines) {
size_t baseline_index = 0;
......@@ -179,4 +179,4 @@ SCENARIO("mappers enable custom type names in JSON input") {
}
}
CAF_TEST_FIXTURE_SCOPE_END()
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_value
#include "caf/json_value.hpp"
#include "core-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_value& val) {
std::string result;
val.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto val = json_value{};
CHECK(val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.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), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from undefined") {
auto val = json_value::undefined();
CHECK(!val.is_null());
CHECK(val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.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), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
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"));
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(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.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), "42");
CHECK_EQ(printed(val), "42");
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") {
auto val = unbox(json_value::parse("42.0"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 42);
CHECK_EQ(val.to_unsigned(), 42u);
CHECK_EQ(val.to_double(), 42.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), "42");
CHECK_EQ(printed(val), "42");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from bool") {
auto val = unbox(json_value::parse("true"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), true);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "true");
CHECK_EQ(printed(val), "true");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from string") {
auto val = unbox(json_value::parse(R"_("Hello, world!")_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), "Hello, world!"sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), R"_("Hello, world!")_");
CHECK_EQ(printed(val), R"_("Hello, world!")_");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty array") {
auto val = unbox(json_value::parse("[]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 0u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[]");
CHECK_EQ(printed(val), "[]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 1") {
auto val = unbox(json_value::parse("[1]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 1u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1]");
CHECK_EQ(printed(val), "[\n 1\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 3") {
auto val = unbox(json_value::parse("[1, 2, 3]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 3u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1, 2, 3]");
CHECK_EQ(printed(val), "[\n 1,\n 2,\n 3\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty object") {
auto val = unbox(json_value::parse("{}"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.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), "{}");
CHECK_EQ(printed(val), "{}");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from non-empty object") {
auto val = unbox(json_value::parse(R"_({"foo": "bar"})_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
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(), 0u);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 1u);
CHECK_EQ(to_string(val), R"_({"foo": "bar"})_");
CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}");
CHECK_EQ(deep_copy(val), val);
}
......@@ -38,7 +38,7 @@ struct fixture {
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_writer_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("the JSON writer converts builtin types to strings") {
GIVEN("an integer") {
......@@ -314,4 +314,33 @@ SCENARIO("the JSON writer annotates variant fields") {
}
}
CAF_TEST_FIXTURE_SCOPE_END()
SCENARIO("the JSON compresses empty lists and objects") {
GIVEN("a map with an empty list value") {
std::map<std::string, std::vector<int>> obj;
obj["xs"] = std::vector<int>{};
obj["ys"] = std::vector<int>{1, 2, 3};
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON contains a compressed representation of the empty list") {
std::string out = R"({
"xs": [],
"ys": [
1,
2,
3
]
})";
CHECK_EQ(to_json_string(obj, 2, true, true), out);
}
}
}
GIVEN("an empty map") {
std::map<std::string, std::vector<int>> obj;
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON contains a compressed representation of the empty map") {
CHECK_EQ(to_json_string(obj, 2, true, true), "{}"s);
}
}
}
}
END_FIXTURE_SCOPE()
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