#pragma once #include #include #include #include #include #include #include #include #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> values; }; struct command_data { std::string command; bool editiable; std::deque> bulks; std::deque 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 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 handle) : bulk_index(index), handle(std::move(handle)) {} size_t bulk_index; std::shared_ptr handle; }; class command_entry { public: static command_entry empty; command_entry() : handle(std::make_shared()) {} 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(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 ::value && !std::is_same::value, int> = 0> T as() { static_assert(converter::supported, "Target type isn't supported!"); static_assert(!converter::supported || converter::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(this->handle->value); else throw command_casted_exception(); } else { const auto& ref = std::any_cast(this->handle->value); this->handle->value = converter::from_string(ref); this->handle->to_string = converter::to_string; this->handle->casted = true; } return std::any_cast(this->handle->value); } template ::value, int> = 0> T as() { return this->string(); } template inline operator T() { return this->as(); } 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 ::value && !std::is_same::value && !std::is_same::value, int>::type = 0> void set(const T& value) { static_assert(converter::supported, "Target type isn't supported!"); static_assert(!converter::supported || converter::to_string, "Target type dosn't support encode"); this->handle->casted = true; this->handle->value = std::move(value); this->handle->to_string = converter::to_string; } template ::value, int>::type = 0> void set(const T& value) { this->handle->value = value; this->handle->casted = false; this->handle->to_string = nullptr; } template void set(const char (&string)[N]) { this->set(std::string(string, N - 1)); } template command_entry&operator=(const T& value) { this->set(value); return *this; } explicit command_entry(std::shared_ptr handle) : handle(std::move(handle)) {} private: std::shared_ptr handle; }; namespace descriptor { namespace tliterals { template using tstring = std::integer_sequence; #ifndef WIN32 template constexpr tstring operator""_tstr() { return { }; } #endif template struct tliteral; template struct tliteral> { static constexpr char string[sizeof...(elements) + 1] = { elements..., '\0' }; }; } namespace impl { namespace templates { template struct _or_ { constexpr static bool value = false; }; template struct _or_ { constexpr static bool value = T || _or_::value; }; template struct index; template struct tuple_index; template struct index : std::integral_constant { }; template struct index : std::integral_constant::value> { }; template struct tuple_index> : std::integral_constant::value> { }; template struct remove_cr { typedef T type; }; template struct remove_cr { typedef T type; }; } struct base; template struct field; struct field_data; struct field_base; struct optional_extend; struct bulk_extend; template struct command_parser { constexpr static bool supported = false; }; inline void parse_field(const std::shared_ptr& description, field_base* field, command& cmd); struct option_data { bool bulked; bool optional; }; template 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; 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&(*ref_values_fn)(); static inline std::vector& ref_values(const void* _this) { void** vtable = *(void***) _this; return ((ref_values_fn) vtable[0])(); } }; template struct field : public base, public options, public extends... { friend struct command_parser>; static_assert(converter::supported, "Target type isn't supported!"); static_assert(!converter::supported || converter::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::from_string; public: template ::value...>::value, int> = 0 */> using as_optional = field, optional_extend, extends...>; template ::value...>::value, int> = 0 */> using as_bulked = field, bulk_extend, extends...>; using optional = as_optional; using bulked = as_bulked; inline static std::shared_ptr describe() { return std::make_shared( object_t { 1, options::options_object(), key, typeid(value_type_t), (void*) converter::from_string, (void*) converter::to_string } ); } inline value_type_t value() const { command_entry& value = this->get_command_entry(); return value.as(); } 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 inline T as() const { command_entry& value = this->get_command_entry(); return value.as(); } template inline operator T() const { return this->as(); } protected: /* ATTENTION: This must be placed at index 0 within the VTable */ virtual __attribute__used std::vector& _v_ref_values() { return this->values; } std::vector 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 inline T get_or(T&& value = T{}) const { if(this->has_value()) return field_base::ref_values(this).front().as(); 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 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 describe() { return std::make_shared( 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 struct command_handle : public command_handle_base, public options { public: using object_t = command_handle_data; inline static std::shared_ptr describe() { return std::make_shared( 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 auto operator[](Arg &&arg) -> decltype(get_command()[std::forward(arg)]) { return _command[std::forward(arg)]; } */ template decltype(std::declval()[std::forward(Arg{})]) operator[](Arg &&arg) { return _command[std::forward(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> function_descriptors_t; template struct describe { inline static void apply(function_descriptors_t& result) {} }; template struct describe { inline static void apply(function_descriptors_t& result) { result.push_back(T::describe()); describe::apply(result); } }; inline void parse_field(const std::shared_ptr& 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 struct command_parser> { constexpr static bool supported = true; typedef field field_t; using descriptor_t = std::shared_ptr; 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 struct command_parser> { constexpr static bool supported = true; typedef trigger trigger_t; using descriptor_t = std::shared_ptr; 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 struct command_parser> { constexpr static bool supported = true; typedef command_handle command_handle_t; using descriptor_t = std::shared_ptr; 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 struct command_parser { constexpr static bool supported = std::is_same::type>::type, command>::value; static_assert(supported, "This type isn't supported!"); using descriptor_t = std::shared_ptr; 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; template using field = impl::field; template using trigger = impl::trigger; template inline impl::function_descriptors_t describe_function() { impl::function_descriptors_t result; impl::describe::apply(result); return result; } struct invocable_function { void operator()(command& command) { this->invoke(command); } virtual void invoke(command& command) = 0; }; template struct typed_invocable_function : public invocable_function { using args_tuple_t = std::tuple; template using command_parser_t = impl::command_parser::type>; using descriptors_t = std::tuple::descriptor_t...>; descriptors_t descriptors; void(*function)(args_t...); void invoke(command& command) override { function(command_parser_t::apply(std::get::value>(descriptors), command)...); } }; template > std::shared_ptr parse_function(void(*function)(args_t...)) { auto result = std::make_shared(); result->function = function; result->descriptors = {impl::command_parser::type>::describe()...}; return result; } template 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 #define tl(lit) _tlit(lit) } //using desc = descriptor::base; } #include "command_internal.h"