591 lines
17 KiB
C++
591 lines
17 KiB
C++
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <iosfwd>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <functional>
|
|
#include <unordered_map>
|
|
|
|
namespace util {
|
|
template<class T> class Buffer;
|
|
}
|
|
|
|
namespace dv {
|
|
using integer_t = int64_t;
|
|
using number_t = double;
|
|
using boolean_t = bool;
|
|
using byte_t = unsigned char;
|
|
using key_t = std::string;
|
|
|
|
enum class value_type : uint8_t {
|
|
none = 0,
|
|
number,
|
|
boolean,
|
|
integer,
|
|
object,
|
|
list,
|
|
bytes,
|
|
string
|
|
};
|
|
|
|
inline const std::string& type_name(value_type type) {
|
|
static std::string type_names[] = {
|
|
"none",
|
|
"number",
|
|
"boolean",
|
|
"integer",
|
|
"object",
|
|
"list",
|
|
"bytes",
|
|
"string"
|
|
};
|
|
return type_names[static_cast<int>(type)];
|
|
}
|
|
|
|
class value;
|
|
|
|
using list_t = std::vector<value>;
|
|
using map_t = std::unordered_map<key_t, value>;
|
|
using pair = std::pair<const key_t, value>;
|
|
|
|
using reference = value&;
|
|
using const_reference = const value&;
|
|
|
|
namespace objects {
|
|
using Object = std::unordered_map<key_t, value>;
|
|
using List = std::vector<value>;
|
|
using Bytes = util::Buffer<byte_t>;
|
|
}
|
|
|
|
/// @brief nullable value reference returned by value.at(...)
|
|
struct optionalvalue {
|
|
value* ptr;
|
|
|
|
optionalvalue(value* ptr) : ptr(ptr) {}
|
|
|
|
inline operator bool() const {
|
|
return ptr != nullptr;
|
|
}
|
|
|
|
inline value& operator*() {
|
|
return *ptr;
|
|
}
|
|
|
|
inline const value& operator*() const {
|
|
return *ptr;
|
|
}
|
|
|
|
bool get(std::string& dst) const;
|
|
bool get(bool& dst) const;
|
|
bool get(char& dst) const;
|
|
bool get(short& dst) const;
|
|
bool get(int& dst) const;
|
|
bool get(long& dst) const;
|
|
bool get(long long& dst) const;
|
|
bool get(unsigned char& dst) const;
|
|
bool get(unsigned short& dst) const;
|
|
bool get(unsigned int& dst) const;
|
|
bool get(unsigned long& dst) const;
|
|
bool get(unsigned long long& dst) const;
|
|
bool get(float& dst) const;
|
|
bool get(double& dst) const;
|
|
};
|
|
|
|
inline void throw_type_error(value_type got, value_type expected) {
|
|
// place breakpoint here to find cause
|
|
throw std::runtime_error(
|
|
"type error: expected " + type_name(expected) + ", got " +
|
|
type_name(got)
|
|
);
|
|
}
|
|
|
|
inline void check_type(value_type got, value_type expected) {
|
|
if (got != expected) {
|
|
throw_type_error(got, expected);
|
|
}
|
|
}
|
|
|
|
class value {
|
|
value_type type = value_type::none;
|
|
union value_u {
|
|
integer_t integer;
|
|
number_t number;
|
|
boolean_t boolean;
|
|
std::unique_ptr<std::string> string;
|
|
std::shared_ptr<objects::Object> object;
|
|
std::shared_ptr<objects::List> list;
|
|
std::shared_ptr<objects::Bytes> bytes;
|
|
value_u() {}
|
|
~value_u() {}
|
|
} val;
|
|
|
|
inline value& setBoolean(boolean_t v) {
|
|
this->~value();
|
|
type = value_type::boolean;
|
|
val.boolean = v;
|
|
return *this;
|
|
}
|
|
inline value& setInteger(integer_t v) {
|
|
this->~value();
|
|
type = value_type::integer;
|
|
val.integer = v;
|
|
return *this;
|
|
}
|
|
inline value& setNumber(number_t v) {
|
|
this->~value();
|
|
type = value_type::number;
|
|
val.number = v;
|
|
return *this;
|
|
}
|
|
inline value& setNone() {
|
|
this->~value();
|
|
type = value_type::none;
|
|
return *this;
|
|
}
|
|
inline value& setString(std::string v) {
|
|
this->~value();
|
|
new(&val.string)std::unique_ptr<std::string>(
|
|
std::make_unique<std::string>(std::move(v)));
|
|
type = value_type::string;
|
|
return *this;
|
|
}
|
|
inline value& setString(std::unique_ptr<std::string> v) {
|
|
this->~value();
|
|
new(&val.string)std::unique_ptr<std::string>(std::move(v));
|
|
type = value_type::string;
|
|
return *this;
|
|
}
|
|
inline value& setList(std::shared_ptr<objects::List> ptr) {
|
|
this->~value();
|
|
new(&val.list)std::shared_ptr<objects::List>(std::move(ptr));
|
|
type = value_type::list;
|
|
return *this;
|
|
}
|
|
inline value& setObject(std::shared_ptr<objects::Object> ptr) {
|
|
this->~value();
|
|
new(&val.object)std::shared_ptr<objects::Object>(std::move(ptr));
|
|
type = value_type::object;
|
|
return *this;
|
|
}
|
|
inline value& setBytes(std::shared_ptr<objects::Bytes> ptr) {
|
|
this->~value();
|
|
new(&val.bytes)std::shared_ptr<objects::Bytes>(std::move(ptr));
|
|
type = value_type::bytes;
|
|
return *this;
|
|
}
|
|
public:
|
|
value() : type(value_type::none) {}
|
|
value(value_type type);
|
|
|
|
template<class T>
|
|
value(T v) {
|
|
this->operator=(v);
|
|
}
|
|
|
|
value(const value& v) noexcept : type(value_type::none) {
|
|
this->operator=(v);
|
|
}
|
|
|
|
value(value&& v) noexcept {
|
|
this->operator=(std::move(v));
|
|
}
|
|
|
|
~value() {
|
|
switch (type) {
|
|
case value_type::object:
|
|
val.object.reset();
|
|
break;
|
|
case value_type::list:
|
|
val.list.reset();
|
|
break;
|
|
case value_type::bytes:
|
|
val.bytes.reset();
|
|
break;
|
|
case value_type::string:
|
|
val.string.reset();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline value& operator=(std::nullptr_t) {
|
|
return setNone();
|
|
}
|
|
|
|
template<typename T>
|
|
inline std::enable_if_t<std::is_integral<T>() && !std::is_same<T, bool>(), value&>
|
|
operator=(T v) {
|
|
return setInteger(v);
|
|
}
|
|
|
|
inline value& operator=(float v) {
|
|
return setNumber(v);
|
|
}
|
|
inline value& operator=(double v) {
|
|
return setNumber(v);
|
|
}
|
|
inline value& operator=(bool v) {
|
|
return setBoolean(v);
|
|
}
|
|
inline value& operator=(std::string_view v) {
|
|
return setString(std::string(v));
|
|
}
|
|
inline value& operator=(std::string v) {
|
|
return setString(std::move(v));
|
|
}
|
|
inline value& operator=(const char* v) {
|
|
return setString(v);
|
|
}
|
|
inline value& operator=(std::shared_ptr<objects::List> ptr) {
|
|
return setList(std::move(ptr));
|
|
}
|
|
inline value& operator=(std::shared_ptr<objects::Object> ptr) {
|
|
return setObject(std::move(ptr));
|
|
}
|
|
inline value& operator=(std::shared_ptr<objects::Bytes> ptr) {
|
|
return setBytes(std::move(ptr));
|
|
}
|
|
value& operator=(const objects::Bytes& bytes);
|
|
|
|
inline value& operator=(const value& v) {
|
|
switch (v.type) {
|
|
case value_type::object:
|
|
setObject(v.val.object);
|
|
break;
|
|
case value_type::list:
|
|
setList(v.val.list);
|
|
break;
|
|
case value_type::bytes:
|
|
setBytes(v.val.bytes);
|
|
break;
|
|
case value_type::string:
|
|
setString(*v.val.string);
|
|
break;
|
|
case value_type::boolean:
|
|
setBoolean(v.val.boolean);
|
|
break;
|
|
case value_type::integer:
|
|
setInteger(v.val.integer);
|
|
break;
|
|
case value_type::number:
|
|
setNumber(v.val.number);
|
|
break;
|
|
case value_type::none:
|
|
setNone();
|
|
break;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
inline value& operator=(value&& v) noexcept {
|
|
if (type < value_type::object) {
|
|
type = v.type;
|
|
switch (v.type) {
|
|
case value_type::none:
|
|
break;
|
|
case value_type::integer:
|
|
val.integer = v.val.integer;
|
|
break;
|
|
case value_type::number:
|
|
val.number = v.val.number;
|
|
break;
|
|
case value_type::boolean:
|
|
val.boolean = v.val.boolean;
|
|
break;
|
|
case value_type::string:
|
|
new(&val.string)std::unique_ptr<std::string>(
|
|
std::move(v.val.string));
|
|
break;
|
|
case value_type::object:
|
|
new(&val.object)std::shared_ptr<objects::Object>(
|
|
std::move(v.val.object));
|
|
break;
|
|
case value_type::list:
|
|
new(&val.list)std::shared_ptr<objects::List>(
|
|
std::move(v.val.list));
|
|
break;
|
|
case value_type::bytes:
|
|
new(&val.list)std::shared_ptr<objects::Bytes>(
|
|
std::move(v.val.bytes));
|
|
break;
|
|
}
|
|
} else {
|
|
switch (v.type) {
|
|
case value_type::object:
|
|
setObject(std::move(v.val.object));
|
|
break;
|
|
case value_type::list:
|
|
setList(std::move(v.val.list));
|
|
break;
|
|
case value_type::bytes:
|
|
setBytes(std::move(v.val.bytes));
|
|
break;
|
|
case value_type::string:
|
|
setString(std::move(v.val.string));
|
|
break;
|
|
case value_type::boolean:
|
|
setBoolean(v.val.boolean);
|
|
break;
|
|
case value_type::integer:
|
|
setInteger(v.val.integer);
|
|
break;
|
|
case value_type::number:
|
|
setNumber(v.val.number);
|
|
break;
|
|
case value_type::none:
|
|
setNone();
|
|
break;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void add(value v);
|
|
|
|
template<class T>
|
|
inline void add(T v) {
|
|
return add(value(v));
|
|
}
|
|
|
|
void erase(const key_t& key);
|
|
|
|
void erase(size_t index);
|
|
|
|
value& operator[](const key_t& key);
|
|
|
|
const value& operator[](const key_t& key) const;
|
|
|
|
value& operator[](size_t index);
|
|
|
|
const value& operator[](size_t index) const;
|
|
|
|
bool operator!=(std::nullptr_t) const noexcept {
|
|
return type != value_type::none;
|
|
}
|
|
|
|
bool operator==(std::nullptr_t) const noexcept {
|
|
return type == value_type::none;
|
|
}
|
|
|
|
value& object(const key_t& key);
|
|
|
|
value& list(const key_t& key);
|
|
|
|
value& object();
|
|
|
|
value& list();
|
|
|
|
list_t::iterator begin();
|
|
list_t::iterator end();
|
|
|
|
list_t::const_iterator begin() const;
|
|
list_t::const_iterator end() const;
|
|
|
|
const std::string& asString() const;
|
|
|
|
integer_t asInteger() const;
|
|
|
|
number_t asNumber() const;
|
|
|
|
boolean_t asBoolean() const;
|
|
|
|
objects::Bytes& asBytes();
|
|
|
|
const objects::Bytes& asBytes() const;
|
|
|
|
const objects::Object& asObject() const;
|
|
|
|
inline value_type getType() const {
|
|
return type;
|
|
}
|
|
|
|
std::string asString(std::string def) const {
|
|
if (type != value_type::string) {
|
|
return def;
|
|
}
|
|
return *val.string;
|
|
}
|
|
|
|
std::string asString(const char* s) const {
|
|
return asString(std::string(s));
|
|
}
|
|
|
|
integer_t asBoolean(boolean_t def) const {
|
|
switch (type) {
|
|
case value_type::boolean:
|
|
return val.boolean;
|
|
default:
|
|
return def;
|
|
}
|
|
}
|
|
|
|
integer_t asInteger(integer_t def) const {
|
|
switch (type) {
|
|
case value_type::integer:
|
|
return val.integer;
|
|
case value_type::number:
|
|
return static_cast<integer_t>(val.number);
|
|
default:
|
|
return def;
|
|
}
|
|
}
|
|
|
|
integer_t asNumber(integer_t def) const {
|
|
switch (type) {
|
|
case value_type::integer:
|
|
return static_cast<number_t>(val.integer);
|
|
case value_type::number:
|
|
return val.number;
|
|
default:
|
|
return def;
|
|
}
|
|
}
|
|
|
|
optionalvalue at(const key_t& k) const {
|
|
check_type(type, value_type::object);
|
|
const auto& found = val.object->find(k);
|
|
if (found == val.object->end()) {
|
|
return optionalvalue(nullptr);
|
|
}
|
|
return optionalvalue(&found->second);
|
|
}
|
|
|
|
optionalvalue at(size_t index) {
|
|
check_type(type, value_type::list);
|
|
return optionalvalue(&val.list->at(index));
|
|
}
|
|
|
|
const optionalvalue at(size_t index) const {
|
|
check_type(type, value_type::list);
|
|
return optionalvalue(&val.list->at(index));
|
|
}
|
|
|
|
bool has(const key_t& k) const;
|
|
|
|
size_t size() const;
|
|
|
|
size_t length() const {
|
|
return size();
|
|
}
|
|
inline bool empty() const {
|
|
return size() == 0;
|
|
}
|
|
|
|
inline bool isString() const {
|
|
return type == value_type::string;
|
|
}
|
|
inline bool isObject() const {
|
|
return type == value_type::object;
|
|
}
|
|
inline bool isList() const {
|
|
return type == value_type::list;
|
|
}
|
|
inline bool isInteger() const {
|
|
return type == value_type::integer;
|
|
}
|
|
inline bool isNumber() const {
|
|
return type == value_type::number;
|
|
}
|
|
};
|
|
|
|
inline value none = value();
|
|
|
|
inline bool is_numeric(const value& val) {
|
|
return val.isInteger() && val.isNumber();
|
|
}
|
|
|
|
using to_string_func = std::function<std::string(const value&)>;
|
|
}
|
|
|
|
namespace dv {
|
|
inline const std::string& type_name(const value& value) {
|
|
return type_name(value.getType());
|
|
}
|
|
|
|
inline value object() {
|
|
return std::make_shared<objects::Object>();
|
|
}
|
|
|
|
inline value list() {
|
|
return std::make_shared<objects::List>();
|
|
}
|
|
|
|
inline value list(std::initializer_list<value> values) {
|
|
return std::make_shared<objects::List>(values);
|
|
}
|
|
|
|
template<typename T> inline bool get_to_int(value* ptr, T& dst) {
|
|
if (ptr) {
|
|
dst = ptr->asInteger();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
template<typename T> inline bool get_to_num(value* ptr, T& dst) {
|
|
if (ptr) {
|
|
dst = ptr->asNumber();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
inline bool optionalvalue::get(std::string& dst) const {
|
|
if (ptr) {
|
|
dst = ptr->asString();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool optionalvalue::get(bool& dst) const {
|
|
if (ptr) {
|
|
dst = ptr->asBoolean();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool optionalvalue::get(char& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(short& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(int& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(long& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(long long& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(unsigned char& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(unsigned short& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(unsigned int& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(unsigned long& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(unsigned long long& dst) const {
|
|
return get_to_int(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(float& dst) const {
|
|
return get_to_num(ptr, dst);
|
|
}
|
|
inline bool optionalvalue::get(double& dst) const {
|
|
return get_to_num(ptr, dst);
|
|
}
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& stream, const dv::value& value);
|