713 lines
21 KiB
C++
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" |