diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aae7f2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +compile_commands.json +.clang-format +experiment/ diff --git a/include/ss/converter.hpp b/include/ss/converter.hpp new file mode 100644 index 0000000..c20a458 --- /dev/null +++ b/include/ss/converter.hpp @@ -0,0 +1,361 @@ +#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 +using no_void_validator_tup_t = typename no_void_validator_tup::type; + +//////////////// +// tied class +//////////////// + +// check if 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; + +//////////////// +// converter +//////////////// + +class converter { + using string_range = std::pair; + using split_input = std::vector; + constexpr static auto default_delimiter = ','; + + public: + // parses line with given delimiter, returns a 'T' object created with + // extracted values of type 'Ts' + template + T convert_struct(const char* const line, + const std::string& delim = "") { + return to_struct(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_struct(const split_input& elems) { + return to_struct(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 (tied_class_v) { + using arg_ref_tuple = + typename std::result_of_t; + + using arg_tuple = + typename apply_trait::type; + + return to_struct( + convert_impl(elems, (arg_tuple*){})); + } else { + return convert_impl(elems); + } + } + + bool valid() const { + return error_.empty(); + } + + const std::string& error() const { + return error_; + } + + // '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 + //////////////// + + 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) { + error_.clear(); + error_.append("invalid conversion for parameter ") + .append(error_sufix(msg, pos)); + } + + void set_error_validate(const char* const error, const string_range msg, + size_t pos) { + error_.clear(); + error_.append(error).append(" ").append(error_sufix(msg, pos)); + } + + void set_error_number_of_colums(size_t expected_pos, size_t pos) { + error_.append("invalid number of columns, expected: ") + .append(std::to_string(expected_pos)) + .append(", got: ") + .append(std::to_string(pos)); + } + + //////////////// + // convert implementation + //////////////// + + template + no_void_validator_tup_t convert_impl(const split_input& elems) { + error_.clear(); + 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 + //////////////// + +#ifdef SS_THROW_ON_INVALID +#define SS_RETURN_ON_INVALID // nop +#else +#define SS_RETURN_ON_INVALID \ + if (!valid()) { \ + return; \ + } +#endif + + template + void extract_one(no_validator_t& dst, const string_range msg, + size_t pos) { + SS_RETURN_ON_INVALID; + + 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_one(std::string& dst, const string_range msg, + size_t) { + SS_RETURN_ON_INVALID; + extract(msg.first, msg.second, dst); + } + +#undef SS_RETURN_ON_INVALID + + 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 not_tuple = + count_not::size == 1; + + if constexpr (not_void) { + if constexpr (not_tuple) { + 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 error_; +}; + +} /* ss */ diff --git a/include/ss/extract.hpp b/include/ss/extract.hpp new file mode 100644 index 0000000..2c752a2 --- /dev/null +++ b/include/ss/extract.hpp @@ -0,0 +1,391 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef SS_THROW_ON_INVALID +#define SS_THROW_OR_NULL(x) throw std::invalid_argument(x) +#define SS_THROW_OR_FALSE(x) throw std::invalid_argument(x) +#else +#define SS_THROW_OR_NULL(x) return std::nullopt +#define SS_THROW_OR_FALSE(x) return false +#endif + +namespace ss { + +// todo +// taken from +// https://gist.github.com/oschonrock/67fc870ba067ebf0f369897a9d52c2dd + +//////////////// +// number converters +//////////////// +template +std::enable_if_t, T> pow10(int n) { + T ret = 1.0; + T r = 10.0; + if (n < 0) { + n = -n; + r = 0.1; + } + + while (n) { + if (n & 1) { + ret *= r; + } + r *= r; + n >>= 1; + } + return ret; +} + +template +std::enable_if_t, std::optional> to_num( + const char* begin, const char* const end) { + if (begin == end) { + SS_THROW_OR_NULL("floating point"); + } + int sign = 1; + T int_part = 0.0; + T frac_part = 0.0; + bool has_frac = false; + bool has_exp = false; + + // +/- sign + if (*begin == '-') { + ++begin; + sign = -1; + } + + while (begin != end) { + if (*begin >= '0' && *begin <= '9') { + int_part = int_part * 10 + (*begin - '0'); + } else if (*begin == '.') { + has_frac = true; + ++begin; + break; + } else if (*begin == 'e') { + has_exp = true; + ++begin; + break; + } else { + SS_THROW_OR_NULL("floating point"); + } + ++begin; + } + + if (has_frac) { + T frac_exp = 0.1; + + while (begin != end) { + if (*begin >= '0' && *begin <= '9') { + frac_part += frac_exp * (*begin - '0'); + frac_exp *= 0.1; + } else if (*begin == 'e') { + has_exp = true; + ++begin; + break; + } else { + SS_THROW_OR_NULL("floating point"); + } + ++begin; + } + } + + // parsing exponent part + T exp_part = 1.0; + if (begin != end && has_exp) { + int exp_sign = 1; + if (*begin == '-') { + exp_sign = -1; + ++begin; + } else if (*begin == '+') { + ++begin; + } + + int e = 0; + while (begin != end && *begin >= '0' && *begin <= '9') { + e = e * 10 + *begin - '0'; + ++begin; + } + + exp_part = pow10(exp_sign * e); + } + + if (begin != end) { + SS_THROW_OR_NULL("floating point"); + } + + return sign * (int_part + frac_part) * exp_part; +} + +inline std::optional from_char(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + SS_THROW_OR_NULL("integral"); +} + +#if defined(__clang__) || defined(__GNUC__) || defined(__GUNG__) +//////////////// +// mul overflow detection +//////////////// +template +bool mul_overflow(T& result, T operand) { + return __builtin_mul_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(int& result, int operand) { + return __builtin_smul_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(long& result, long operand) { + return __builtin_smull_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(long long& result, long long operand) { + return __builtin_smulll_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(unsigned int& result, unsigned int operand) { + return __builtin_umul_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(unsigned long& result, unsigned long operand) { + return __builtin_umull_overflow(result, operand, &result); +} + +template <> +inline bool mul_overflow(unsigned long long& result, + unsigned long long operand) { + return __builtin_umulll_overflow(result, operand, &result); +} + +//////////////// +// addition overflow detection +//////////////// + +template +inline bool add_overflow(T& result, T operand) { + return __builtin_add_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(int& result, int operand) { + return __builtin_sadd_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(long& result, long operand) { + return __builtin_saddl_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(long long& result, long long operand) { + return __builtin_saddll_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(unsigned int& result, unsigned int operand) { + return __builtin_uadd_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(unsigned long& result, unsigned long operand) { + return __builtin_uaddl_overflow(result, operand, &result); +} + +template <> +inline bool add_overflow(unsigned long long& result, + unsigned long long operand) { + return __builtin_uaddll_overflow(result, operand, &result); +} + +//////////////// +// substraction overflow detection +//////////////// +template +inline bool sub_overflow(T& result, T operand) { + return __builtin_sub_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(int& result, int operand) { + return __builtin_ssub_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(long& result, long operand) { + return __builtin_ssubl_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(long long& result, long long operand) { + return __builtin_ssubll_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(unsigned int& result, unsigned int operand) { + return __builtin_usub_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(unsigned long& result, unsigned long operand) { + return __builtin_usubl_overflow(result, operand, &result); +} + +template <> +inline bool sub_overflow(unsigned long long& result, + unsigned long long operand) { + return __builtin_usubll_overflow(result, operand, &result); +} + +template +bool shift_and_add_overflow(T& value, T digit, F add_last_digit_owerflow) { + if (mul_overflow(value, 10) || + add_last_digit_owerflow(value, digit)) { + return true; + } + return false; +} +#else + +#warning "use clang or gcc!!!" +template +bool shift_and_add_overflow(T& value, T digit, U is_negative) { + digit = (is_negative) ? -digit : digit; + T old_value = value; + value = 10 * value + digit; + + T expected_old_value = (value - digit) / 10; + if (old_value != expected_old_value) { + return true; + } + return false; +} + +#endif + +template +std::enable_if_t, std::optional> to_num( + const char* begin, const char* end) { + if (begin == end) { + SS_THROW_OR_NULL("integral"); + } + bool is_negative = false; + if constexpr (std::is_signed_v) { + is_negative = *begin == '-'; + if (is_negative) { + ++begin; + } + } + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + auto add_last_digit_owerflow = + (is_negative) ? sub_overflow : add_overflow; +#else + auto add_last_digit_owerflow = is_negative; +#endif + + T value = 0; + for (auto i = begin; i != end; ++i) { + if (auto digit = from_char(*i); + !digit || + shift_and_add_overflow(value, digit.value(), + add_last_digit_owerflow)) { + SS_THROW_OR_NULL("integral"); + } + } + + return value; +} + +//////////////// +// extract +//////////////// + +namespace error { +template +struct unsupported_type { + constexpr static bool value = false; +}; +} /* namespace */ + +template +std::enable_if_t && !std::is_floating_point_v, bool> +extract(const char*, const char*, T&) { + + static_assert(error::unsupported_type::value, + "Conversion for given type is not defined, an " + "\'extract\' function needs to be defined!"); +} + +//////////////// +// extract specialization +//////////////// + +template +std::enable_if_t || std::is_floating_point_v, bool> +extract(const char* begin, const char* end, T& value) { + auto optional_value = to_num(begin, end); +#ifndef SS_THROW_ON_INVALID + if (!optional_value) { + return false; + } +#endif + value = optional_value.value(); + return true; +} + +template <> +inline bool extract(const char* begin, const char* end, bool& value) { + if (end == begin + 1) { + if (*begin == '1') { + value = true; + return true; + } else if (*begin == '0') { + value = false; + return true; + } + } else { + size_t size = end - begin; + if (size == 4 && strncmp(begin, "true", size) == 0) { + value = true; + return true; + } else if (size == 5 && strncmp(begin, "false", size) == 0) { + value = false; + return true; + } + } + + SS_THROW_OR_FALSE("boolean"); +} + +template <> +inline bool extract(const char* begin, const char* end, char& value) { + value = *begin; + if (end != begin + 1) { + SS_THROW_OR_FALSE("character"); + } + return true; +} + +template <> +inline bool extract(const char* begin, const char* end, std::string& value) { + value = std::string(begin, end); + return true; +} + +#undef SS_THROW_OR_NULL +#undef SS_THROW_OR_FALSE + +} /* ss */ diff --git a/include/ss/function_traits.hpp b/include/ss/function_traits.hpp new file mode 100644 index 0000000..f91b6a9 --- /dev/null +++ b/include/ss/function_traits.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +namespace ss { + +//////////////// +// function traits +//////////////// + +template +struct decayed_arg_n { + static_assert(N - 1 != sizeof...(Ts), "index out of range"); + using type = typename decayed_arg_n::type; +}; + +template +struct decayed_arg_n<0, T, Ts...> { + using type = std::decay_t; +}; + +template +struct function_traits; + +template +struct function_traits> { + using arg_type = Arg; +}; + +template +struct function_traits { + using arg0 = typename decayed_arg_n<0, Ts...>::type; +}; + +template +struct function_traits : function_traits {}; + +template +struct function_traits : function_traits {}; + +template +struct function_traits : function_traits {}; + +template +struct function_traits : function_traits {}; + +template +struct function_traits : function_traits {}; + +template +struct member_wrapper; + +template +struct member_wrapper { + using arg_type = typename function_traits::arg0; +}; + +//////////////// +// has method +//////////////// + +#define INIT_HAS_METHOD(method) \ + template \ + class has_m_##method { \ + template \ + static std::true_type test(decltype(&C::method)); \ + \ + template \ + static std::false_type test(...); \ + \ + public: \ + constexpr static bool value = decltype(test(0))::value; \ + }; \ + \ + template \ + constexpr bool has_m_##method##_t = has_m_##method::value; + +} /* trait */ diff --git a/include/ss/parser.hpp b/include/ss/parser.hpp new file mode 100644 index 0000000..b33c6cd --- /dev/null +++ b/include/ss/parser.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include "converter.hpp" +#include "extract.hpp" +#include "restrictions.hpp" +#include +#include +#include +#include + +namespace ss { + +class parser { + public: + parser(const std::string& file_name, const std::string& delimiter) + : file_name_{file_name}, delim_{delimiter}, + file_{fopen(file_name_.c_str(), "r")} { + if (file_) { + read_line(); + } else { + set_error_file_not_open(); + eof_ = true; + } + } + + ~parser() { + fclose(file_); + } + + bool valid() const { + return error_.empty(); + } + + bool eof() const { + return eof_; + } + + bool ignore_next() { + return buff_.read(file_); + } + + const std::string& error() const { + return error_; + } + + template + T get_struct() { + return to_struct(get_next()); + } + + template + no_void_validator_tup_t get_next() { + error_.clear(); + if (eof_) { + set_error_eof_reached(); + return {}; + } + + auto value = converter_.convert(buff_.get(), delim_); + + if (!converter_.valid()) { + set_error_invalid_conversion(); + } + + read_line(); + return value; + } + + private: + //////////////// + // line reading + //////////////// + + class buffer { + char* buffer_{nullptr}; + size_t size_{0}; + + public: + ~buffer() { + free(buffer_); + } + + bool read(FILE* file) { + ssize_t size = getline(&buffer_, &size_, file); + if (size == -1) { + return false; + } + + buffer_[size - 1] = '\0'; + return true; + } + + const char* get() const { + return buffer_; + } + }; + + void read_line() { + eof_ = !buff_.read(file_); + ++line_number_; + } + + //////////////// + // error + //////////////// + + void set_error_file_not_open() { + error_.append(file_name_).append(" could not be not open."); + } + + void set_error_eof_reached() { + error_.append(file_name_).append(" reached end of file."); + } + + void set_error_invalid_conversion() { + error_.append(file_name_) + .append(" ") + .append(std::to_string(line_number_)) + .append(": ") + .append(converter_.error()) + .append(": \"") + .append(buff_.get()); + error_.append("\""); + } + + //////////////// + // members + //////////////// + + const std::string file_name_; + const std::string delim_; + std::string error_; + ss::converter converter_; + FILE* file_{nullptr}; + buffer buff_; + size_t line_number_{0}; + bool eof_{false}; +}; + +} /* ss */ diff --git a/include/ss/restrictions.hpp b/include/ss/restrictions.hpp new file mode 100644 index 0000000..1848e3e --- /dev/null +++ b/include/ss/restrictions.hpp @@ -0,0 +1,100 @@ +#pragma once + +namespace ss { + +//////////////// +// all except +//////////////// + +template +struct ax { + private: + template + bool ss_valid_impl(const T& x) const { + if constexpr (sizeof...(Xs) != 0) { + return x != X && ss_valid_impl(x); + } + return x != X; + } + + public: + bool ss_valid(const T& value) const { + return ss_valid_impl(value); + } + + const char* error() const { + return "value excluded"; + } +}; + +//////////////// +// none except +//////////////// + +template +struct nx { + private: + template + bool ss_valid_impl(const T& x) const { + if constexpr (sizeof...(Xs) != 0) { + return x == X || ss_valid_impl(x); + } + return x == X; + } + + public: + bool ss_valid(const T& value) const { + return ss_valid_impl(value); + } + + const char* error() const { + return "value excluded"; + } +}; + +//////////////// +// in range +//////////////// + +template +struct ir { + bool ss_valid(const T& value) const { + return value >= Min && value <= Max; + } + + const char* error() const { + return "out of range"; + } +}; + +//////////////// +// out of range +//////////////// + +template +struct oor { + bool ss_valid(const T& value) const { + return value < Min || value > Max; + } + + const char* error() const { + return "in restricted range"; + } +}; + +//////////////// +// non empty +//////////////// + +template +struct ne { + bool ss_valid(const T& value) const { + return !value.empty(); + } + + const char* error() const { + return "empty field"; + } +}; + +} /* ss */ diff --git a/include/ss/type_traits.hpp b/include/ss/type_traits.hpp new file mode 100644 index 0000000..dbe979e --- /dev/null +++ b/include/ss/type_traits.hpp @@ -0,0 +1,329 @@ +#pragma once + +#include +#include + +namespace ss { + +//////////////// +// tup merge/cat +//////////////// + +template +struct tup_cat; + +template +struct tup_cat, std::tuple> { + using type = std::tuple; +}; + +template +struct tup_cat> { + using type = std::tuple; +}; + +template +using tup_cat_t = typename tup_cat::type; + +//////////////// +// tup first/head +//////////////// + +template +struct left_of_impl; + +template +struct left_of_impl { + static_assert(N < 128, "recursion limit reached"); + static_assert(N != 0, "cannot take the whole tuple"); + using type = tup_cat_t::type>; +}; + +template +struct left_of_impl<0, T, Ts...> { + using type = std::tuple; +}; + +template +using left_of_t = typename left_of_impl::type; + +template +using first_t = typename left_of_impl::type; + +template +using head_t = typename left_of_impl<0, Ts...>::type; + +//////////////// +// tup tail/last +//////////////// + +template +struct right_of_impl; + +template +struct right_of_impl { + using type = typename right_of_impl::type; +}; + +template +struct right_of_impl<0, T, Ts...> { + using type = std::tuple; +}; + +template +using right_of_t = typename right_of_impl::type; + +template +using tail_t = typename right_of_impl<1, Ts...>::type; + +template +using last_t = typename right_of_impl::type; + +//////////////// +// apply trait +//////////////// + +template