#pragma once // TODO remove #include #ifndef DBG void log(const std::string& log) { std::cout << log << std::endl; } #else void log(const std::string&) { } #endif // // #include "extract.hpp" #include "function_traits.hpp" #include "restrictions.hpp" #include "type_traits.hpp" #include #include #include constexpr auto space = '_'; constexpr auto escaping = true; constexpr auto quote = '"'; 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 // additionally if one element is left in the pack, it will be unwrapped 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 { error_string, error_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)); } // same as above, but uses cached split line template T convert_object() { return to_object(convert()); } // 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); } } // same as above, but uses cached split line template no_void_validator_tup_t convert() { return convert(input_); } bool valid() const { return (error_mode_ == error_mode::error_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 beginnings 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_quotation() { if (error_mode_ == error_mode::error_string) { string_error_.clear(); string_error_.append("invalid quotation"); } else { bool_error_ = true; } } void set_error_unterminated_quote() { if (error_mode_ == error_mode::error_string) { string_error_.clear(); string_error_.append("unterminated quote"); } else { bool_error_ = true; } } void set_error_invalid_conversion(const string_range msg, size_t pos) { if (error_mode_ == error_mode::error_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::error_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::error_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, begin] = substring(line, delim); input_.push_back(range); while (range.second[0] != '\0') { if constexpr (quote != '\0') { if (*begin == quote) { ++begin; } if (*begin == '\0') { break; } } std::tie(range, begin) = substring(begin + delim_size, delim); log("-> " + std::string{range.first, range.second}); input_.push_back(range); } return input_; } size_t match(const char* begin, char delim) const { const char* p = begin; if constexpr (space == '\0') { if (*p == delim) { return 1; } } else { while (*p == space) { ++p; } if (*p == '\0') { return p - begin; } if (*p != delim) { return 0; } do ++p; while (*p == space); return p - begin; } } size_t match(const char* end, const std::string& delim) const { // TODO log("ahamm"); return strncmp(end, delim.c_str(), delim.size()) != 0; } template std::tuple substring(const char* begin, Delim delim) { const char* end; const char* i; for (i = begin; *i != '\0'; ++i) ; log(">> " + std::string{begin, i}); if constexpr (quote != '\0') { if (*begin == quote) { ++begin; for (end = begin; true; ++end) { if (*end == '\0') { log("error"); set_error_unterminated_quote(); return {string_range{begin, end}, end}; } if constexpr (escaping) { if (end[-1] == '\\') { continue; } } if (*end == quote) { break; } } // end is not \0 size_t to_ignore = match(end + 1, delim); log(std::to_string(to_ignore)); if (to_ignore != 0) { return {string_range{begin, end}, end + to_ignore}; } log("error"); set_error_invalid_quotation(); return {string_range{begin, end}, end}; } } for (end = begin; *end != '\0'; ++end) { size_t to_ignore = match(end, delim); log(std::to_string(to_ignore)); if (to_ignore != 0) { return {string_range{begin, end}, end + to_ignore}; } } return {string_range{begin, end}, 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::error_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 */