#pragma once #include "extract.hpp" #include "function_traits.hpp" #include "restrictions.hpp" #include "type_traits.hpp" #include #include #include namespace ss { INIT_HAS_METHOD(tied); INIT_HAS_METHOD(ss_valid); INIT_HAS_METHOD(error); //////////////// // replace validator //////////////// // replace 'validator' types with elements they operate on // eg. no_validator_tup_t> <=> std::tuple // where ss::nx is a validator '(n)one e(x)cept' which // checks if the returned character is either 'A' or 'B', returns error if not // additionaly if one element is left in the pack, it will be unwraped from // the tuple eg. no_void_validator_tup_t <=> int instead of std::tuple template struct no_validator; template struct no_validator>> { using type = typename member_wrapper::arg_type; }; template struct no_validator { using type = T; }; template using no_validator_t = typename no_validator::type; template struct no_validator_tup { using type = typename apply_trait>::type; }; template struct no_validator_tup> { using type = typename no_validator_tup::type; }; template struct no_validator_tup> { using type = no_validator_t; }; template using no_validator_tup_t = typename no_validator_tup::type; //////////////// // no void tuple //////////////// template struct no_void_tup { using type = typename filter_not>::type; }; template using no_void_tup_t = filter_not_t; //////////////// // no void or validator //////////////// // replace 'validators' and remove void from tuple template struct no_void_validator_tup { using type = no_validator_tup_t>; }; template struct no_void_validator_tup> { using type = no_validator_tup_t>; }; template using no_void_validator_tup_t = typename no_void_validator_tup::type; //////////////// // tied class //////////////// // check if the parameter pack is only one element which is a class and has // the 'tied' method which is to be used for type deduction when converting template struct tied_class { constexpr static bool value = (sizeof...(Ts) == 0 && std::is_class_v && has_m_tied::value); }; template constexpr bool tied_class_v = tied_class::value; // the error can be set inside a string, or a bool enum class error_mode { String, Bool }; //////////////// // converter //////////////// class converter { using string_range = std::pair; constexpr static auto default_delimiter = ','; public: using split_input = std::vector; // parses line with given delimiter, returns a 'T' object created with // extracted values of type 'Ts' template T convert_object(const char* const line, const std::string& delim = "") { return to_object(convert(line, delim)); } // parses line with given delimiter, returns tuple of objects with // extracted values of type 'Ts' template no_void_validator_tup_t convert(const char* const line, const std::string& delim = "") { input_ = split(line, delim); return convert(input_); } // parses already split line, returns 'T' object with extracted values template T convert_object(const split_input& elems) { return to_object(convert(elems)); } // parses already split line, returns either a tuple of objects with // parsed values (returns raw element (no tuple) if Ts is empty), or if // one argument is given which is a class which has a tied // method which returns a tuple, returns that type template no_void_validator_tup_t convert(const split_input& elems) { if constexpr (sizeof...(Ts) == 0 && is_instance_of::value) { return convert_impl(elems, (T*){}); } else if constexpr (tied_class_v) { using arg_ref_tuple = typename std::result_of_t; using arg_tuple = typename apply_trait::type; return to_object(convert_impl(elems, (arg_tuple*){})); } else { return convert_impl(elems); } } bool valid() const { return (error_mode_ == error_mode::String) ? string_error_.empty() : bool_error_ == false; } const std::string& error_msg() const { return string_error_; } void set_error_mode(error_mode mode) { error_mode_ = mode; } // 'splits' string by given delimiter, returns vector of pairs which // contain the beginings and the ends of each column of the string const split_input& split(const char* const line, const std::string& delim = "") { input_.clear(); if (line[0] == '\0') { return input_; } switch (delim.size()) { case 0: return split_impl(line, ','); case 1: return split_impl(line, delim[0]); default: return split_impl(line, delim, delim.size()); }; } private: //////////////// // error //////////////// void clear_error() { string_error_.clear(); bool_error_ = false; } std::string error_sufix(const string_range msg, size_t pos) const { std::string error; error.reserve(32); error.append("at column ") .append(std::to_string(pos + 1)) .append(": \'") .append(msg.first, msg.second) .append("\'"); return error; } void set_error_invalid_conversion(const string_range msg, size_t pos) { if (error_mode_ == error_mode::String) { string_error_.clear(); string_error_.append("invalid conversion for parameter ") .append(error_sufix(msg, pos)); } else { bool_error_ = true; } } void set_error_validate(const char* const error, const string_range msg, size_t pos) { if (error_mode_ == error_mode::String) { string_error_.clear(); string_error_.append(error).append(" ").append( error_sufix(msg, pos)); } else { bool_error_ = true; } } void set_error_number_of_colums(size_t expected_pos, size_t pos) { if (error_mode_ == error_mode::String) { string_error_.clear(); string_error_.append("invalid number of columns, expected: ") .append(std::to_string(expected_pos)) .append(", got: ") .append(std::to_string(pos)); } else { bool_error_ = true; } } //////////////// // convert implementation //////////////// template no_void_validator_tup_t convert_impl(const split_input& elems) { clear_error(); no_void_validator_tup_t ret{}; if (sizeof...(Ts) != elems.size()) { set_error_number_of_colums(sizeof...(Ts), elems.size()); return ret; } return extract_tuple(elems); } // do not know how to specialize by return type :( template no_void_validator_tup_t> convert_impl( const split_input& elems, const std::tuple*) { return convert_impl(elems); } //////////////// // substring //////////////// template const split_input& split_impl(const char* const line, Delim delim, size_t delim_size = 1) { auto range = substring(line, delim); input_.push_back(range); while (range.second[0] != '\0') { range = substring(range.second + delim_size, delim); input_.push_back(range); } return input_; } bool no_match(const char* end, char delim) const { return *end != delim; } bool no_match(const char* end, const std::string& delim) const { return strncmp(end, delim.c_str(), delim.size()) != 0; } template string_range substring(const char* const begin, Delim delim) const { const char* end; for (end = begin; *end != '\0' && no_match(end, delim); ++end) ; return string_range{begin, end}; } //////////////// // conversion //////////////// template void extract_one(no_validator_t& dst, const string_range msg, size_t pos) { if (!valid()) { return; } if (!extract(msg.first, msg.second, dst)) { set_error_invalid_conversion(msg, pos); return; } if constexpr (has_m_ss_valid_t) { if (T validator; !validator.ss_valid(dst)) { if constexpr (has_m_error_t) { set_error_validate(validator.error(), msg, pos); } else { set_error_validate("validation error", msg, pos); } return; } } } template void extract_multiple(no_void_validator_tup_t& tup, const split_input& elems) { using elem_t = std::tuple_element_t>; constexpr bool not_void = !std::is_void_v; constexpr bool one_element = count_not::size == 1; if constexpr (not_void) { if constexpr (one_element) { extract_one(tup, elems[ArgN], ArgN); } else { auto& el = std::get(tup); extract_one(el, elems[ArgN], ArgN); } } if constexpr (sizeof...(Ts) > ArgN + 1) { constexpr size_t NewTupN = (not_void) ? TupN + 1 : TupN; extract_multiple(tup, elems); } } template no_void_validator_tup_t extract_tuple(const split_input& elems) { static_assert(!all_of::value, "at least one parameter must be non void"); no_void_validator_tup_t ret; extract_multiple<0, 0, Ts...>(ret, elems); return ret; }; //////////////// // members //////////////// std::vector input_; std::string string_error_; bool bool_error_; enum error_mode error_mode_ { error_mode::Bool }; }; template <> inline void converter::extract_one(std::string& dst, const string_range msg, size_t) { if (!valid()) { return; } extract(msg.first, msg.second, dst); } } /* ss */