TeaSpeakLibrary/src/query/command2.h

713 lines
21 KiB
C++

#pragma once
#include <any>
#include <memory>
#include <vector>
#include <map>
#include <cassert>
#include <string_view>
#include <deque>
#include <tuple>
#include "Error.h"
#include "command_exception.h"
#include "converters/converter.h"
#ifdef WIN32
#define __attribute__used
#else
#define __attribute__used __attribute__((used))
#endif
namespace ts {
/* data impl stuff */
namespace impl {
struct command_data;
struct command_value {
bool casted = false; /* true if value isn't a std::string */
std::any value;
std::string(*to_string)(const std::any&);
};
struct command_bulk {
command_data* handle;
std::map<std::string, std::shared_ptr<command_value>> values;
};
struct command_data {
std::string command;
bool editiable;
std::deque<std::shared_ptr<command_bulk>> bulks;
std::deque<std::string> triggers;
};
}
/* Container stuff */
class command_bulk;
class command_entry;
class command {
public:
struct format {
enum value {
QUERY,
BRACE_ESCAPED_QUERY,
JSON
};
};
static command parse(const std::string_view& /* command data */, bool /* expect type */ = true, bool /* drop non UTF-8 characters */ = false);
explicit command(const std::string& /* command */ = "", bool /* editable */ = true);
std::string identifier() const;
void set_identifier(const std::string& /* command */);
command_bulk bulk(size_t /* bulk index */);
const command_bulk bulk(size_t /* bulk index */) const;
size_t bulk_count() const;
inline command_bulk operator[](size_t /* index */);
inline const command_bulk operator[](size_t /* index */) const;
command_entry value(const std::string& /* key */);
const command_entry value(const std::string& /* key */) const;
bool has_value(const std::string& /* key */) const;
command_entry operator[](const std::string& /* key */);
const command_entry operator[](const std::string& /* key */) const;
bool has_trigger(const std::string& /* key */) const;
void set_trigger(const std::string& /* key */, bool /* value */ = true);
std::string build(format::value /* format */ = format::QUERY);
/* TODO add a json object build method */
private:
std::shared_ptr<impl::command_data> handle;
};
class command_bulk {
friend class command;
public:
bool has(const std::string& /* key */) const;
command_entry value(const std::string& /* key */);
command_entry const value(const std::string& /* key */) const;
inline command_entry operator[](const std::string& /* key */);
inline const command_entry operator[](const std::string& /* key */) const;
private:
command_bulk(size_t index, std::shared_ptr<impl::command_bulk> handle) : bulk_index(index), handle(std::move(handle)) {}
size_t bulk_index;
std::shared_ptr<impl::command_bulk> handle;
};
class command_entry {
public:
static command_entry empty;
command_entry() : handle(std::make_shared<impl::command_value>()) {}
command_entry(const command_entry& ref) : handle(ref.handle) {}
command_entry(command_entry&& ref) : handle(std::move(ref.handle)) {}
command_entry&operator=(const command_entry& other) {
this->handle = other.handle;
return *this;
}
inline bool is_empty() const { return !this->handle->value.has_value(); }
command_entry& reset() {
this->handle->value.reset();
return *this;
}
const std::string string() const {
if(this->is_empty()) return "";
if(!this->handle->casted || this->handle->value.type() == typeid(std::string)) //No cast needed
return std::any_cast<std::string>(this->handle->value);
if(!this->handle->to_string) throw command_cannot_uncast_exception();
return this->handle->to_string(this->handle->value);
}
inline const std::string value() const { return (std::string) this->string(); }
inline operator std::string() const {
return this->string();
}
template <typename T, std::enable_if_t<!std::is_same<T, std::string>::value && !std::is_same<T, std::string>::value, int> = 0>
T as() {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::from_string, "Target type dosn't support parsing");
if(this->is_empty()) return T();
if(this->handle->casted) {
if(this->handle->value.type() == typeid(T))
return std::any_cast<T>(this->handle->value);
else
throw command_casted_exception();
} else {
const auto& ref = std::any_cast<const std::string&>(this->handle->value);
this->handle->value = converter<T>::from_string(ref);
this->handle->to_string = converter<T>::to_string;
this->handle->casted = true;
}
return std::any_cast<T>(this->handle->value);
}
template <typename T, std::enable_if_t<std::is_same<T, std::string>::value, int> = 0>
T as() {
return this->string();
}
template <typename T>
inline operator T() {
return this->as<T>();
}
command_entry& melt() {
if(this->handle->casted) {
this->handle->value = this->handle->to_string(this->handle->value);
this->handle->casted = false;
this->handle->to_string = nullptr;
}
return *this;
}
template <typename T, typename std::enable_if<!std::is_same<T, std::string>::value && !std::is_same<T, const char*>::value && !std::is_same<T, const char (&)[]>::value, int>::type = 0>
void set(const T& value) {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::to_string, "Target type dosn't support encode");
this->handle->casted = true;
this->handle->value = std::move(value);
this->handle->to_string = converter<T>::to_string;
}
template <typename T, typename std::enable_if<std::is_same<T, std::string>::value, int>::type = 0>
void set(const T& value) {
this->handle->value = value;
this->handle->casted = false;
this->handle->to_string = nullptr;
}
template <int N>
void set(const char (&string)[N]) {
this->set(std::string(string, N - 1));
}
template <typename T>
command_entry&operator=(const T& value) {
this->set(value);
return *this;
}
explicit command_entry(std::shared_ptr<impl::command_value> handle) : handle(std::move(handle)) {}
private:
std::shared_ptr<impl::command_value> handle;
};
namespace descriptor {
namespace tliterals {
template <char... chars>
using tstring = std::integer_sequence<char, chars...>;
#ifndef WIN32
template <typename T, T... chars>
constexpr tstring<chars...> operator""_tstr() { return { }; }
#endif
template <typename>
struct tliteral;
template <char... elements>
struct tliteral<tstring<elements...>> {
static constexpr char string[sizeof...(elements) + 1] = { elements..., '\0' };
};
}
namespace impl {
namespace templates {
template <bool...>
struct _or_ {
constexpr static bool value = false;
};
template <bool T, bool... Args>
struct _or_<T, Args...> {
constexpr static bool value = T || _or_<Args...>::value;
};
template <typename... >
struct index;
template <typename... >
struct tuple_index;
template <typename T, typename... R>
struct index<T, T, R...> : std::integral_constant<size_t, 0>
{ };
template <typename T, typename F, typename... R>
struct index<T, F, R...> : std::integral_constant<size_t, 1 + index<T, R...>::value>
{ };
template <typename T, typename... R>
struct tuple_index<T, std::tuple<R...>> : std::integral_constant<size_t, index<T, R...>::value>
{ };
template <typename T>
struct remove_cr {
typedef T type;
};
template <typename T>
struct remove_cr<const T&> {
typedef T type;
};
}
struct base;
template <class key_t, typename value_type_t, class options, class... extends>
struct field;
struct field_data;
struct field_base;
struct optional_extend;
struct bulk_extend;
template <typename...>
struct command_parser {
constexpr static bool supported = false;
};
inline void parse_field(const std::shared_ptr<field_data>& description, field_base* field, command& cmd);
struct option_data {
bool bulked;
bool optional;
};
template <bool bulked_t, bool optional_t>
struct options {
using object_data_t = option_data;
static constexpr auto is_bulked = bulked_t;
static constexpr auto is_optional = optional_t;
protected:
inline static object_data_t options_object() {
return {
is_bulked,
is_optional
};
}
};
using default_options = options<false, false>;
struct base { };
struct base_data {
int type; /* 1 = field | 2 = switch | 3 = command handle */
option_data options;
};
struct field_data : public base_data {
const char* key;
const std::type_info& field_type;
void* from_string;
void* to_string;
};
struct field_base {
typedef std::vector<command_entry>&(*ref_values_fn)();
static inline std::vector<command_entry>& ref_values(const void* _this) {
void** vtable = *(void***) _this;
return ((ref_values_fn) vtable[0])();
}
};
template <class key_t, typename value_type_t, class options, class... extends>
struct field : public base, public options, public extends... {
friend struct command_parser<field<key_t, value_type_t, options, extends...>>;
static_assert(converter<value_type_t>::supported, "Target type isn't supported!");
static_assert(!converter<value_type_t>::supported || converter<value_type_t>::from_string, "Target type dosn't support parsing");
protected:
using object_t = field_data;
using value_type = value_type_t;
static constexpr auto key = key_t::string;
static constexpr auto from_string = converter<value_type_t>::from_string;
public:
template <bool flag = true /*, std::enable_if_t<!templates::_or_<std::is_same<extends, optional_extend>::value...>::value, int> = 0 */>
using as_optional = field<key_t, value_type_t, impl::options<options::is_bulked, flag>, optional_extend, extends...>;
template <bool flag = true /*, std::enable_if_t<!templates::_or_<std::is_same<extends, bulk_extend>::value...>::value, int> = 0 */>
using as_bulked = field<key_t, value_type_t, impl::options<flag, options::is_optional>, bulk_extend, extends...>;
using optional = as_optional<true>;
using bulked = as_bulked<true>;
inline static std::shared_ptr<object_t> describe() {
return std::make_shared<object_t>(
object_t {
1,
options::options_object(),
key,
typeid(value_type_t),
(void*) converter<value_type_t>::from_string,
(void*) converter<value_type_t>::to_string
}
);
}
inline value_type_t value() const {
command_entry& value = this->get_command_entry();
return value.as<value_type_t>();
}
inline command_entry& get_command_entry() const {
if(this->values.empty())
throw command_value_missing_exception{0, key};
const auto& front = this->values.front();
return *(command_entry*) &front;
}
template <typename T>
inline T as() const {
command_entry& value = this->get_command_entry();
return value.as<T>();
}
template <typename T>
inline operator T() const {
return this->as<T>();
}
protected:
/* ATTENTION: This must be placed at index 0 within the VTable */
virtual __attribute__used std::vector<command_entry>& _v_ref_values() {
return this->values;
}
std::vector<command_entry> values;
};
struct optional_extend {
public:
inline bool has_value() const {
auto& values = field_base::ref_values(this);
return !values.empty() && !values[0].is_empty();
}
template <typename T>
inline T get_or(T&& value = T{}) const {
if(this->has_value())
return field_base::ref_values(this).front().as<T>();
return value;
}
};
struct bulk_extend {
public:
inline bool has_index(size_t index) const {
return !this->at(index).is_empty();
}
inline size_t length() const { return field_base::ref_values(this).size(); }
inline command_entry at(size_t index) const {
auto& values = field_base::ref_values(this);
if(index > values.size())
throw command_bulk_exceed_index_exception();
return values[index];
}
inline command_entry operator[](size_t index) const {
return this->at(index);
}
};
struct trigger_data : public base_data {
const char* key;
};
struct trigger_base : public base {
typedef bool&(*ref_flag_fn)();
static inline bool& ref_flag(void* _this) {
void** vtable = *(void***) _this;
return ((ref_flag_fn) vtable[0])();
}
};
template <class key_t, class options>
struct trigger : public trigger_base, public options {
protected:
static constexpr auto key = key_t::string;
public:
using object_t = trigger_data;
inline static std::shared_ptr<object_t> describe() {
return std::make_shared<object_t>(
object_t {
2,
options::options_object(),
key
}
);
}
inline bool is_set() const { return this->flag_set; }
operator bool() const { return this->flag_set; }
private:
/* ATTENTION: This must be placed at index 0 within the VTable */
virtual __attribute__used bool& _v_ref_values() {
return this->flag_set;
}
bool flag_set;
};
struct command_handle_data : public base_data { };
struct command_handle_base : public base {
typedef command&(*ref_flag_fn)();
static inline command& ref_command(void* _this) {
void** vtable = *(void***) _this;
return ((ref_flag_fn) vtable[0])();
}
};
template <class options>
struct command_handle : public command_handle_base, public options {
public:
using object_t = command_handle_data;
inline static std::shared_ptr<object_t> describe() {
return std::make_shared<object_t>(
command_handle_data {
3,
options::options_object()
}
);
}
inline command get_command() { return this->_command; }
operator command() { return this->_command; }
inline command* operator->() const noexcept {
return (command*) &_command;
}
/*
template<class Arg>
auto operator[](Arg &&arg) -> decltype(get_command()[std::forward<Arg>(arg)]) {
return _command[std::forward<Arg>(arg)];
}
*/
template<class Arg>
decltype(std::declval<command>()[std::forward<Arg>(Arg{})]) operator[](Arg &&arg) {
return _command[std::forward<Arg>(arg)];
}
private:
/* ATTENTION: This must be placed at index 0 within the VTable */
virtual __attribute__used class command& _v_ref_values() {
return this->_command;
}
command _command;
};
typedef std::vector<std::shared_ptr<impl::base_data>> function_descriptors_t;
template <typename...>
struct describe {
inline static void apply(function_descriptors_t& result) {}
};
template <typename T, typename... Args>
struct describe<T, Args...> {
inline static void apply(function_descriptors_t& result) {
result.push_back(T::describe());
describe<Args...>::apply(result);
}
};
inline void parse_field(const std::shared_ptr<field_data>& description, void* field, command& cmd) {
//if(!description->options.optional && !cmd.has_value(description->key))
// throw command_value_missing_exception();
auto& values = field_base::ref_values(field);
values.clear();
if(description->options.bulked) {
values.resize(cmd.bulk_count());
for(size_t bulk_index = 0; bulk_index < cmd.bulk_count(); bulk_index++) {
if(!cmd[bulk_index].has(description->key)) {
if(!description->options.optional)
throw command_value_missing_exception(bulk_index, description->key);
else
values[bulk_index] = command_entry::empty;
} else {
values[bulk_index] = cmd[bulk_index][description->key];
}
}
} else {
if(!cmd.has_value(description->key)) {
if(!description->options.optional)
throw command_value_missing_exception(0, description->key);
else
values.push_back(command_entry::empty);
} else
values.push_back(cmd[description->key]);
}
}
template <class key_t, class options, class... extends>
struct command_parser<field<key_t, options, extends...>> {
constexpr static bool supported = true;
typedef field<key_t, options, extends...> field_t;
using descriptor_t = std::shared_ptr<typename field_t::object_t>;
inline static descriptor_t describe() {
return field_t::describe();
}
inline static field_t apply(descriptor_t& descriptor, command& cmd) {
assert(descriptor->type == 1);
field_t result{};
parse_field(descriptor, (void*) &result, cmd);
return result;
}
};
template <typename key_t, class options>
struct command_parser<trigger<key_t, options>> {
constexpr static bool supported = true;
typedef trigger<key_t, options> trigger_t;
using descriptor_t = std::shared_ptr<typename trigger_t::object_t>;
inline static descriptor_t describe() {
return trigger_t::describe();
}
inline static trigger_t apply(descriptor_t& descriptor, command& cmd) {
assert(descriptor->type == 2);
trigger_t result{};
trigger_base::ref_flag(&result) = cmd.has_trigger(descriptor->key);
return result;
}
};
template <class options>
struct command_parser<command_handle<options>> {
constexpr static bool supported = true;
typedef command_handle<options> command_handle_t;
using descriptor_t = std::shared_ptr<typename command_handle_t::object_t>;
inline static descriptor_t describe() {
return command_handle_t::describe();
}
inline static command_handle_t apply(descriptor_t& descriptor, command& cmd) {
assert(descriptor->type == 3);
command_handle_t result{};
command_handle_base::ref_command(&result) = cmd;
return result;
}
};
template <typename C>
struct command_parser<C> {
constexpr static bool supported = std::is_same<typename std::remove_reference<typename std::remove_cv<C>::type>::type, command>::value;
static_assert(supported, "This type isn't supported!");
using descriptor_t = std::shared_ptr<void>;
inline static descriptor_t describe() {
return nullptr;
}
inline static command& apply(descriptor_t& descriptor, command& cmd) {
return cmd;
}
};
}
/* direct use of command is possible as well! */
using command_handle = impl::command_handle<impl::default_options>;
template <class key_t, typename value_type_t>
using field = impl::field<key_t, value_type_t, impl::default_options>;
template <class key_t>
using trigger = impl::trigger<key_t, impl::default_options>;
template <typename... args_t>
inline impl::function_descriptors_t describe_function() {
impl::function_descriptors_t result;
impl::describe<args_t...>::apply(result);
return result;
}
struct invocable_function {
void operator()(command& command) {
this->invoke(command);
}
virtual void invoke(command& command) = 0;
};
template <typename... args_t>
struct typed_invocable_function : public invocable_function {
using args_tuple_t = std::tuple<args_t...>;
template <typename arg_t>
using command_parser_t = impl::command_parser<typename impl::templates::remove_cr<arg_t>::type>;
using descriptors_t = std::tuple<typename command_parser_t<args_t>::descriptor_t...>;
descriptors_t descriptors;
void(*function)(args_t...);
void invoke(command& command) override {
function(command_parser_t<args_t>::apply(std::get<impl::templates::tuple_index<args_t, args_tuple_t>::value>(descriptors), command)...);
}
};
template <typename... args_t, typename typed_function = typed_invocable_function<args_t...>>
std::shared_ptr<invocable_function> parse_function(void(*function)(args_t...)) {
auto result = std::make_shared<typed_function>();
result->function = function;
result->descriptors = {impl::command_parser<typename impl::templates::remove_cr<args_t>::type>::describe()...};
return result;
}
template <typename... args_t>
void invoke_function(void(*function)(args_t...), command& command) {
auto descriptor = parse_function(function);
(*descriptor)(command);
}
/* converts a literal into a template literal */
#define _tlit(literal) ::ts::descriptor::tliterals::tliteral<decltype(literal ##_tstr)>
#define tl(lit) _tlit(lit)
}
//using desc = descriptor::base<descriptor::impl::default_options>;
}
#include "command_internal.h"