diff --git a/.gitignore b/.gitignore index eb880af..55f29f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ compile_commands.json .clang-format -.ccls-cache/* +.clang-tidy +.ccls-cache/ +.cache/ experiment/ build/ hbuild/ diff --git a/include/ss/common.hpp b/include/ss/common.hpp index c70230e..e507994 100644 --- a/include/ss/common.hpp +++ b/include/ss/common.hpp @@ -1,11 +1,15 @@ #pragma once #include -#include #include #include #include #include +#if !__unix__ +#include +#include +#endif + namespace ss { struct none {}; @@ -17,18 +21,18 @@ constexpr inline auto default_delimiter = ","; constexpr inline auto get_line_initial_buffer_size = 128; template -inline void assert_string_error_defined() { +void assert_string_error_defined() { static_assert(StringError, "'string_error' needs to be enabled to use 'error_msg'"); } template -inline void assert_throw_on_error_not_defined() { +void assert_throw_on_error_not_defined() { static_assert(!ThrowOnError, "cannot handle errors manually if " "'throw_on_error' is enabled"); } -inline void* strict_realloc(void* ptr, size_t size) { +[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) { ptr = std::realloc(ptr, size); if (!ptr) { throw std::bad_alloc{}; @@ -38,18 +42,20 @@ inline void* strict_realloc(void* ptr, size_t size) { } #if __unix__ -inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { +[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, + FILE* file) { return getline(&lineptr, &n, file); } #else using ssize_t = intptr_t; -ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { - char buff[get_line_initial_buffer_size]; +[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, + FILE* file) { + std::array buff; if (lineptr == nullptr || n < sizeof(buff)) { - size_t new_n = sizeof(buff); + const size_t new_n = sizeof(buff); lineptr = static_cast(strict_realloc(lineptr, new_n)); n = new_n; } @@ -57,17 +63,17 @@ ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { lineptr[0] = '\0'; size_t line_used = 0; - while (std::fgets(buff, sizeof(buff), file) != nullptr) { + while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) { line_used = std::strlen(lineptr); - size_t buff_used = std::strlen(buff); + size_t buff_used = std::strlen(buff.data()); if (n <= buff_used + line_used) { - size_t new_n = n * 2; + const size_t new_n = n * 2; lineptr = static_cast(strict_realloc(lineptr, new_n)); n = new_n; } - std::memcpy(lineptr + line_used, buff, buff_used); + std::memcpy(lineptr + line_used, buff.data(), buff_used); line_used += buff_used; lineptr[line_used] = '\0'; @@ -81,15 +87,16 @@ ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { #endif -ssize_t get_line_buffer(char*& lineptr, size_t& n, - const char* const csv_data_buffer, size_t csv_data_size, - size_t& curr_char) { +[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n, + const char* const csv_data_buffer, + size_t csv_data_size, + size_t& curr_char) { if (curr_char >= csv_data_size) { return -1; } if (lineptr == nullptr || n < get_line_initial_buffer_size) { - auto new_lineptr = static_cast( + auto* new_lineptr = static_cast( strict_realloc(lineptr, get_line_initial_buffer_size)); lineptr = new_lineptr; n = get_line_initial_buffer_size; @@ -98,7 +105,7 @@ ssize_t get_line_buffer(char*& lineptr, size_t& n, size_t line_used = 0; while (curr_char < csv_data_size) { if (line_used + 1 >= n) { - size_t new_n = n * 2; + const size_t new_n = n * 2; char* new_lineptr = static_cast(strict_realloc(lineptr, new_n)); @@ -114,19 +121,15 @@ ssize_t get_line_buffer(char*& lineptr, size_t& n, } } - if (line_used != 0) { - lineptr[line_used] = '\0'; - return line_used; - } - - return -1; + lineptr[line_used] = '\0'; + return line_used; } -std::tuple get_line(char*& buffer, size_t& buffer_size, - FILE* file, - const char* const csv_data_buffer, - size_t csv_data_size, size_t& curr_char) { - ssize_t ssize; +[[nodiscard]] inline std::tuple get_line( + char*& buffer, size_t& buffer_size, FILE* file, + const char* const csv_data_buffer, size_t csv_data_size, + size_t& curr_char) { + ssize_t ssize = 0; if (file) { ssize = get_line_file(buffer, buffer_size, file); curr_char += ssize; @@ -145,4 +148,4 @@ std::tuple get_line(char*& buffer, size_t& buffer_size, return {ssize, false}; } -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/converter.hpp b/include/ss/converter.hpp index 5058df5..06e67d2 100644 --- a/include/ss/converter.hpp +++ b/include/ss/converter.hpp @@ -110,19 +110,19 @@ public: // parses line with given delimiter, returns a 'T' object created with // extracted values of type 'Ts' template - T convert_object(line_ptr_type line, - const std::string& delim = default_delimiter) { + [[nodiscard]] T convert_object( + line_ptr_type line, const std::string& delim = default_delimiter) { 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( + [[nodiscard]] no_void_validator_tup_t convert( line_ptr_type line, const std::string& delim = default_delimiter) { split(line, delim); if (splitter_.valid()) { - return convert(splitter_.split_data_); + return convert(splitter_.get_split_data()); } else { handle_error_bad_split(); return {}; @@ -131,13 +131,13 @@ public: // parses already split line, returns 'T' object with extracted values template - T convert_object(const split_data& elems) { + [[nodiscard]] T convert_object(const split_data& elems) { return to_object(convert(elems)); } // same as above, but uses cached split line template - T convert_object() { + [[nodiscard]] T convert_object() { return to_object(convert()); } @@ -146,7 +146,8 @@ public: // 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_data& elems) { + [[nodiscard]] no_void_validator_tup_t convert( + const split_data& elems) { if constexpr (sizeof...(Ts) == 0 && is_instance_of_v) { return convert_impl(elems, static_cast(nullptr)); } else if constexpr (tied_class_v) { @@ -162,11 +163,11 @@ public: // same as above, but uses cached split line template - no_void_validator_tup_t convert() { - return convert(splitter_.split_data_); + [[nodiscard]] no_void_validator_tup_t convert() { + return convert(splitter_.get_split_data()); } - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -176,12 +177,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool unterminated_quote() const { + [[nodiscard]] bool unterminated_quote() const { return splitter_.unterminated_quote(); } @@ -189,9 +190,9 @@ public: // contain the beginnings and the ends of each column of the string const split_data& split(line_ptr_type line, const std::string& delim = default_delimiter) { - splitter_.split_data_.clear(); + splitter_.clear_split_data(); if (line[0] == '\0') { - return splitter_.split_data_; + return splitter_.get_split_data(); } return splitter_.split(line, delim); @@ -207,7 +208,7 @@ private: return splitter_.resplit(new_line, new_size, delim); } - size_t size_shifted() { + [[nodiscard]] size_t size_shifted() { return splitter_.size_shifted(); } @@ -223,9 +224,11 @@ private: } } - std::string error_sufix(const string_range msg, size_t pos) const { + [[nodiscard]] std::string error_sufix(const string_range msg, + size_t pos) const { + constexpr static auto reserve_size = 32; std::string error; - error.reserve(32); + error.reserve(reserve_size); error.append("at column ") .append(std::to_string(pos + 1)) .append(": \'") @@ -350,7 +353,8 @@ private: //////////////// template - no_void_validator_tup_t convert_impl(const split_data& elems) { + [[nodiscard]] no_void_validator_tup_t convert_impl( + const split_data& elems) { clear_error(); if (!splitter_.valid()) { @@ -381,7 +385,7 @@ private: } template - no_void_validator_tup_t> convert_impl( + [[nodiscard]] no_void_validator_tup_t> convert_impl( const split_data& elems, const std::tuple*) { return convert_impl(elems); } @@ -390,11 +394,11 @@ private: // column mapping //////////////// - bool columns_mapped() const { - return column_mappings_.size() != 0; + [[nodiscard]] bool columns_mapped() const { + return !column_mappings_.empty(); } - size_t column_position(size_t tuple_position) const { + [[nodiscard]] size_t column_position(size_t tuple_position) const { if (!columns_mapped()) { return tuple_position; } @@ -404,7 +408,7 @@ private: // assumes positions are valid and the vector is not empty void set_column_mapping(std::vector positions, size_t number_of_columns) { - column_mappings_ = positions; + column_mappings_ = std::move(positions); number_of_columns_ = number_of_columns; } @@ -425,7 +429,7 @@ private: } if constexpr (std::is_same_v) { - extract(msg.first, msg.second, dst); + static_cast(extract(msg.first, msg.second, dst)); return; } @@ -471,7 +475,8 @@ private: } template - no_void_validator_tup_t extract_tuple(const split_data& elems) { + [[nodiscard]] no_void_validator_tup_t extract_tuple( + const split_data& elems) { static_assert(!all_of_v, "at least one parameter must be non void"); no_void_validator_tup_t ret{}; @@ -490,7 +495,7 @@ private: friend class parser; std::vector column_mappings_; - size_t number_of_columns_; + size_t number_of_columns_{0}; }; -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/exception.hpp b/include/ss/exception.hpp index 97c714a..90f1441 100644 --- a/include/ss/exception.hpp +++ b/include/ss/exception.hpp @@ -12,12 +12,12 @@ class exception : public std::exception { std::string msg_; public: - exception(const std::string& msg): msg_{msg} { + exception(std::string msg) : msg_{std::move(msg)} { } - virtual char const* what() const noexcept { + [[nodiscard]] char const* what() const noexcept override { return msg_.c_str(); } }; -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/extract.hpp b/include/ss/extract.hpp index 64b6b1b..58cea4e 100644 --- a/include/ss/extract.hpp +++ b/include/ss/extract.hpp @@ -2,8 +2,8 @@ #include "type_traits.hpp" #include +#include #include -#include #include #include #include @@ -13,6 +13,7 @@ #include #else #include +#include #include #endif @@ -25,8 +26,8 @@ namespace ss { #ifndef SSP_DISABLE_FAST_FLOAT template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, std::optional> +to_num(const char* const begin, const char* const end) { T ret; auto [ptr, ec] = fast_float::from_chars(begin, end, ret); @@ -39,22 +40,23 @@ std::enable_if_t, std::optional> to_num( #else template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, std::optional> +to_num(const char* const begin, const char* const end) { static_assert(!std::is_same_v, "Conversion to long double is disabled"); constexpr static auto buff_max = 64; - char short_buff[buff_max]; - size_t string_range = std::distance(begin, end); + std::array short_buff; + + const size_t string_range = std::distance(begin, end); std::string long_buff; - char* buff; + char* buff = nullptr; if (string_range > buff_max) { long_buff = std::string{begin, end}; buff = long_buff.data(); } else { - buff = short_buff; + buff = short_buff.data(); buff[string_range] = '\0'; std::copy_n(begin, string_range, buff); } @@ -86,12 +88,14 @@ struct numeric_wrapper { using type = T; numeric_wrapper() = default; - numeric_wrapper(numeric_wrapper&&) = default; + numeric_wrapper(numeric_wrapper&&) noexcept = default; numeric_wrapper(const numeric_wrapper&) = default; - numeric_wrapper& operator=(numeric_wrapper&&) = default; + numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default; numeric_wrapper& operator=(const numeric_wrapper&) = default; + ~numeric_wrapper() = default; + numeric_wrapper(T other) : value{other} { } @@ -110,7 +114,7 @@ using int8 = numeric_wrapper; using uint8 = numeric_wrapper; template -std::enable_if_t, std::optional> to_num( +[[nodiscard]] std::enable_if_t, std::optional> to_num( const char* const begin, const char* const end) { T ret; auto [ptr, ec] = std::from_chars(begin, end, ret); @@ -122,8 +126,9 @@ std::enable_if_t, std::optional> to_num( } template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, + std::optional> +to_num(const char* const begin, const char* const end) { T ret; auto [ptr, ec] = std::from_chars(begin, end, ret.value); @@ -142,14 +147,15 @@ template struct unsupported_type { constexpr static bool value = false; }; -} /* namespace */ +} /* namespace errors */ template -std::enable_if_t && !std::is_floating_point_v && - !is_instance_of_v && - !is_instance_of_v && - !is_instance_of_v, - bool> +[[nodiscard]] std::enable_if_t && + !std::is_floating_point_v && + !is_instance_of_v && + !is_instance_of_v && + !is_instance_of_v, + bool> extract(const char*, const char*, T&) { static_assert(error::unsupported_type::value, "Conversion for given type is not defined, an " @@ -157,9 +163,10 @@ extract(const char*, const char*, T&) { } template -std::enable_if_t || std::is_floating_point_v || - is_instance_of_v, - bool> +[[nodiscard]] std::enable_if_t || + std::is_floating_point_v || + is_instance_of_v, + bool> extract(const char* begin, const char* end, T& value) { auto optional_value = to_num(begin, end); if (!optional_value) { @@ -170,8 +177,8 @@ extract(const char* begin, const char* end, T& value) { } template -std::enable_if_t, bool> extract( - const char* begin, const char* end, T& value) { +[[nodiscard]] std::enable_if_t, bool> +extract(const char* begin, const char* end, T& value) { typename T::value_type raw_value; if (extract(begin, end, raw_value)) { value = raw_value; @@ -182,7 +189,8 @@ std::enable_if_t, bool> extract( } template -bool extract_variant(const char* begin, const char* end, T& value) { +[[nodiscard]] bool extract_variant(const char* begin, const char* end, + T& value) { using IthType = std::variant_alternative_t>; IthType ithValue; if (extract(begin, end, ithValue)) { @@ -195,7 +203,7 @@ bool extract_variant(const char* begin, const char* end, T& value) { } template -std::enable_if_t, bool> extract( +[[nodiscard]] std::enable_if_t, bool> extract( const char* begin, const char* end, T& value) { return extract_variant(begin, end, value); } @@ -205,7 +213,8 @@ std::enable_if_t, bool> extract( //////////////// template <> -inline bool extract(const char* begin, const char* end, bool& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + bool& value) { if (end == begin + 1) { if (*begin == '1') { value = true; @@ -215,10 +224,13 @@ inline bool extract(const char* begin, const char* end, bool& value) { return false; } } else { - size_t size = end - begin; - if (size == 4 && std::strncmp(begin, "true", size) == 0) { + constexpr static auto true_size = 4; + constexpr static auto false_size = 5; + const size_t size = end - begin; + if (size == true_size && std::strncmp(begin, "true", size) == 0) { value = true; - } else if (size == 5 && std::strncmp(begin, "false", size) == 0) { + } else if (size == false_size && + std::strncmp(begin, "false", size) == 0) { value = false; } else { return false; @@ -229,22 +241,24 @@ inline bool extract(const char* begin, const char* end, bool& value) { } template <> -inline bool extract(const char* begin, const char* end, char& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + char& value) { value = *begin; return (end == begin + 1); } template <> -inline bool extract(const char* begin, const char* end, std::string& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + std::string& value) { value = std::string{begin, end}; return true; } template <> -inline bool extract(const char* begin, const char* end, - std::string_view& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + std::string_view& value) { value = std::string_view{begin, static_cast(end - begin)}; return true; } -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/function_traits.hpp b/include/ss/function_traits.hpp index c22a936..b71df1b 100644 --- a/include/ss/function_traits.hpp +++ b/include/ss/function_traits.hpp @@ -2,7 +2,6 @@ #include #include -#include namespace ss { @@ -77,4 +76,4 @@ struct member_wrapper { template \ constexpr bool has_m_##method##_t = has_m_##method::value; -} /* trait */ +} /* namespace ss */ diff --git a/include/ss/parser.hpp b/include/ss/parser.hpp index e800593..b054328 100644 --- a/include/ss/parser.hpp +++ b/include/ss/parser.hpp @@ -5,7 +5,6 @@ #include "exception.hpp" #include "extract.hpp" #include "restrictions.hpp" -#include #include #include #include @@ -32,10 +31,12 @@ class parser { constexpr static bool ignore_empty = setup::ignore_empty; + using header_splitter = ss::splitter< + ss::filter_not_t>; + public: - parser(const std::string& file_name, - const std::string& delim = ss::default_delimiter) - : file_name_{file_name}, reader_{file_name_, delim} { + parser(std::string file_name, std::string delim = ss::default_delimiter) + : file_name_{std::move(file_name)}, reader_{file_name_, delim} { if (reader_.file_) { read_line(); if constexpr (ignore_header) { @@ -51,7 +52,7 @@ public: parser(const char* const csv_data_buffer, size_t csv_data_size, const std::string& delim = ss::default_delimiter) - : file_name_{"buffer line"}, + : file_name_{"CSV data buffer"}, reader_{csv_data_buffer, csv_data_size, delim} { if (csv_data_buffer) { read_line(); @@ -66,14 +67,15 @@ public: } } - parser(parser&& other) = default; - parser& operator=(parser&& other) = default; + parser(parser&& other) noexcept = default; + parser& operator=(parser&& other) noexcept = default; + ~parser() = default; parser() = delete; parser(const parser& other) = delete; parser& operator=(const parser& other) = delete; - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -83,12 +85,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool eof() const { + [[nodiscard]] bool eof() const { return eof_; } @@ -97,23 +99,21 @@ public: } template - T get_object() { + [[nodiscard]] T get_object() { return to_object(get_next()); } - size_t line() const { + [[nodiscard]] size_t line() const { return reader_.line_number_ > 0 ? reader_.line_number_ - 1 : reader_.line_number_; } - size_t position() const { + [[nodiscard]] size_t position() const { return reader_.chars_read_; } template - no_void_validator_tup_t get_next() { - std::optional error; - + [[nodiscard]] no_void_validator_tup_t get_next() { if (!eof_) { if constexpr (throw_on_error) { try { @@ -162,20 +162,49 @@ public: return value; } - bool field_exists(const std::string& field) { + [[nodiscard]] std::string raw_header() const { + assert_ignore_header_not_defined(); + return raw_header_; + } + + [[nodiscard]] std::vector header() { + assert_ignore_header_not_defined(); + clear_error(); + + header_splitter splitter; + std::string raw_header_copy = raw_header_; + + if (!strict_split(splitter, raw_header_copy)) { + return {}; + } + + std::vector split_header; + for (const auto& [begin, end] : splitter.get_split_data()) { + split_header.emplace_back(begin, end); + } + + return split_header; + } + + [[nodiscard]] bool field_exists(const std::string& field) { + assert_ignore_header_not_defined(); + clear_error(); + if (header_.empty()) { split_header_data(); } + if (!valid()) { + return false; + } + return header_index(field).has_value(); } template void use_fields(const Ts&... fields_args) { - if constexpr (ignore_header) { - handle_error_header_ignored(); - return; - } + assert_ignore_header_not_defined(); + clear_error(); if (header_.empty() && !eof()) { split_header_data(); @@ -188,7 +217,7 @@ public: auto fields = std::vector{fields_args...}; if (fields.empty()) { - handle_error_empty_mapping(); + handle_error_invalid_use_fields_argument(); return; } @@ -236,13 +265,17 @@ public: } iterator(const iterator& other) = default; - iterator(iterator&& other) = default; + iterator(iterator&& other) noexcept = default; + ~iterator() = default; - value& operator*() { + iterator& operator=(const iterator& other) = delete; + iterator& operator=(iterator&& other) noexcept = delete; + + [[nodiscard]] value& operator*() { return value_; } - value* operator->() { + [[nodiscard]] value* operator->() { return &value_; } @@ -261,17 +294,21 @@ public: return *this; } - iterator& operator++(int) { - return ++*this; + iterator operator++(int) { + auto result = *this; + ++*this; + return result; } - friend bool operator==(const iterator& lhs, const iterator& rhs) { + [[nodiscard]] friend bool operator==(const iterator& lhs, + const iterator& rhs) { return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) || (lhs.parser_ == rhs.parser_ && &lhs.value_ == &rhs.value_); } - friend bool operator!=(const iterator& lhs, const iterator& rhs) { + [[nodiscard]] friend bool operator!=(const iterator& lhs, + const iterator& rhs) { return !(lhs == rhs); } @@ -283,11 +320,11 @@ public: iterable(parser* parser) : parser_{parser} { } - iterator begin() { + [[nodiscard]] iterator begin() { return ++iterator{parser_}; } - iterator end() { + [[nodiscard]] iterator end() { return iterator{}; } @@ -296,12 +333,12 @@ public: }; template - auto iterate() { + [[nodiscard]] auto iterate() { return iterable{this}; } template - auto iterate_object() { + [[nodiscard]] auto iterate_object() { return iterable{this}; } @@ -326,7 +363,7 @@ public: Fun&& fun = none{}) { using Value = no_void_validator_tup_t; std::optional value; - try_convert_and_invoke(value, fun); + try_convert_and_invoke(value, std::forward(fun)); return composite_with(std::move(value)); } @@ -335,11 +372,11 @@ public: template composite> or_object(Fun&& fun = none{}) { std::optional value; - try_convert_and_invoke(value, fun); + try_convert_and_invoke(value, std::forward(fun)); return composite_with(std::move(value)); } - std::tuple values() { + [[nodiscard]] std::tuple values() { return values_; } @@ -362,7 +399,7 @@ public: private: template - composite composite_with(T&& new_value) { + [[nodiscard]] composite composite_with(T&& new_value) { auto merged_values = std::tuple_cat(std::move(values_), std::tuple{parser_.valid() @@ -392,7 +429,7 @@ public: } template - no_void_validator_tup_t try_same() { + [[nodiscard]] no_void_validator_tup_t try_same() { parser_.clear_error(); auto value = parser_.reader_.converter_.template convert(); @@ -413,8 +450,8 @@ public: // tries to convert a line and returns a composite which is // able to try additional conversions in case of failure template - composite>> try_next( - Fun&& fun = none{}) { + [[nodiscard]] composite>> + try_next(Fun&& fun = none{}) { assert_throw_on_error_not_defined(); using Ret = no_void_validator_tup_t; return try_invoke_and_make_composite< @@ -424,7 +461,7 @@ public: // identical to try_next but returns composite with object instead of a // tuple template - composite> try_object(Fun&& fun = none{}) { + [[nodiscard]] composite> try_object(Fun&& fun = none{}) { assert_throw_on_error_not_defined(); return try_invoke_and_make_composite< std::optional>(get_object(), std::forward(fun)); @@ -443,7 +480,8 @@ private: using Ret = decltype(try_invoke_impl(arg, std::forward(fun))); constexpr bool returns_void = std::is_same_v; if constexpr (!returns_void) { - if (!try_invoke_impl(arg, std::forward(fun))) { + if (!try_invoke_impl(std::forward(arg), + std::forward(fun))) { handle_error_failed_check(); } } else { @@ -474,26 +512,55 @@ private: } template - composite try_invoke_and_make_composite(T&& value, Fun&& fun) { + [[nodiscard]] composite try_invoke_and_make_composite(T&& value, + Fun&& fun) { if (valid()) { try_invoke(*value, std::forward(fun)); } - return {valid() ? std::move(value) : std::nullopt, *this}; + return {valid() ? std::forward(value) : std::nullopt, *this}; } //////////////// // header //////////////// + void assert_ignore_header_not_defined() const { + static_assert(!ignore_header, + "cannot use this method when 'ignore_header' is defined"); + } + + [[nodiscard]] bool strict_split(header_splitter& splitter, + std::string& header) { + if constexpr (throw_on_error) { + try { + splitter.split(header.data(), reader_.delim_); + } catch (const ss::exception& e) { + decorate_rethrow_invalid_header_split(e); + } + } else { + splitter.split(header.data(), reader_.delim_); + if (!splitter.valid()) { + handle_error_invalid_header_split(splitter); + return false; + } + } + + return true; + } + void split_header_data() { - ss::splitter splitter; + header_splitter splitter; std::string raw_header_copy = raw_header_; - splitter.split(raw_header_copy.data(), reader_.delim_); - for (const auto& [begin, end] : splitter.split_data_) { + + if (!strict_split(splitter, raw_header_copy)) { + return; + } + + for (const auto& [begin, end] : splitter.get_split_data()) { std::string field{begin, end}; if (std::find(header_.begin(), header_.end(), field) != header_.end()) { - handle_error_invalid_header(field); + handle_error_duplicate_header_field(field); header_.clear(); return; } @@ -501,7 +568,7 @@ private: } } - std::optional header_index(const std::string& field) { + [[nodiscard]] std::optional header_index(const std::string& field) { auto it = std::find(header_.begin(), header_.end(), field); if (it == header_.end()) { @@ -524,7 +591,7 @@ private: } void handle_error_failed_check() { - constexpr static auto error_msg = " failed check"; + constexpr static auto error_msg = ": failed check"; if constexpr (string_error) { error_.clear(); @@ -537,7 +604,7 @@ private: } void handle_error_null_buffer() { - constexpr static auto error_msg = " received null data buffer"; + constexpr static auto error_msg = ": received null data buffer"; if constexpr (string_error) { error_.clear(); @@ -550,7 +617,7 @@ private: } void handle_error_file_not_open() { - constexpr static auto error_msg = " could not be opened"; + constexpr static auto error_msg = ": could not be opened"; if constexpr (string_error) { error_.clear(); @@ -563,7 +630,7 @@ private: } void handle_error_eof_reached() { - constexpr static auto error_msg = " read on end of file"; + constexpr static auto error_msg = ": read on end of file"; if constexpr (string_error) { error_.clear(); @@ -588,20 +655,6 @@ private: } } - void handle_error_header_ignored() { - constexpr static auto error_msg = - ": the header row is ignored within the setup it cannot be used"; - - if constexpr (string_error) { - error_.clear(); - error_.append(file_name_).append(error_msg); - } else if constexpr (throw_on_error) { - throw ss::exception{file_name_ + error_msg}; - } else { - error_ = true; - } - } - void handle_error_invalid_field(const std::string& field) { constexpr static auto error_msg = ": header does not contain given field: "; @@ -629,8 +682,9 @@ private: } } - void handle_error_empty_mapping() { - constexpr static auto error_msg = "received empty mapping"; + void handle_error_invalid_use_fields_argument() { + constexpr static auto error_msg = + "received invalid argument for 'use_fields'"; if constexpr (string_error) { error_.clear(); @@ -642,19 +696,53 @@ private: } } - void handle_error_invalid_header(const std::string& field) { - constexpr static auto error_msg = "header contains duplicates: "; + void handle_error_invalid_header_field() { + constexpr static auto error_msg = ": header contains empty field"; if constexpr (string_error) { error_.clear(); - error_.append(error_msg).append(error_msg); + error_.append(file_name_).append(error_msg); } else if constexpr (throw_on_error) { - throw ss::exception{error_msg + field}; + throw ss::exception{file_name_ + error_msg}; } else { error_ = true; } } + void handle_error_duplicate_header_field(const std::string& field) { + constexpr static auto error_msg = ": header contains duplicate: "; + + if constexpr (string_error) { + error_.clear(); + error_.append(file_name_).append(error_msg).append(field); + } else if constexpr (throw_on_error) { + throw ss::exception{file_name_ + error_msg + field}; + } else { + error_ = true; + } + } + + void handle_error_invalid_header_split(const header_splitter& splitter) { + constexpr static auto error_msg = ": failed header parsing: "; + + if constexpr (string_error) { + error_.clear(); + error_.append(file_name_) + .append(error_msg) + .append(splitter.error_msg()); + } else { + error_ = true; + } + } + + void decorate_rethrow_invalid_header_split(const ss::exception& e) const { + static_assert(throw_on_error, + "throw_on_error needs to be enabled to use this method"); + throw ss::exception{std::string{file_name_} + .append(": failed header parsing: ") + .append(e.what())}; + } + void decorate_rethrow(const ss::exception& e) const { static_assert(throw_on_error, "throw_on_error needs to be enabled to use this method"); @@ -674,17 +762,18 @@ private: } struct reader { - reader(const std::string& file_name_, const std::string& delim) - : delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} { + reader(const std::string& file_name_, std::string delim) + : delim_{std::move(delim)}, + file_{std::fopen(file_name_.c_str(), "rb")} { } reader(const char* const buffer, size_t csv_data_size, - const std::string& delim) - : delim_{delim}, csv_data_buffer_{buffer}, + std::string delim) + : delim_{std::move(delim)}, csv_data_buffer_{buffer}, csv_data_size_{csv_data_size} { } - reader(reader&& other) + reader(reader&& other) noexcept : buffer_{other.buffer_}, next_line_buffer_{other.next_line_buffer_}, helper_buffer_{other.helper_buffer_}, @@ -705,7 +794,7 @@ private: other.file_ = nullptr; } - reader& operator=(reader&& other) { + reader& operator=(reader&& other) noexcept { if (this != &other) { buffer_ = other.buffer_; next_line_buffer_ = other.next_line_buffer_; @@ -741,7 +830,7 @@ private: std::free(helper_buffer_); if (file_) { - std::fclose(file_); + std::ignore = std::fclose(file_); } } @@ -750,7 +839,7 @@ private: reader& operator=(const reader& other) = delete; // read next line each time in order to set eof_ - bool read_next() { + [[nodiscard]] bool read_next() { next_line_converter_.clear_error(); size_t size = 0; while (size == 0) { @@ -842,7 +931,7 @@ private: std::swap(converter_, next_line_converter_); } - bool multiline_limit_reached(size_t& limit) { + [[nodiscard]] bool multiline_limit_reached(size_t& limit) { if constexpr (multiline::size > 0) { if (limit++ >= multiline::size) { next_line_converter_.handle_error_multiline_limit_reached(); @@ -852,8 +941,8 @@ private: return false; } - bool escaped_eol(size_t size) { - const char* curr; + [[nodiscard]] bool escaped_eol(size_t size) { + const char* curr = nullptr; for (curr = next_line_buffer_ + size - 1; curr >= next_line_buffer_ && setup::escape::match(*curr); @@ -862,7 +951,7 @@ private: return (next_line_buffer_ - curr + size) % 2 == 0; } - bool unterminated_quote() { + [[nodiscard]] bool unterminated_quote() { return next_line_converter_.unterminated_quote(); } @@ -877,7 +966,7 @@ private: } } - size_t remove_eol(char*& buffer, size_t ssize) { + [[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) { if (buffer[ssize - 1] != '\n') { crlf_ = false; return ssize; @@ -899,7 +988,7 @@ private: size_t& buffer_size, const char* const second, size_t second_size) { buffer_size = first_size + second_size + 3; - auto new_first = static_cast( + auto* new_first = static_cast( strict_realloc(static_cast(first), buffer_size)); first = new_first; @@ -907,8 +996,9 @@ private: first_size += second_size; } - bool append_next_line_to_buffer(char*& buffer, size_t& line_size, - size_t buffer_size) { + [[nodiscard]] bool append_next_line_to_buffer(char*& buffer, + size_t& line_size, + size_t buffer_size) { undo_remove_eol(buffer, line_size, buffer_size); chars_read_ = curr_char_; @@ -927,8 +1017,8 @@ private: return true; } - std::string get_buffer() { - return std::string{next_line_buffer_, next_line_buffer_size_}; + [[nodiscard]] std::string get_buffer() { + return std::string{next_line_buffer_, next_line_size_}; } //////////////// @@ -971,4 +1061,4 @@ private: bool eof_{false}; }; -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/restrictions.hpp b/include/ss/restrictions.hpp index e72611e..bcd8335 100644 --- a/include/ss/restrictions.hpp +++ b/include/ss/restrictions.hpp @@ -10,7 +10,7 @@ template struct ax { private: template - bool ss_valid_impl(const T& x) const { + [[nodiscard]] bool ss_valid_impl(const T& x) const { if constexpr (sizeof...(Xs) != 0) { return x != X && ss_valid_impl(x); } @@ -18,11 +18,11 @@ private: } public: - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return ss_valid_impl(value); } - const char* error() const { + [[nodiscard]] const char* error() const { return "value excluded"; } }; @@ -35,7 +35,7 @@ template struct nx { private: template - bool ss_valid_impl(const T& x) const { + [[nodiscard]] bool ss_valid_impl(const T& x) const { if constexpr (sizeof...(Xs) != 0) { return x == X || ss_valid_impl(x); } @@ -43,11 +43,11 @@ private: } public: - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return ss_valid_impl(value); } - const char* error() const { + [[nodiscard]] const char* error() const { return "value excluded"; } }; @@ -61,28 +61,28 @@ public: template struct gt { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value > N; } }; template struct gte { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value >= N; } }; template struct lt { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value < N; } }; template struct lte { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value <= N; } }; @@ -93,7 +93,7 @@ struct lte { template struct ir { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value >= Min && value <= Max; } }; @@ -104,7 +104,7 @@ struct ir { template struct oor { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value < Min || value > Max; } }; @@ -115,13 +115,13 @@ struct oor { template struct ne { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return !value.empty(); } - const char* error() const { + [[nodiscard]] const char* error() const { return "empty field"; } }; -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/setup.hpp b/include/ss/setup.hpp index 2f298fc..d0172ba 100644 --- a/include/ss/setup.hpp +++ b/include/ss/setup.hpp @@ -293,4 +293,7 @@ private: template struct setup> : setup {}; -} /* ss */ +template +struct setup> : setup {}; + +} /* namespace ss */ diff --git a/include/ss/splitter.hpp b/include/ss/splitter.hpp index d974e83..0f06499 100644 --- a/include/ss/splitter.hpp +++ b/include/ss/splitter.hpp @@ -2,11 +2,9 @@ #include "common.hpp" #include "exception.hpp" #include "setup.hpp" -#include "type_traits.hpp" #include #include #include -#include #include #include @@ -30,7 +28,7 @@ private: public: using line_ptr_type = std::conditional_t; - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -40,12 +38,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool unterminated_quote() const { + [[nodiscard]] bool unterminated_quote() const { return unterminated_quote_; } @@ -57,13 +55,21 @@ public: return split_impl_select_delim(delimiter); } + [[nodiscard]] const split_data& get_split_data() const { + return split_data_; + } + + void clear_split_data() { + split_data_.clear(); + } + private: //////////////// // resplit //////////////// // number of characters the end of line is shifted backwards - size_t size_shifted() const { + [[nodiscard]] size_t size_shifted() const { return escaped_; } @@ -86,7 +92,7 @@ private: } const auto [old_line, old_begin] = *std::prev(split_data_.end()); - size_t begin = old_begin - old_line - 1; + const size_t begin = old_begin - old_line - 1; // safety measure if (new_size != -1 && static_cast(new_size) < begin) { @@ -194,19 +200,19 @@ private: // matching //////////////// - bool match(const char* const curr, char delim) { + [[nodiscard]] bool match(const char* const curr, char delim) { return *curr == delim; }; - bool match(const char* const curr, const std::string& delim) { + [[nodiscard]] bool match(const char* const curr, const std::string& delim) { return std::strncmp(curr, delim.c_str(), delim.size()) == 0; }; - size_t delimiter_size(char) { + [[nodiscard]] size_t delimiter_size(char) { return 1; } - size_t delimiter_size(const std::string& delim) { + [[nodiscard]] size_t delimiter_size(const std::string& delim) { return delim.size(); } @@ -227,8 +233,8 @@ private: } template - std::tuple match_delimiter(line_ptr_type begin, - const Delim& delim) { + [[nodiscard]] std::tuple match_delimiter(line_ptr_type begin, + const Delim& delim) { line_ptr_type end = begin; trim_right_if_enabled(end); @@ -322,8 +328,9 @@ private: trim_left_if_enabled(begin_); - for (done_ = false; !done_; read(delim)) - ; + for (done_ = false; !done_;) { + read(delim); + } return split_data_; } @@ -462,7 +469,6 @@ private: // members //////////////// -public: error_type error_{}; bool unterminated_quote_{false}; bool done_{true}; @@ -479,4 +485,4 @@ public: friend class converter; }; -} /* ss */ +} /* namespace ss */ diff --git a/include/ss/type_traits.hpp b/include/ss/type_traits.hpp index 294b8bb..0f8a776 100644 --- a/include/ss/type_traits.hpp +++ b/include/ss/type_traits.hpp @@ -34,7 +34,11 @@ struct left_of_impl; template struct left_of_impl { - static_assert(N < 128, "recursion limit reached"); +private: + constexpr static auto recursion_limit = 128; + +public: + static_assert(N < recursion_limit, "recursion limit reached"); static_assert(N != 0, "cannot take the whole tuple"); using type = tup_cat_t::type>; }; @@ -362,12 +366,12 @@ constexpr bool is_instance_of_v = is_instance_of::value; //////////////// template -T to_object_impl(std::index_sequence, U&& data) { +[[nodiscard]] T to_object_impl(std::index_sequence, U&& data) { return {std::get(std::forward(data))...}; } template -T to_object(U&& data) { +[[nodiscard]] T to_object(U&& data) { using NoRefU = std::decay_t; if constexpr (is_instance_of_v) { return to_object_impl< @@ -378,4 +382,4 @@ T to_object(U&& data) { } } -} /* trait */ +} /* namespace ss */ diff --git a/script/single_header_generator.py b/script/single_header_generator.py index 91c9dcc..7aa9bfc 100755 --- a/script/single_header_generator.py +++ b/script/single_header_generator.py @@ -14,14 +14,21 @@ headers = ['type_traits.hpp', combined_file = [] includes = [] +in_pp_block = False for header in headers: with open(headers_dir + header) as f: for line in f.read().splitlines(): + if '#if ' in line: + in_pp_block = True + + if '#endif' in line: + in_pp_block = False + if '#include "' in line or '#include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +50,11 @@ struct left_of_impl; template struct left_of_impl { - static_assert(N < 128, "recursion limit reached"); +private: + constexpr static auto recursion_limit = 128; + +public: + static_assert(N < recursion_limit, "recursion limit reached"); static_assert(N != 0, "cannot take the whole tuple"); using type = tup_cat_t::type>; }; @@ -378,12 +382,12 @@ constexpr bool is_instance_of_v = is_instance_of::value; //////////////// template -T to_object_impl(std::index_sequence, U&& data) { +[[nodiscard]] T to_object_impl(std::index_sequence, U&& data) { return {std::get(std::forward(data))...}; } template -T to_object(U&& data) { +[[nodiscard]] T to_object(U&& data) { using NoRefU = std::decay_t; if constexpr (is_instance_of_v) { return to_object_impl< @@ -394,7 +398,7 @@ T to_object(U&& data) { } } -} /* trait */ +} /* namespace ss */ namespace ss { @@ -406,15 +410,15 @@ class exception : public std::exception { std::string msg_; public: - exception(const std::string& msg): msg_{msg} { + exception(std::string msg) : msg_{std::move(msg)} { } - virtual char const* what() const noexcept { + [[nodiscard]] char const* what() const noexcept override { return msg_.c_str(); } }; -} /* ss */ +} /* namespace ss */ namespace ss { @@ -490,7 +494,7 @@ struct member_wrapper { template \ constexpr bool has_m_##method##_t = has_m_##method::value; -} /* trait */ +} /* namespace ss */ namespace ss { @@ -502,7 +506,7 @@ template struct ax { private: template - bool ss_valid_impl(const T& x) const { + [[nodiscard]] bool ss_valid_impl(const T& x) const { if constexpr (sizeof...(Xs) != 0) { return x != X && ss_valid_impl(x); } @@ -510,11 +514,11 @@ private: } public: - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return ss_valid_impl(value); } - const char* error() const { + [[nodiscard]] const char* error() const { return "value excluded"; } }; @@ -527,7 +531,7 @@ template struct nx { private: template - bool ss_valid_impl(const T& x) const { + [[nodiscard]] bool ss_valid_impl(const T& x) const { if constexpr (sizeof...(Xs) != 0) { return x == X || ss_valid_impl(x); } @@ -535,11 +539,11 @@ private: } public: - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return ss_valid_impl(value); } - const char* error() const { + [[nodiscard]] const char* error() const { return "value excluded"; } }; @@ -553,28 +557,28 @@ public: template struct gt { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value > N; } }; template struct gte { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value >= N; } }; template struct lt { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value < N; } }; template struct lte { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value <= N; } }; @@ -585,7 +589,7 @@ struct lte { template struct ir { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value >= Min && value <= Max; } }; @@ -596,7 +600,7 @@ struct ir { template struct oor { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return value < Min || value > Max; } }; @@ -607,16 +611,21 @@ struct oor { template struct ne { - bool ss_valid(const T& value) const { + [[nodiscard]] bool ss_valid(const T& value) const { return !value.empty(); } - const char* error() const { + [[nodiscard]] const char* error() const { return "empty field"; } }; -} /* ss */ +} /* namespace ss */ + +#if !__unix__ +#include +#include +#endif namespace ss { @@ -629,18 +638,18 @@ constexpr inline auto default_delimiter = ","; constexpr inline auto get_line_initial_buffer_size = 128; template -inline void assert_string_error_defined() { +void assert_string_error_defined() { static_assert(StringError, "'string_error' needs to be enabled to use 'error_msg'"); } template -inline void assert_throw_on_error_not_defined() { +void assert_throw_on_error_not_defined() { static_assert(!ThrowOnError, "cannot handle errors manually if " "'throw_on_error' is enabled"); } -inline void* strict_realloc(void* ptr, size_t size) { +[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) { ptr = std::realloc(ptr, size); if (!ptr) { throw std::bad_alloc{}; @@ -650,18 +659,20 @@ inline void* strict_realloc(void* ptr, size_t size) { } #if __unix__ -inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { +[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, + FILE* file) { return getline(&lineptr, &n, file); } #else using ssize_t = intptr_t; -ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { - char buff[get_line_initial_buffer_size]; +[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, + FILE* file) { + std::array buff; if (lineptr == nullptr || n < sizeof(buff)) { - size_t new_n = sizeof(buff); + const size_t new_n = sizeof(buff); lineptr = static_cast(strict_realloc(lineptr, new_n)); n = new_n; } @@ -669,17 +680,17 @@ ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { lineptr[0] = '\0'; size_t line_used = 0; - while (std::fgets(buff, sizeof(buff), file) != nullptr) { + while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) { line_used = std::strlen(lineptr); - size_t buff_used = std::strlen(buff); + size_t buff_used = std::strlen(buff.data()); if (n <= buff_used + line_used) { - size_t new_n = n * 2; + const size_t new_n = n * 2; lineptr = static_cast(strict_realloc(lineptr, new_n)); n = new_n; } - std::memcpy(lineptr + line_used, buff, buff_used); + std::memcpy(lineptr + line_used, buff.data(), buff_used); line_used += buff_used; lineptr[line_used] = '\0'; @@ -693,15 +704,16 @@ ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) { #endif -ssize_t get_line_buffer(char*& lineptr, size_t& n, - const char* const csv_data_buffer, size_t csv_data_size, - size_t& curr_char) { +[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n, + const char* const csv_data_buffer, + size_t csv_data_size, + size_t& curr_char) { if (curr_char >= csv_data_size) { return -1; } if (lineptr == nullptr || n < get_line_initial_buffer_size) { - auto new_lineptr = static_cast( + auto* new_lineptr = static_cast( strict_realloc(lineptr, get_line_initial_buffer_size)); lineptr = new_lineptr; n = get_line_initial_buffer_size; @@ -710,7 +722,7 @@ ssize_t get_line_buffer(char*& lineptr, size_t& n, size_t line_used = 0; while (curr_char < csv_data_size) { if (line_used + 1 >= n) { - size_t new_n = n * 2; + const size_t new_n = n * 2; char* new_lineptr = static_cast(strict_realloc(lineptr, new_n)); @@ -726,19 +738,15 @@ ssize_t get_line_buffer(char*& lineptr, size_t& n, } } - if (line_used != 0) { - lineptr[line_used] = '\0'; - return line_used; - } - - return -1; + lineptr[line_used] = '\0'; + return line_used; } -std::tuple get_line(char*& buffer, size_t& buffer_size, - FILE* file, - const char* const csv_data_buffer, - size_t csv_data_size, size_t& curr_char) { - ssize_t ssize; +[[nodiscard]] inline std::tuple get_line( + char*& buffer, size_t& buffer_size, FILE* file, + const char* const csv_data_buffer, size_t csv_data_size, + size_t& curr_char) { + ssize_t ssize = 0; if (file) { ssize = get_line_file(buffer, buffer_size, file); curr_char += ssize; @@ -757,7 +765,7 @@ std::tuple get_line(char*& buffer, size_t& buffer_size, return {ssize, false}; } -} /* ss */ +} /* namespace ss */ namespace ss { @@ -1050,7 +1058,10 @@ private: template struct setup> : setup {}; -} /* ss */ +template +struct setup> : setup {}; + +} /* namespace ss */ namespace ss { @@ -1072,7 +1083,7 @@ private: public: using line_ptr_type = std::conditional_t; - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -1082,12 +1093,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool unterminated_quote() const { + [[nodiscard]] bool unterminated_quote() const { return unterminated_quote_; } @@ -1099,13 +1110,21 @@ public: return split_impl_select_delim(delimiter); } + [[nodiscard]] const split_data& get_split_data() const { + return split_data_; + } + + void clear_split_data() { + split_data_.clear(); + } + private: //////////////// // resplit //////////////// // number of characters the end of line is shifted backwards - size_t size_shifted() const { + [[nodiscard]] size_t size_shifted() const { return escaped_; } @@ -1128,7 +1147,7 @@ private: } const auto [old_line, old_begin] = *std::prev(split_data_.end()); - size_t begin = old_begin - old_line - 1; + const size_t begin = old_begin - old_line - 1; // safety measure if (new_size != -1 && static_cast(new_size) < begin) { @@ -1236,19 +1255,19 @@ private: // matching //////////////// - bool match(const char* const curr, char delim) { + [[nodiscard]] bool match(const char* const curr, char delim) { return *curr == delim; }; - bool match(const char* const curr, const std::string& delim) { + [[nodiscard]] bool match(const char* const curr, const std::string& delim) { return std::strncmp(curr, delim.c_str(), delim.size()) == 0; }; - size_t delimiter_size(char) { + [[nodiscard]] size_t delimiter_size(char) { return 1; } - size_t delimiter_size(const std::string& delim) { + [[nodiscard]] size_t delimiter_size(const std::string& delim) { return delim.size(); } @@ -1269,8 +1288,8 @@ private: } template - std::tuple match_delimiter(line_ptr_type begin, - const Delim& delim) { + [[nodiscard]] std::tuple match_delimiter(line_ptr_type begin, + const Delim& delim) { line_ptr_type end = begin; trim_right_if_enabled(end); @@ -1364,8 +1383,9 @@ private: trim_left_if_enabled(begin_); - for (done_ = false; !done_; read(delim)) - ; + for (done_ = false; !done_;) { + read(delim); + } return split_data_; } @@ -1504,7 +1524,6 @@ private: // members //////////////// -public: error_type error_{}; bool unterminated_quote_{false}; bool done_{true}; @@ -1521,7 +1540,7 @@ public: friend class converter; }; -} /* ss */ +} /* namespace ss */ #ifndef SSP_DISABLE_FAST_FLOAT @@ -1537,8 +1556,8 @@ namespace ss { #ifndef SSP_DISABLE_FAST_FLOAT template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, std::optional> +to_num(const char* const begin, const char* const end) { T ret; auto [ptr, ec] = fast_float::from_chars(begin, end, ret); @@ -1551,22 +1570,23 @@ std::enable_if_t, std::optional> to_num( #else template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, std::optional> +to_num(const char* const begin, const char* const end) { static_assert(!std::is_same_v, "Conversion to long double is disabled"); constexpr static auto buff_max = 64; - char short_buff[buff_max]; - size_t string_range = std::distance(begin, end); + std::array short_buff; + + const size_t string_range = std::distance(begin, end); std::string long_buff; - char* buff; + char* buff = nullptr; if (string_range > buff_max) { long_buff = std::string{begin, end}; buff = long_buff.data(); } else { - buff = short_buff; + buff = short_buff.data(); buff[string_range] = '\0'; std::copy_n(begin, string_range, buff); } @@ -1598,12 +1618,14 @@ struct numeric_wrapper { using type = T; numeric_wrapper() = default; - numeric_wrapper(numeric_wrapper&&) = default; + numeric_wrapper(numeric_wrapper&&) noexcept = default; numeric_wrapper(const numeric_wrapper&) = default; - numeric_wrapper& operator=(numeric_wrapper&&) = default; + numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default; numeric_wrapper& operator=(const numeric_wrapper&) = default; + ~numeric_wrapper() = default; + numeric_wrapper(T other) : value{other} { } @@ -1622,7 +1644,7 @@ using int8 = numeric_wrapper; using uint8 = numeric_wrapper; template -std::enable_if_t, std::optional> to_num( +[[nodiscard]] std::enable_if_t, std::optional> to_num( const char* const begin, const char* const end) { T ret; auto [ptr, ec] = std::from_chars(begin, end, ret); @@ -1634,8 +1656,9 @@ std::enable_if_t, std::optional> to_num( } template -std::enable_if_t, std::optional> to_num( - const char* const begin, const char* const end) { +[[nodiscard]] std::enable_if_t, + std::optional> +to_num(const char* const begin, const char* const end) { T ret; auto [ptr, ec] = std::from_chars(begin, end, ret.value); @@ -1654,14 +1677,15 @@ template struct unsupported_type { constexpr static bool value = false; }; -} /* namespace */ +} /* namespace errors */ template -std::enable_if_t && !std::is_floating_point_v && - !is_instance_of_v && - !is_instance_of_v && - !is_instance_of_v, - bool> +[[nodiscard]] std::enable_if_t && + !std::is_floating_point_v && + !is_instance_of_v && + !is_instance_of_v && + !is_instance_of_v, + bool> extract(const char*, const char*, T&) { static_assert(error::unsupported_type::value, "Conversion for given type is not defined, an " @@ -1669,9 +1693,10 @@ extract(const char*, const char*, T&) { } template -std::enable_if_t || std::is_floating_point_v || - is_instance_of_v, - bool> +[[nodiscard]] std::enable_if_t || + std::is_floating_point_v || + is_instance_of_v, + bool> extract(const char* begin, const char* end, T& value) { auto optional_value = to_num(begin, end); if (!optional_value) { @@ -1682,8 +1707,8 @@ extract(const char* begin, const char* end, T& value) { } template -std::enable_if_t, bool> extract( - const char* begin, const char* end, T& value) { +[[nodiscard]] std::enable_if_t, bool> +extract(const char* begin, const char* end, T& value) { typename T::value_type raw_value; if (extract(begin, end, raw_value)) { value = raw_value; @@ -1694,7 +1719,8 @@ std::enable_if_t, bool> extract( } template -bool extract_variant(const char* begin, const char* end, T& value) { +[[nodiscard]] bool extract_variant(const char* begin, const char* end, + T& value) { using IthType = std::variant_alternative_t>; IthType ithValue; if (extract(begin, end, ithValue)) { @@ -1707,7 +1733,7 @@ bool extract_variant(const char* begin, const char* end, T& value) { } template -std::enable_if_t, bool> extract( +[[nodiscard]] std::enable_if_t, bool> extract( const char* begin, const char* end, T& value) { return extract_variant(begin, end, value); } @@ -1717,7 +1743,8 @@ std::enable_if_t, bool> extract( //////////////// template <> -inline bool extract(const char* begin, const char* end, bool& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + bool& value) { if (end == begin + 1) { if (*begin == '1') { value = true; @@ -1727,10 +1754,13 @@ inline bool extract(const char* begin, const char* end, bool& value) { return false; } } else { - size_t size = end - begin; - if (size == 4 && std::strncmp(begin, "true", size) == 0) { + constexpr static auto true_size = 4; + constexpr static auto false_size = 5; + const size_t size = end - begin; + if (size == true_size && std::strncmp(begin, "true", size) == 0) { value = true; - } else if (size == 5 && std::strncmp(begin, "false", size) == 0) { + } else if (size == false_size && + std::strncmp(begin, "false", size) == 0) { value = false; } else { return false; @@ -1741,25 +1771,27 @@ inline bool extract(const char* begin, const char* end, bool& value) { } template <> -inline bool extract(const char* begin, const char* end, char& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + char& value) { value = *begin; return (end == begin + 1); } template <> -inline bool extract(const char* begin, const char* end, std::string& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + std::string& value) { value = std::string{begin, end}; return true; } template <> -inline bool extract(const char* begin, const char* end, - std::string_view& value) { +[[nodiscard]] inline bool extract(const char* begin, const char* end, + std::string_view& value) { value = std::string_view{begin, static_cast(end - begin)}; return true; } -} /* ss */ +} /* namespace ss */ namespace ss { INIT_HAS_METHOD(tied) @@ -1862,19 +1894,19 @@ public: // parses line with given delimiter, returns a 'T' object created with // extracted values of type 'Ts' template - T convert_object(line_ptr_type line, - const std::string& delim = default_delimiter) { + [[nodiscard]] T convert_object( + line_ptr_type line, const std::string& delim = default_delimiter) { 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( + [[nodiscard]] no_void_validator_tup_t convert( line_ptr_type line, const std::string& delim = default_delimiter) { split(line, delim); if (splitter_.valid()) { - return convert(splitter_.split_data_); + return convert(splitter_.get_split_data()); } else { handle_error_bad_split(); return {}; @@ -1883,13 +1915,13 @@ public: // parses already split line, returns 'T' object with extracted values template - T convert_object(const split_data& elems) { + [[nodiscard]] T convert_object(const split_data& elems) { return to_object(convert(elems)); } // same as above, but uses cached split line template - T convert_object() { + [[nodiscard]] T convert_object() { return to_object(convert()); } @@ -1898,7 +1930,8 @@ public: // 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_data& elems) { + [[nodiscard]] no_void_validator_tup_t convert( + const split_data& elems) { if constexpr (sizeof...(Ts) == 0 && is_instance_of_v) { return convert_impl(elems, static_cast(nullptr)); } else if constexpr (tied_class_v) { @@ -1914,11 +1947,11 @@ public: // same as above, but uses cached split line template - no_void_validator_tup_t convert() { - return convert(splitter_.split_data_); + [[nodiscard]] no_void_validator_tup_t convert() { + return convert(splitter_.get_split_data()); } - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -1928,12 +1961,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool unterminated_quote() const { + [[nodiscard]] bool unterminated_quote() const { return splitter_.unterminated_quote(); } @@ -1941,9 +1974,9 @@ public: // contain the beginnings and the ends of each column of the string const split_data& split(line_ptr_type line, const std::string& delim = default_delimiter) { - splitter_.split_data_.clear(); + splitter_.clear_split_data(); if (line[0] == '\0') { - return splitter_.split_data_; + return splitter_.get_split_data(); } return splitter_.split(line, delim); @@ -1959,7 +1992,7 @@ private: return splitter_.resplit(new_line, new_size, delim); } - size_t size_shifted() { + [[nodiscard]] size_t size_shifted() { return splitter_.size_shifted(); } @@ -1975,9 +2008,11 @@ private: } } - std::string error_sufix(const string_range msg, size_t pos) const { + [[nodiscard]] std::string error_sufix(const string_range msg, + size_t pos) const { + constexpr static auto reserve_size = 32; std::string error; - error.reserve(32); + error.reserve(reserve_size); error.append("at column ") .append(std::to_string(pos + 1)) .append(": \'") @@ -2102,7 +2137,8 @@ private: //////////////// template - no_void_validator_tup_t convert_impl(const split_data& elems) { + [[nodiscard]] no_void_validator_tup_t convert_impl( + const split_data& elems) { clear_error(); if (!splitter_.valid()) { @@ -2133,7 +2169,7 @@ private: } template - no_void_validator_tup_t> convert_impl( + [[nodiscard]] no_void_validator_tup_t> convert_impl( const split_data& elems, const std::tuple*) { return convert_impl(elems); } @@ -2142,11 +2178,11 @@ private: // column mapping //////////////// - bool columns_mapped() const { - return column_mappings_.size() != 0; + [[nodiscard]] bool columns_mapped() const { + return !column_mappings_.empty(); } - size_t column_position(size_t tuple_position) const { + [[nodiscard]] size_t column_position(size_t tuple_position) const { if (!columns_mapped()) { return tuple_position; } @@ -2156,7 +2192,7 @@ private: // assumes positions are valid and the vector is not empty void set_column_mapping(std::vector positions, size_t number_of_columns) { - column_mappings_ = positions; + column_mappings_ = std::move(positions); number_of_columns_ = number_of_columns; } @@ -2177,7 +2213,7 @@ private: } if constexpr (std::is_same_v) { - extract(msg.first, msg.second, dst); + static_cast(extract(msg.first, msg.second, dst)); return; } @@ -2223,7 +2259,8 @@ private: } template - no_void_validator_tup_t extract_tuple(const split_data& elems) { + [[nodiscard]] no_void_validator_tup_t extract_tuple( + const split_data& elems) { static_assert(!all_of_v, "at least one parameter must be non void"); no_void_validator_tup_t ret{}; @@ -2242,10 +2279,10 @@ private: friend class parser; std::vector column_mappings_; - size_t number_of_columns_; + size_t number_of_columns_{0}; }; -} /* ss */ +} /* namespace ss */ namespace ss { @@ -2268,10 +2305,12 @@ class parser { constexpr static bool ignore_empty = setup::ignore_empty; + using header_splitter = ss::splitter< + ss::filter_not_t>; + public: - parser(const std::string& file_name, - const std::string& delim = ss::default_delimiter) - : file_name_{file_name}, reader_{file_name_, delim} { + parser(std::string file_name, std::string delim = ss::default_delimiter) + : file_name_{std::move(file_name)}, reader_{file_name_, delim} { if (reader_.file_) { read_line(); if constexpr (ignore_header) { @@ -2287,7 +2326,7 @@ public: parser(const char* const csv_data_buffer, size_t csv_data_size, const std::string& delim = ss::default_delimiter) - : file_name_{"buffer line"}, + : file_name_{"CSV data buffer"}, reader_{csv_data_buffer, csv_data_size, delim} { if (csv_data_buffer) { read_line(); @@ -2302,14 +2341,15 @@ public: } } - parser(parser&& other) = default; - parser& operator=(parser&& other) = default; + parser(parser&& other) noexcept = default; + parser& operator=(parser&& other) noexcept = default; + ~parser() = default; parser() = delete; parser(const parser& other) = delete; parser& operator=(const parser& other) = delete; - bool valid() const { + [[nodiscard]] bool valid() const { if constexpr (string_error) { return error_.empty(); } else if constexpr (throw_on_error) { @@ -2319,12 +2359,12 @@ public: } } - const std::string& error_msg() const { + [[nodiscard]] const std::string& error_msg() const { assert_string_error_defined(); return error_; } - bool eof() const { + [[nodiscard]] bool eof() const { return eof_; } @@ -2333,23 +2373,21 @@ public: } template - T get_object() { + [[nodiscard]] T get_object() { return to_object(get_next()); } - size_t line() const { + [[nodiscard]] size_t line() const { return reader_.line_number_ > 0 ? reader_.line_number_ - 1 : reader_.line_number_; } - size_t position() const { + [[nodiscard]] size_t position() const { return reader_.chars_read_; } template - no_void_validator_tup_t get_next() { - std::optional error; - + [[nodiscard]] no_void_validator_tup_t get_next() { if (!eof_) { if constexpr (throw_on_error) { try { @@ -2398,20 +2436,49 @@ public: return value; } - bool field_exists(const std::string& field) { + [[nodiscard]] std::string raw_header() const { + assert_ignore_header_not_defined(); + return raw_header_; + } + + [[nodiscard]] std::vector header() { + assert_ignore_header_not_defined(); + clear_error(); + + header_splitter splitter; + std::string raw_header_copy = raw_header_; + + if (!strict_split(splitter, raw_header_copy)) { + return {}; + } + + std::vector split_header; + for (const auto& [begin, end] : splitter.get_split_data()) { + split_header.emplace_back(begin, end); + } + + return split_header; + } + + [[nodiscard]] bool field_exists(const std::string& field) { + assert_ignore_header_not_defined(); + clear_error(); + if (header_.empty()) { split_header_data(); } + if (!valid()) { + return false; + } + return header_index(field).has_value(); } template void use_fields(const Ts&... fields_args) { - if constexpr (ignore_header) { - handle_error_header_ignored(); - return; - } + assert_ignore_header_not_defined(); + clear_error(); if (header_.empty() && !eof()) { split_header_data(); @@ -2424,7 +2491,7 @@ public: auto fields = std::vector{fields_args...}; if (fields.empty()) { - handle_error_empty_mapping(); + handle_error_invalid_use_fields_argument(); return; } @@ -2472,13 +2539,17 @@ public: } iterator(const iterator& other) = default; - iterator(iterator&& other) = default; + iterator(iterator&& other) noexcept = default; + ~iterator() = default; - value& operator*() { + iterator& operator=(const iterator& other) = delete; + iterator& operator=(iterator&& other) noexcept = delete; + + [[nodiscard]] value& operator*() { return value_; } - value* operator->() { + [[nodiscard]] value* operator->() { return &value_; } @@ -2497,17 +2568,21 @@ public: return *this; } - iterator& operator++(int) { - return ++*this; + iterator operator++(int) { + auto result = *this; + ++*this; + return result; } - friend bool operator==(const iterator& lhs, const iterator& rhs) { + [[nodiscard]] friend bool operator==(const iterator& lhs, + const iterator& rhs) { return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) || (lhs.parser_ == rhs.parser_ && &lhs.value_ == &rhs.value_); } - friend bool operator!=(const iterator& lhs, const iterator& rhs) { + [[nodiscard]] friend bool operator!=(const iterator& lhs, + const iterator& rhs) { return !(lhs == rhs); } @@ -2519,11 +2594,11 @@ public: iterable(parser* parser) : parser_{parser} { } - iterator begin() { + [[nodiscard]] iterator begin() { return ++iterator{parser_}; } - iterator end() { + [[nodiscard]] iterator end() { return iterator{}; } @@ -2532,12 +2607,12 @@ public: }; template - auto iterate() { + [[nodiscard]] auto iterate() { return iterable{this}; } template - auto iterate_object() { + [[nodiscard]] auto iterate_object() { return iterable{this}; } @@ -2562,7 +2637,7 @@ public: Fun&& fun = none{}) { using Value = no_void_validator_tup_t; std::optional value; - try_convert_and_invoke(value, fun); + try_convert_and_invoke(value, std::forward(fun)); return composite_with(std::move(value)); } @@ -2571,11 +2646,11 @@ public: template composite> or_object(Fun&& fun = none{}) { std::optional value; - try_convert_and_invoke(value, fun); + try_convert_and_invoke(value, std::forward(fun)); return composite_with(std::move(value)); } - std::tuple values() { + [[nodiscard]] std::tuple values() { return values_; } @@ -2598,7 +2673,7 @@ public: private: template - composite composite_with(T&& new_value) { + [[nodiscard]] composite composite_with(T&& new_value) { auto merged_values = std::tuple_cat(std::move(values_), std::tuple{parser_.valid() @@ -2628,7 +2703,7 @@ public: } template - no_void_validator_tup_t try_same() { + [[nodiscard]] no_void_validator_tup_t try_same() { parser_.clear_error(); auto value = parser_.reader_.converter_.template convert(); @@ -2649,8 +2724,8 @@ public: // tries to convert a line and returns a composite which is // able to try additional conversions in case of failure template - composite>> try_next( - Fun&& fun = none{}) { + [[nodiscard]] composite>> + try_next(Fun&& fun = none{}) { assert_throw_on_error_not_defined(); using Ret = no_void_validator_tup_t; return try_invoke_and_make_composite< @@ -2660,7 +2735,7 @@ public: // identical to try_next but returns composite with object instead of a // tuple template - composite> try_object(Fun&& fun = none{}) { + [[nodiscard]] composite> try_object(Fun&& fun = none{}) { assert_throw_on_error_not_defined(); return try_invoke_and_make_composite< std::optional>(get_object(), std::forward(fun)); @@ -2679,7 +2754,8 @@ private: using Ret = decltype(try_invoke_impl(arg, std::forward(fun))); constexpr bool returns_void = std::is_same_v; if constexpr (!returns_void) { - if (!try_invoke_impl(arg, std::forward(fun))) { + if (!try_invoke_impl(std::forward(arg), + std::forward(fun))) { handle_error_failed_check(); } } else { @@ -2710,26 +2786,55 @@ private: } template - composite try_invoke_and_make_composite(T&& value, Fun&& fun) { + [[nodiscard]] composite try_invoke_and_make_composite(T&& value, + Fun&& fun) { if (valid()) { try_invoke(*value, std::forward(fun)); } - return {valid() ? std::move(value) : std::nullopt, *this}; + return {valid() ? std::forward(value) : std::nullopt, *this}; } //////////////// // header //////////////// + void assert_ignore_header_not_defined() const { + static_assert(!ignore_header, + "cannot use this method when 'ignore_header' is defined"); + } + + [[nodiscard]] bool strict_split(header_splitter& splitter, + std::string& header) { + if constexpr (throw_on_error) { + try { + splitter.split(header.data(), reader_.delim_); + } catch (const ss::exception& e) { + decorate_rethrow_invalid_header_split(e); + } + } else { + splitter.split(header.data(), reader_.delim_); + if (!splitter.valid()) { + handle_error_invalid_header_split(splitter); + return false; + } + } + + return true; + } + void split_header_data() { - ss::splitter splitter; + header_splitter splitter; std::string raw_header_copy = raw_header_; - splitter.split(raw_header_copy.data(), reader_.delim_); - for (const auto& [begin, end] : splitter.split_data_) { + + if (!strict_split(splitter, raw_header_copy)) { + return; + } + + for (const auto& [begin, end] : splitter.get_split_data()) { std::string field{begin, end}; if (std::find(header_.begin(), header_.end(), field) != header_.end()) { - handle_error_invalid_header(field); + handle_error_duplicate_header_field(field); header_.clear(); return; } @@ -2737,7 +2842,7 @@ private: } } - std::optional header_index(const std::string& field) { + [[nodiscard]] std::optional header_index(const std::string& field) { auto it = std::find(header_.begin(), header_.end(), field); if (it == header_.end()) { @@ -2760,7 +2865,7 @@ private: } void handle_error_failed_check() { - constexpr static auto error_msg = " failed check"; + constexpr static auto error_msg = ": failed check"; if constexpr (string_error) { error_.clear(); @@ -2773,7 +2878,7 @@ private: } void handle_error_null_buffer() { - constexpr static auto error_msg = " received null data buffer"; + constexpr static auto error_msg = ": received null data buffer"; if constexpr (string_error) { error_.clear(); @@ -2786,7 +2891,7 @@ private: } void handle_error_file_not_open() { - constexpr static auto error_msg = " could not be opened"; + constexpr static auto error_msg = ": could not be opened"; if constexpr (string_error) { error_.clear(); @@ -2799,7 +2904,7 @@ private: } void handle_error_eof_reached() { - constexpr static auto error_msg = " read on end of file"; + constexpr static auto error_msg = ": read on end of file"; if constexpr (string_error) { error_.clear(); @@ -2824,20 +2929,6 @@ private: } } - void handle_error_header_ignored() { - constexpr static auto error_msg = - ": the header row is ignored within the setup it cannot be used"; - - if constexpr (string_error) { - error_.clear(); - error_.append(file_name_).append(error_msg); - } else if constexpr (throw_on_error) { - throw ss::exception{file_name_ + error_msg}; - } else { - error_ = true; - } - } - void handle_error_invalid_field(const std::string& field) { constexpr static auto error_msg = ": header does not contain given field: "; @@ -2865,8 +2956,9 @@ private: } } - void handle_error_empty_mapping() { - constexpr static auto error_msg = "received empty mapping"; + void handle_error_invalid_use_fields_argument() { + constexpr static auto error_msg = + "received invalid argument for 'use_fields'"; if constexpr (string_error) { error_.clear(); @@ -2878,19 +2970,53 @@ private: } } - void handle_error_invalid_header(const std::string& field) { - constexpr static auto error_msg = "header contains duplicates: "; + void handle_error_invalid_header_field() { + constexpr static auto error_msg = ": header contains empty field"; if constexpr (string_error) { error_.clear(); - error_.append(error_msg).append(error_msg); + error_.append(file_name_).append(error_msg); } else if constexpr (throw_on_error) { - throw ss::exception{error_msg + field}; + throw ss::exception{file_name_ + error_msg}; } else { error_ = true; } } + void handle_error_duplicate_header_field(const std::string& field) { + constexpr static auto error_msg = ": header contains duplicate: "; + + if constexpr (string_error) { + error_.clear(); + error_.append(file_name_).append(error_msg).append(field); + } else if constexpr (throw_on_error) { + throw ss::exception{file_name_ + error_msg + field}; + } else { + error_ = true; + } + } + + void handle_error_invalid_header_split(const header_splitter& splitter) { + constexpr static auto error_msg = ": failed header parsing: "; + + if constexpr (string_error) { + error_.clear(); + error_.append(file_name_) + .append(error_msg) + .append(splitter.error_msg()); + } else { + error_ = true; + } + } + + void decorate_rethrow_invalid_header_split(const ss::exception& e) const { + static_assert(throw_on_error, + "throw_on_error needs to be enabled to use this method"); + throw ss::exception{std::string{file_name_} + .append(": failed header parsing: ") + .append(e.what())}; + } + void decorate_rethrow(const ss::exception& e) const { static_assert(throw_on_error, "throw_on_error needs to be enabled to use this method"); @@ -2910,17 +3036,18 @@ private: } struct reader { - reader(const std::string& file_name_, const std::string& delim) - : delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} { + reader(const std::string& file_name_, std::string delim) + : delim_{std::move(delim)}, + file_{std::fopen(file_name_.c_str(), "rb")} { } reader(const char* const buffer, size_t csv_data_size, - const std::string& delim) - : delim_{delim}, csv_data_buffer_{buffer}, + std::string delim) + : delim_{std::move(delim)}, csv_data_buffer_{buffer}, csv_data_size_{csv_data_size} { } - reader(reader&& other) + reader(reader&& other) noexcept : buffer_{other.buffer_}, next_line_buffer_{other.next_line_buffer_}, helper_buffer_{other.helper_buffer_}, @@ -2941,7 +3068,7 @@ private: other.file_ = nullptr; } - reader& operator=(reader&& other) { + reader& operator=(reader&& other) noexcept { if (this != &other) { buffer_ = other.buffer_; next_line_buffer_ = other.next_line_buffer_; @@ -2977,7 +3104,7 @@ private: std::free(helper_buffer_); if (file_) { - std::fclose(file_); + std::ignore = std::fclose(file_); } } @@ -2986,7 +3113,7 @@ private: reader& operator=(const reader& other) = delete; // read next line each time in order to set eof_ - bool read_next() { + [[nodiscard]] bool read_next() { next_line_converter_.clear_error(); size_t size = 0; while (size == 0) { @@ -3078,7 +3205,7 @@ private: std::swap(converter_, next_line_converter_); } - bool multiline_limit_reached(size_t& limit) { + [[nodiscard]] bool multiline_limit_reached(size_t& limit) { if constexpr (multiline::size > 0) { if (limit++ >= multiline::size) { next_line_converter_.handle_error_multiline_limit_reached(); @@ -3088,8 +3215,8 @@ private: return false; } - bool escaped_eol(size_t size) { - const char* curr; + [[nodiscard]] bool escaped_eol(size_t size) { + const char* curr = nullptr; for (curr = next_line_buffer_ + size - 1; curr >= next_line_buffer_ && setup::escape::match(*curr); @@ -3098,7 +3225,7 @@ private: return (next_line_buffer_ - curr + size) % 2 == 0; } - bool unterminated_quote() { + [[nodiscard]] bool unterminated_quote() { return next_line_converter_.unterminated_quote(); } @@ -3113,7 +3240,7 @@ private: } } - size_t remove_eol(char*& buffer, size_t ssize) { + [[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) { if (buffer[ssize - 1] != '\n') { crlf_ = false; return ssize; @@ -3135,7 +3262,7 @@ private: size_t& buffer_size, const char* const second, size_t second_size) { buffer_size = first_size + second_size + 3; - auto new_first = static_cast( + auto* new_first = static_cast( strict_realloc(static_cast(first), buffer_size)); first = new_first; @@ -3143,8 +3270,9 @@ private: first_size += second_size; } - bool append_next_line_to_buffer(char*& buffer, size_t& line_size, - size_t buffer_size) { + [[nodiscard]] bool append_next_line_to_buffer(char*& buffer, + size_t& line_size, + size_t buffer_size) { undo_remove_eol(buffer, line_size, buffer_size); chars_read_ = curr_char_; @@ -3163,8 +3291,8 @@ private: return true; } - std::string get_buffer() { - return std::string{next_line_buffer_, next_line_buffer_size_}; + [[nodiscard]] std::string get_buffer() { + return std::string{next_line_buffer_, next_line_size_}; } //////////////// @@ -3207,4 +3335,4 @@ private: bool eof_{false}; }; -} /* ss */ +} /* namespace ss */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index be399a1..6fd368c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,10 +33,11 @@ set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src") enable_testing() foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2 - test_parser1_3 test_parser1_4 test_converter - test_extractions test_parser2_1 test_parser2_2 - test_parser2_3 test_parser2_4 test_parser2_5 - test_parser2_6 test_extractions_without_fast_float) + test_parser1_3 test_parser1_4 test_parser1_5 + test_converter test_extractions test_parser2_1 + test_parser2_2 test_parser2_3 test_parser2_4 + test_parser2_5 test_parser2_6 + test_extractions_without_fast_float) add_executable("${name}" "${name}.cpp") target_link_libraries("${name}" PRIVATE ssp::ssp fast_float doctest::doctest) diff --git a/test/meson.build b/test/meson.build index f4e6639..6be0f1f 100644 --- a/test/meson.build +++ b/test/meson.build @@ -6,6 +6,7 @@ tests = [ 'parser1_2', 'parser1_3', 'parser1_4', + 'parser1_5', 'splitter', 'converter', 'extractions', diff --git a/test/test_converter.cpp b/test/test_converter.cpp index 659e78f..e4e0a8c 100644 --- a/test/test_converter.cpp +++ b/test/test_converter.cpp @@ -1,17 +1,17 @@ #include "test_helpers.hpp" -#include #include TEST_CASE("converter test split") { ss::converter c; for (const auto& [s, expected, delim] : // clang-format off - {std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","), - {"", {}, " "}, - {" x x x x | x ", {" x x x x ", " x "}, "|"}, - {"a::b::c::d", {"a", "b", "c", "d"}, "::"}, - {"x\t-\ty", {"x", "y"}, "\t-\t"}, - {"x", {"x"}, ","}} // clang-format on + {std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","), + {"", {}, " "}, + {" x x x x | x ", {" x x x x ", " x "}, "|"}, + {"a::b::c::d", {"a", "b", "c", "d"}, "::"}, + {"x\t-\ty", {"x", "y"}, "\t-\t"}, + {"x", {"x"}, ","}} + // clang-format on ) { auto split = c.split(s, delim); CHECK_EQ(split.size(), expected.size()); @@ -278,37 +278,38 @@ TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int, TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) { ss::converter c; - c.convert(""); + std::ignore = c.convert(""); REQUIRE_FALSE(c.valid()); - c.convert("1", ""); + std::ignore = c.convert("1", ""); REQUIRE_FALSE(c.valid()); - c.convert("10", ""); + std::ignore = c.convert("10", ""); REQUIRE_FALSE(c.valid()); - c.convert(""); + std::ignore = c.convert(""); REQUIRE_FALSE(c.valid()); - c.convert(",junk"); + std::ignore = c.convert(",junk"); REQUIRE_FALSE(c.valid()); - c.convert("junk,"); + std::ignore = c.convert("junk,"); REQUIRE_FALSE(c.valid()); - c.convert("x"); + std::ignore = c.convert("x"); REQUIRE_FALSE(c.valid()); - c.convert("x"); + std::ignore = c.convert("x"); REQUIRE_FALSE(c.valid()); - c.convert("x,junk"); + std::ignore = c.convert("x,junk"); REQUIRE_FALSE(c.valid()); - c.convert("junk,x"); + std::ignore = c.convert("junk,x"); REQUIRE_FALSE(c.valid()); - c.convert, double>("junk;.5.5;6", ";"); + std::ignore = + c.convert, double>("junk;.5.5;6", ";"); REQUIRE_FALSE(c.valid()); } @@ -316,34 +317,36 @@ TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int, ss::uint8) { ss::converter c; - REQUIRE_EXCEPTION(c.convert("")); - REQUIRE_EXCEPTION(c.convert("1", "")); - REQUIRE_EXCEPTION(c.convert("10", "")); - REQUIRE_EXCEPTION(c.convert("")); - REQUIRE_EXCEPTION(c.convert(",junk")); - REQUIRE_EXCEPTION(c.convert("junk,")); - REQUIRE_EXCEPTION(c.convert("x")); - REQUIRE_EXCEPTION(c.convert("x")); - REQUIRE_EXCEPTION(c.convert("x,junk")); - REQUIRE_EXCEPTION(c.convert("junk,x")); + REQUIRE_EXCEPTION(std::ignore = c.convert("")); + REQUIRE_EXCEPTION(std::ignore = c.convert("1", "")); + REQUIRE_EXCEPTION(std::ignore = c.convert("10", "")); + REQUIRE_EXCEPTION(std::ignore = c.convert("")); + REQUIRE_EXCEPTION(std::ignore = c.convert(",junk")); + REQUIRE_EXCEPTION(std::ignore = c.convert("junk,")); + REQUIRE_EXCEPTION(std::ignore = c.convert("x")); + REQUIRE_EXCEPTION(std::ignore = c.convert("x")); + REQUIRE_EXCEPTION(std::ignore = c.convert("x,junk")); + REQUIRE_EXCEPTION(std::ignore = c.convert("junk,x")); REQUIRE_EXCEPTION( - c.convert, double>("junk;.5.5;6", ";")); + std::ignore = + c.convert, double>("junk;.5.5;6", + ";")); } TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int, ss::uint8) { ss::converter c; - c.convert>("0"); + std::ignore = c.convert>("0"); REQUIRE_FALSE(c.valid()); - c.convert>("1"); + std::ignore = c.convert>("1"); REQUIRE_FALSE(c.valid()); - c.convert>("junk,c,1"); + std::ignore = c.convert>("junk,c,1"); REQUIRE_FALSE(c.valid()); - c.convert, char>("1,c"); + std::ignore = c.convert, char>("1,c"); REQUIRE_FALSE(c.valid()); { T tup = c.convert>("3"); @@ -367,10 +370,11 @@ TEST_CASE_TEMPLATE( ss::uint8) { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("0")); - REQUIRE_EXCEPTION(c.convert>("1")); - REQUIRE_EXCEPTION(c.convert>("junk,c,1")); - REQUIRE_EXCEPTION(c.convert, char>("1,c")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("0")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("1")); + REQUIRE_EXCEPTION( + std::ignore = c.convert>("junk,c,1")); + REQUIRE_EXCEPTION(std::ignore = c.convert, char>("1,c")); try { { @@ -393,13 +397,13 @@ TEST_CASE_TEMPLATE( TEST_CASE("converter test ss:nx restriction (none except)") { ss::converter c; - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("c,3"); + std::ignore = c.convert>("c,3"); REQUIRE_FALSE(c.valid()); - c.convert, char>("3,c"); + std::ignore = c.convert, char>("3,c"); REQUIRE_FALSE(c.valid()); { @@ -427,9 +431,10 @@ TEST_CASE("converter test ss:nx restriction (none except)") { TEST_CASE("converter test ss:nx restriction (none except) with exceptions") { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("c,3")); - REQUIRE_EXCEPTION(c.convert, char>("3,c")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = + c.convert>("c,3")); + REQUIRE_EXCEPTION(std::ignore = c.convert, char>("3,c")); try { { @@ -461,13 +466,13 @@ TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int, ss::uint8) { ss::converter c; - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("c,3"); + std::ignore = c.convert>("c,3"); REQUIRE_FALSE(c.valid()); - c.convert, char>("3,c"); + std::ignore = c.convert, char>("3,c"); REQUIRE_FALSE(c.valid()); { @@ -497,9 +502,9 @@ TEST_CASE_TEMPLATE( ss::uint8) { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("c,3")); - REQUIRE_EXCEPTION(c.convert, char>("3,c")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("c,3")); + REQUIRE_EXCEPTION(std::ignore = c.convert, char>("3,c")); try { { @@ -530,16 +535,16 @@ TEST_CASE_TEMPLATE( TEST_CASE("converter test ss:oor restriction (out of range)") { ss::converter c; - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("2"); + std::ignore = c.convert>("2"); REQUIRE_FALSE(c.valid()); - c.convert, void>("c,1,junk"); + std::ignore = c.convert, void>("c,1,junk"); REQUIRE_FALSE(c.valid()); - c.convert, char>("1,c"); + std::ignore = c.convert, char>("1,c"); REQUIRE_FALSE(c.valid()); { @@ -564,10 +569,12 @@ TEST_CASE("converter test ss:oor restriction (out of range)") { TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("2")); - REQUIRE_EXCEPTION(c.convert, void>("c,1,junk")); - REQUIRE_EXCEPTION(c.convert, char>("1,c")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("2")); + REQUIRE_EXCEPTION( + std::ignore = c.convert, void>("c,1,junk")); + REQUIRE_EXCEPTION(std::ignore = + c.convert, char>("1,c")); try { { @@ -608,19 +615,19 @@ inline bool ss::extract(const char* begin, const char* end, TEST_CASE("converter test ss:ne restriction (not empty)") { ss::converter c; - c.convert>(""); + std::ignore = c.convert>(""); REQUIRE_FALSE(c.valid()); - c.convert>("3,"); + std::ignore = c.convert>("3,"); REQUIRE_FALSE(c.valid()); - c.convert, int>(",3"); + std::ignore = c.convert, int>(",3"); REQUIRE_FALSE(c.valid()); - c.convert, int>("junk,,3"); + std::ignore = c.convert, int>("junk,,3"); REQUIRE_FALSE(c.valid()); - c.convert>>(""); + std::ignore = c.convert>>(""); REQUIRE_FALSE(c.valid()); { @@ -643,11 +650,12 @@ TEST_CASE("converter test ss:ne restriction (not empty)") { TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("")); - REQUIRE_EXCEPTION(c.convert>("3,")); - REQUIRE_EXCEPTION(c.convert, int>(",3")); - REQUIRE_EXCEPTION(c.convert, int>("junk,,3")); - REQUIRE_EXCEPTION(c.convert>>("")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3,")); + REQUIRE_EXCEPTION(std::ignore = c.convert, int>(",3")); + REQUIRE_EXCEPTION(std::ignore = + c.convert, int>("junk,,3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>>("")); try { { @@ -675,22 +683,22 @@ TEST_CASE( "converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") { ss::converter c; - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); - c.convert>("3"); + std::ignore = c.convert>("3"); REQUIRE_FALSE(c.valid()); { @@ -734,12 +742,12 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) " "with exception") { ss::converter c; - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("3")); - REQUIRE_EXCEPTION(c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); + REQUIRE_EXCEPTION(std::ignore = c.convert>("3")); try { { @@ -784,14 +792,14 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) " TEST_CASE("converter test error mode") { ss::converter c; - c.convert("junk"); + std::ignore = c.convert("junk"); CHECK_FALSE(c.valid()); CHECK_FALSE(c.error_msg().empty()); } TEST_CASE("converter test throw on error mode") { ss::converter c; - REQUIRE_EXCEPTION(c.convert("junk")); + REQUIRE_EXCEPTION(std::ignore = c.convert("junk")); } TEST_CASE("converter test converter with quotes spacing and escaping") { @@ -908,7 +916,7 @@ TEST_CASE("converter test invalid split conversions") { { // mismatched quote - c.convert( + std::ignore = c.convert( buff(R"( "just , some , "12.3","a" )")); CHECK_FALSE(c.valid()); CHECK_FALSE(c.unterminated_quote()); @@ -917,7 +925,7 @@ TEST_CASE("converter test invalid split conversions") { { // unterminated quote - c.convert( + std::ignore = c.convert( buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")); CHECK_FALSE(c.valid()); CHECK(c.unterminated_quote()); @@ -926,7 +934,7 @@ TEST_CASE("converter test invalid split conversions") { { // unterminated escape - c.convert( + std::ignore = c.convert( buff(R"(just,some,2,strings\)")); CHECK_FALSE(c.valid()); CHECK_FALSE(c.unterminated_quote()); @@ -935,7 +943,7 @@ TEST_CASE("converter test invalid split conversions") { { // unterminated escape while quoting - c.convert( + std::ignore = c.convert( buff(R"(just,some,2,"strings\)")); CHECK_FALSE(c.valid()); CHECK_FALSE(c.unterminated_quote()); @@ -944,7 +952,7 @@ TEST_CASE("converter test invalid split conversions") { { // unterminated escaped quote - c.convert( + std::ignore = c.convert( buff(R"(just,some,2,"strings\")")); CHECK_FALSE(c.valid()); CHECK(c.unterminated_quote()); @@ -958,27 +966,32 @@ TEST_CASE("converter test invalid split conversions with exceptions") { c; // mismatched quote - REQUIRE_EXCEPTION(c.convert( - buff(R"( "just , some , "12.3","a" )"))); + REQUIRE_EXCEPTION(std::ignore = + c.convert( + buff(R"( "just , some , "12.3","a" )"))); CHECK_FALSE(c.unterminated_quote()); // unterminated quote - REQUIRE_EXCEPTION(c.convert( - buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"))); + REQUIRE_EXCEPTION( + std::ignore = c.convert( + buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"))); CHECK(c.unterminated_quote()); // unterminated escape - REQUIRE_EXCEPTION(c.convert( - buff(R"(just,some,2,strings\)"))); + REQUIRE_EXCEPTION( + std::ignore = c.convert( + buff(R"(just,some,2,strings\)"))); CHECK_FALSE(c.unterminated_quote()); // unterminated escape while quoting - REQUIRE_EXCEPTION(c.convert( - buff(R"(just,some,2,"strings\)"))); + REQUIRE_EXCEPTION( + std::ignore = c.convert( + buff(R"(just,some,2,"strings\)"))); CHECK_FALSE(c.unterminated_quote()); // unterminated escaped quote - REQUIRE_EXCEPTION(c.convert( - buff(R"(just,some,2,"strings\")"))); + REQUIRE_EXCEPTION( + std::ignore = c.convert( + buff(R"(just,some,2,"strings\")"))); CHECK(c.unterminated_quote()); } diff --git a/test/test_extractions.cpp b/test/test_extractions.cpp index 88ec317..6e44196 100644 --- a/test/test_extractions.cpp +++ b/test/test_extractions.cpp @@ -23,7 +23,7 @@ struct is_unsigned : public std::is_unsigned {}; template <> struct is_unsigned : public std::true_type {}; -} /* namespace */ +} /* anonymous namespace */ static_assert(is_signed::value); static_assert(is_unsigned::value); diff --git a/test/test_extractions_without_fast_float.cpp b/test/test_extractions_without_fast_float.cpp index 2c9a984..7add461 100644 --- a/test/test_extractions_without_fast_float.cpp +++ b/test/test_extractions_without_fast_float.cpp @@ -1,5 +1,4 @@ #include "test_helpers.hpp" -#include #define SSP_DISABLE_FAST_FLOAT #include diff --git a/test/test_helpers.hpp b/test/test_helpers.hpp index 188e731..6841d25 100644 --- a/test/test_helpers.hpp +++ b/test/test_helpers.hpp @@ -19,7 +19,7 @@ namespace ss { template class parser; -} /* ss */ +} /* namespace ss */ namespace { @@ -145,6 +145,17 @@ struct unique_file_name { CHECK_FALSE(std::string{e.what()}.empty()); \ } +#define CHECK_EQ_ARRAY(first, second) \ + { \ + const auto& first_ = (first); \ + const auto& second_ = (second); \ + CHECK_EQ(first_.size(), second_.size()); \ + for (size_t i_ = 0; i_ < std::min(first_.size(), second_.size()); \ + ++i_) { \ + CHECK_EQ(first_[i_], second_[i_]); \ + } \ + } + template [[maybe_unused]] std::vector> vector_combinations( const std::vector& v, size_t n) { @@ -166,6 +177,22 @@ template return ret; } +[[maybe_unused]] std::string merge_header( + const std::vector& header, + const std::string& delimiter = ss::default_delimiter) { + std::string s; + if (!header.empty()) { + for (const auto& i : header) { + s.append(i); + s.append(delimiter); + } + for (size_t i = 0; i < delimiter.size(); ++i) { + s.pop_back(); + } + } + return s; +}; + [[maybe_unused]] std::string make_buffer(const std::string& file_name) { std::ifstream in{file_name, std::ios::binary}; std::string tmp; @@ -185,6 +212,7 @@ template } }; + // Evade small string optimization out.reserve(sizeof(out) + 1); copy_if_whitespaces(); @@ -224,4 +252,4 @@ make_parser(const std::string& file_name, return make_parser_impl(file_name, delim); } -} /* namespace */ +} /* anonymous namespace */ diff --git a/test/test_parser1.hpp b/test/test_parser1.hpp index 90267c8..c40bdc3 100644 --- a/test/test_parser1.hpp +++ b/test/test_parser1.hpp @@ -12,8 +12,9 @@ #include namespace { -[[maybe_unused]] void replace_all(std::string& s, const std::string& from, - const std::string& to) { +#ifdef _WIN32 +void replace_all(std::string& s, const std::string& from, + const std::string& to) { if (from.empty()) return; size_t start_pos = 0; while ((start_pos = s.find(from, start_pos)) != std::string::npos) { @@ -21,6 +22,7 @@ namespace { start_pos += to.length(); } } +#endif template void expect_error_on_command(ss::parser& p, @@ -28,6 +30,7 @@ void expect_error_on_command(ss::parser& p, if (ss::setup::throw_on_error) { try { command(); + FAIL("expected exception"); } catch (const std::exception& e) { CHECK_FALSE(std::string{e.what()}.empty()); } @@ -55,7 +58,7 @@ struct X { double d; std::string s; - std::string to_string() const { + [[nodiscard]] std::string to_string() const { if (s == empty) { return ""; } @@ -66,14 +69,15 @@ struct X { .append(delim) .append(s); } - auto tied() const { + + [[nodiscard]] auto tied() const { return std::tie(i, d, s); } }; template -std::enable_if_t, bool> operator==(const T& lhs, - const T& rhs) { +[[nodiscard]] std::enable_if_t, bool> operator==( + const T& lhs, const T& rhs) { return lhs.tied() == rhs.tied(); } @@ -109,4 +113,4 @@ static void make_and_write(const std::string& file_name, } } -} /* namespace */ +} /* anonymous namespace */ diff --git a/test/test_parser1_1.cpp b/test/test_parser1_1.cpp index 650393b..6338ee8 100644 --- a/test/test_parser1_1.cpp +++ b/test/test_parser1_1.cpp @@ -57,7 +57,7 @@ struct Y { .append(s3); } - auto tied() const { + [[nodiscard]] auto tied() const { return std::tie(s1, s2, s3); } }; @@ -115,7 +115,8 @@ TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) { CHECK_EQ(p.line(), expected_line); while (!p.eof()) { - auto _ = p.template get_next(); + std::ignore = + p.template get_next(); ++expected_line; CHECK_EQ(p.line(), expected_line); } diff --git a/test/test_parser1_2.cpp b/test/test_parser1_2.cpp index eaf9516..dffcf17 100644 --- a/test/test_parser1_2.cpp +++ b/test/test_parser1_2.cpp @@ -51,14 +51,16 @@ TEST_CASE_TEMPLATE("test moving of parsed composite values", T, // to compile is enough return; auto [p, _] = make_parser("", ""); - p.template try_next() - .template or_else( - [](auto&&) {}) - .template or_else([](auto&) {}) - .template or_else([](auto&&) {}) - .template or_object([](auto&&) {}) - .template or_else>( - [](auto&, auto&, auto&) {}); + std::ignore = + p.template try_next() + .template or_else( + [](auto&&) {}) + .template or_else([](auto&) {}) + .template or_else([](auto&&) {}) + .template or_object( + [](auto&&) {}) + .template or_else>( + [](auto&, auto&, auto&) {}); } TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type, @@ -73,7 +75,7 @@ TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type, auto [p, _] = make_parser(f.name, ","); REQUIRE_FALSE(p.eof()); - p.template get_next(); + std::ignore = p.template get_next(); CHECK_FALSE(p.valid()); CHECK_FALSE(p.error_msg().empty()); } @@ -92,7 +94,7 @@ TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type, REQUIRE_FALSE(p.eof()); try { - p.template get_next(); + std::ignore = p.template get_next(); FAIL("Expected exception..."); } catch (const std::exception& e) { CHECK_FALSE(std::string{e.what()}.empty()); @@ -148,7 +150,8 @@ TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) { make_parser>(f.name, ","); while (!p.eof()) { auto command = [&p_no_multiline = p_no_multiline] { - p_no_multiline.template get_next(); + std::ignore = + p_no_multiline.template get_next(); }; expect_error_on_command(p_no_multiline, command); } diff --git a/test/test_parser1_3.cpp b/test/test_parser1_3.cpp index e1f9b6e..69ddd8e 100644 --- a/test/test_parser1_3.cpp +++ b/test/test_parser1_3.cpp @@ -83,7 +83,7 @@ void test_unterminated_line(const std::vector& lines, size_t line = 0; while (!p.eof()) { auto command = [&p = p] { - p.template get_next(); + std::ignore = p.template get_next(); }; if (line == bad_line) { diff --git a/test/test_parser1_4.cpp b/test/test_parser1_4.cpp index 993e74b..e6e6966 100644 --- a/test/test_parser1_4.cpp +++ b/test/test_parser1_4.cpp @@ -9,6 +9,7 @@ struct has_type> template static void test_fields(const std::string file_name, const std::vector& data, + const std::vector& header, const std::vector& fields) { constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; @@ -17,9 +18,14 @@ static void test_fields(const std::string file_name, const std::vector& data, auto [p, _] = make_parser(file_name, ","); CHECK_FALSE(p.field_exists("Unknown")); p.use_fields(fields); + + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); std::vector i; for (const auto& a : p.template iterate()) { + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); i.push_back(a); } @@ -40,12 +46,12 @@ static void test_fields(const std::string file_name, const std::vector& data, TEST_CASE_TEMPLATE("test various cases with header", T, ParserOptionCombinations) { unique_file_name f{"various_cases_with_header"}; + using str = std::string; + constexpr static auto Int = "Int"; constexpr static auto Dbl = "Double"; constexpr static auto Str = "String"; - using str = std::string; - - std::vector header{Int, Dbl, Str}; + const std::vector header{Int, Dbl, Str}; std::vector data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"}, {7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}}; @@ -59,6 +65,8 @@ TEST_CASE_TEMPLATE("test various cases with header", T, std::vector i; for (const auto& a : p.iterate()) { + CHECK_EQ(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); i.emplace_back(ss::to_object(a)); } @@ -71,46 +79,22 @@ TEST_CASE_TEMPLATE("test various cases with header", T, p.ignore_next(); for (const auto& a : p.iterate()) { + CHECK_EQ(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); i.emplace_back(ss::to_object(a)); } CHECK_EQ(i, data); } - { - ss::parser p{f.name, ","}; - std::vector i; - - for (const auto& a : p.iterate()) { - i.emplace_back(ss::to_object(a)); - } - - CHECK_EQ(i, data); - } - - { - ss::parser p{f.name, ","}; - p.use_fields(Int, Dbl, Str); - CHECK_FALSE(p.valid()); - } - - { - ss::parser p{f.name, ","}; - CHECK_FALSE(p.field_exists("Unknown")); - - p.use_fields(Int, "Unknown"); - CHECK_FALSE(p.valid()); - } - - { - ss::parser p{f.name, ","}; - p.use_fields(Int, Int); - CHECK_FALSE(p.valid()); - } - { ss::parser p{f.name, ","}; + CHECK_EQ(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); + p.use_fields(Int, Dbl); + CHECK_EQ(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); { auto [int_, double_] = p.get_next(); @@ -119,6 +103,8 @@ TEST_CASE_TEMPLATE("test various cases with header", T, } p.use_fields(Dbl, Int); + CHECK_EQ(header, p.header()); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); { auto [double_, int_] = p.get_next(); @@ -163,25 +149,25 @@ TEST_CASE_TEMPLATE("test various cases with header", T, template_params.append(type) arg_params.append(header[type]) call = 'testFields<' + ', '.join(template_params) + \ - '>(o, d, {' + ', '.join(arg_params) + '});' + '>(o, d, header, {' + ', '.join(arg_params) + '});' print(call) */ - test_fields(o, d, {Str}); - test_fields(o, d, {Int}); - test_fields(o, d, {Dbl}); - test_fields(o, d, {Str, Int}); - test_fields(o, d, {Str, Dbl}); - test_fields(o, d, {Int, Str}); - test_fields(o, d, {Int, Dbl}); - test_fields(o, d, {Dbl, Str}); - test_fields(o, d, {Dbl, Int}); - test_fields(o, d, {Str, Int, Dbl}); - test_fields(o, d, {Str, Dbl, Int}); - test_fields(o, d, {Int, Str, Dbl}); - test_fields(o, d, {Int, Dbl, Str}); - test_fields(o, d, {Dbl, Str, Int}); - test_fields(o, d, {Dbl, Int, Str}); + test_fields(o, d, header, {Str}); + test_fields(o, d, header, {Int}); + test_fields(o, d, header, {Dbl}); + test_fields(o, d, header, {Str, Int}); + test_fields(o, d, header, {Str, Dbl}); + test_fields(o, d, header, {Int, Str}); + test_fields(o, d, header, {Int, Dbl}); + test_fields(o, d, header, {Dbl, Str}); + test_fields(o, d, header, {Dbl, Int}); + test_fields(o, d, header, {Str, Int, Dbl}); + test_fields(o, d, header, {Str, Dbl, Int}); + test_fields(o, d, header, {Int, Str, Dbl}); + test_fields(o, d, header, {Int, Dbl, Str}); + test_fields(o, d, header, {Dbl, Str, Int}); + test_fields(o, d, header, {Dbl, Int, Str}); } template @@ -190,6 +176,18 @@ void test_invalid_fields(const std::vector& lines, constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; + auto check_header = [&lines](auto& p) { + if (lines.empty()) { + CHECK_EQ(p.header().size(), 1); + CHECK_EQ(p.header().at(0), ""); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); + } else { + CHECK_EQ(lines[0], merge_header(p.header())); + CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); + } + CHECK(p.valid()); + }; + unique_file_name f{"invalid_fields"}; { std::ofstream out{f.name}; @@ -203,6 +201,7 @@ void test_invalid_fields(const std::vector& lines, auto [p, _] = make_parser(f.name, ","); auto command = [&p = p] { p.use_fields(); }; expect_error_on_command(p, command); + check_header(p); } { @@ -210,6 +209,7 @@ void test_invalid_fields(const std::vector& lines, auto [p, _] = make_parser(f.name, ","); auto command = [&p = p] { p.use_fields("Unknown"); }; expect_error_on_command(p, command); + check_header(p); } { @@ -221,6 +221,7 @@ void test_invalid_fields(const std::vector& lines, if (!fields.empty()) { expect_error_on_command(p, command); } + check_header(p); } { @@ -228,17 +229,21 @@ void test_invalid_fields(const std::vector& lines, auto [p, _] = make_parser(f.name, ","); auto command = [&p = p, &fields = fields] { p.use_fields(fields.at(0)); - p.template get_next(); + std::ignore = p.template get_next(); }; + check_header(p); + if (!fields.empty()) { expect_error_on_command(p, command); } + check_header(p); } { // Invalid header auto [p, _] = make_parser(f.name, ","); auto command = [&p = p, &fields = fields] { p.use_fields(fields); }; + check_header(p); if (!fields.empty()) { // Pass if there are no duplicates, fail otherwise @@ -255,10 +260,11 @@ void test_invalid_fields(const std::vector& lines, } } } + check_header(p); } } -TEST_CASE_TEMPLATE("test invalid fheader fields usage", T, +TEST_CASE_TEMPLATE("test invalid header fields usage", T, ParserOptionCombinations) { test_invalid_fields({}, {}); @@ -289,7 +295,7 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T, constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; - unique_file_name f{"invalid rows with header"}; + unique_file_name f{"invalid_rows_with_header"}; { std::ofstream out{f.name}; out << "Int,String,Double" << std::endl; @@ -301,8 +307,12 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T, out << "six,line6,10.11" << std::endl; } + std::vector header = {"Int", "String", "Double"}; + { auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); p.use_fields("Int", "String", "Double"); using data = std::tuple; @@ -325,10 +335,14 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T, {3, "line3", 67.8}, {5, "line5", 9.10}}; CHECK_EQ(i, expected); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); } { auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); p.use_fields("Double", "Int"); using data = std::tuple; @@ -349,10 +363,14 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T, std::vector expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}}; CHECK_EQ(i, expected); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); } { auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); p.use_fields("String", "Double"); using data = std::tuple; @@ -376,96 +394,7 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T, {"line5", 9.10}, {"line6", 10.11}}; CHECK_EQ(i, expected); + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(merge_header(p.header()), p.raw_header()); } } - -template -void test_ignore_empty(const std::vector& data) { - constexpr auto buffer_mode = T::BufferMode::value; - using ErrorMode = typename T::ErrorMode; - - unique_file_name f{"ignore_empty"}; - make_and_write(f.name, data); - - std::vector expected; - for (const auto& d : data) { - if (d.s != X::empty) { - expected.push_back(d); - } - } - - { - auto [p, _] = - make_parser(f.name, ","); - - std::vector i; - for (const auto& a : p.template iterate()) { - i.push_back(a); - } - - CHECK_EQ(i, expected); - } - - { - auto [p, _] = make_parser(f.name, ","); - std::vector i; - size_t n = 0; - while (!p.eof()) { - try { - ++n; - const auto& a = p.template get_next(); - if (data.at(n - 1).s == X::empty) { - CHECK_FALSE(p.valid()); - continue; - } - i.push_back(a); - } catch (...) { - CHECK_EQ(data.at(n - 1).s, X::empty); - } - } - - CHECK_EQ(i, expected); - } -} - -TEST_CASE_TEMPLATE("test various cases with empty lines", T, - ParserOptionCombinations) { - test_ignore_empty( - {{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}}); - - test_ignore_empty( - {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}}); - - test_ignore_empty( - {{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}}); - - test_ignore_empty( - {{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}}); - - test_ignore_empty( - {{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}}); - - test_ignore_empty( - {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}}); - - test_ignore_empty( - {{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}}); - - test_ignore_empty( - {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}}); - - test_ignore_empty({{1, 2, X::empty}, - {3, 4, X::empty}, - {9, 10, X::empty}, - {11, 12, X::empty}}); - - test_ignore_empty( - {{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}}); - - test_ignore_empty( - {{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}}); - - test_ignore_empty({{11, 12, X::empty}}); - - test_ignore_empty({}); -} diff --git a/test/test_parser1_5.cpp b/test/test_parser1_5.cpp new file mode 100644 index 0000000..e56a3a2 --- /dev/null +++ b/test/test_parser1_5.cpp @@ -0,0 +1,301 @@ +#include "test_parser1.hpp" + +TEST_CASE_TEMPLATE("test empty fields header", T, ParserOptionCombinations) { + constexpr auto buffer_mode = T::BufferMode::value; + using ErrorMode = typename T::ErrorMode; + + unique_file_name f{"empty_fields_header"}; + + // Empty header + { + std::ofstream out{f.name}; + out << "" << std::endl; + out << "1" << std::endl; + } + + { + std::vector expected_header = {""}; + auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(expected_header, p.header()); + CHECK_EQ("", p.raw_header()); + CHECK(p.valid()); + } + + // All empty header fields + { + std::ofstream out{f.name}; + out << ",," << std::endl; + out << "1,2,3" << std::endl; + } + + { + std::vector expected_header = {"", "", ""}; + auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(expected_header, p.header()); + CHECK_EQ(",,", p.raw_header()); + CHECK(p.valid()); + + auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); }; + expect_error_on_command(p, command1); + + auto command2 = [&p = p] { p.use_fields("Int"); }; + expect_error_on_command(p, command2); + } + + // One empty field + const std::vector valid_fields = {"Int0", "Int1", ""}; + + using svec = std::vector; + const std::vector> valid_field_combinations = + {svec{"Int0"}, + svec{"Int1"}, + svec{""}, + svec{"", "Int0"}, + svec{"Int0", "Int1"}, + svec{"Int1", ""}, + svec{"Int0", "", "Int1"}, + svec{"", "Int1", "Int0"}}; + + // Last header field empty + { + std::ofstream out{f.name}; + out << "Int0,Int1," << std::endl; + out << "1,2,3" << std::endl; + } + + { + std::vector expected_header = {"Int0", "Int1", ""}; + auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(expected_header, p.header()); + CHECK_EQ("Int0,Int1,", p.raw_header()); + CHECK(p.valid()); + + for (const auto& field : valid_fields) { + CHECK(p.field_exists(field)); + CHECK(p.valid()); + } + + for (const auto& fields : valid_field_combinations) { + p.use_fields(fields); + CHECK(p.valid()); + } + } + + // First header field empty + { + std::ofstream out{f.name}; + out << ",Int0,Int1" << std::endl; + out << "1,2,3" << std::endl; + } + + { + std::vector expected_header = {"", "Int0", "Int1"}; + auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(expected_header, p.header()); + CHECK_EQ(",Int0,Int1", p.raw_header()); + CHECK(p.valid()); + + for (const auto& field : valid_fields) { + CHECK(p.field_exists(field)); + CHECK(p.valid()); + } + + for (const auto& fields : valid_field_combinations) { + p.use_fields(fields); + CHECK(p.valid()); + } + } + + // Middle header field empty + { + std::ofstream out{f.name}; + out << "Int0,,Int1" << std::endl; + out << "1,2,3" << std::endl; + } + + { + std::vector expected_header = {"Int0", "", "Int1"}; + auto [p, _] = make_parser(f.name); + CHECK_EQ_ARRAY(expected_header, p.header()); + CHECK_EQ("Int0,,Int1", p.raw_header()); + CHECK(p.valid()); + + for (const auto& field : valid_fields) { + CHECK(p.field_exists(field)); + CHECK(p.valid()); + } + + for (const auto& fields : valid_field_combinations) { + p.use_fields(fields); + CHECK(p.valid()); + } + } +} + +template +void test_unterminated_quote_header() { + constexpr auto buffer_mode = T::BufferMode::value; + using ErrorMode = typename T::ErrorMode; + + unique_file_name f{"unterminated_quote_header"}; + + { + std::ofstream out{f.name}; + out << "\"Int" << std::endl; + out << "1" << std::endl; + } + + { + auto [p, _] = make_parser(f.name); + + auto command0 = [&p = p] { std::ignore = p.header(); }; + expect_error_on_command(p, command0); + CHECK_EQ(p.raw_header(), "\"Int"); + + auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); }; + expect_error_on_command(p, command1); + + auto command2 = [&p = p] { p.use_fields("Int"); }; + expect_error_on_command(p, command2); + } +} + +TEST_CASE_TEMPLATE("test unterminated quote header", T, + ParserOptionCombinations) { + using quote = ss::quote<'"'>; + using escape = ss::escape<'\\'>; + test_unterminated_quote_header(); + test_unterminated_quote_header(); + test_unterminated_quote_header(); + test_unterminated_quote_header(); +} + +template +void test_unterminated_escape_header() { + constexpr auto buffer_mode = T::BufferMode::value; + using ErrorMode = typename T::ErrorMode; + + unique_file_name f{"unterminated_escape_header"}; + + // Unterminated escape in header + { + std::ofstream out{f.name}; + out << "Int\\" << std::endl; + out << "1" << std::endl; + } + + { + auto [p, _] = make_parser(f.name); + + auto command0 = [&p = p] { std::ignore = p.header(); }; + expect_error_on_command(p, command0); + CHECK_EQ(p.raw_header(), "Int\\"); + + auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); }; + expect_error_on_command(p, command1); + + auto command2 = [&p = p] { p.use_fields("Int"); }; + expect_error_on_command(p, command2); + } +} + +TEST_CASE_TEMPLATE("test unterminated escape header", T, + ParserOptionCombinations) { + using quote = ss::quote<'"'>; + using escape = ss::escape<'\\'>; + test_unterminated_escape_header(); + test_unterminated_escape_header(); + test_unterminated_escape_header(); + test_unterminated_escape_header(); +} + +template +void test_ignore_empty(const std::vector& data) { + constexpr auto buffer_mode = T::BufferMode::value; + using ErrorMode = typename T::ErrorMode; + + unique_file_name f{"ignore_empty"}; + make_and_write(f.name, data); + + std::vector expected; + for (const auto& d : data) { + if (d.s != X::empty) { + expected.push_back(d); + } + } + + { + auto [p, _] = + make_parser(f.name, ","); + + std::vector i; + for (const auto& a : p.template iterate()) { + i.push_back(a); + } + + CHECK_EQ(i, expected); + } + + { + auto [p, _] = make_parser(f.name, ","); + std::vector i; + size_t n = 0; + while (!p.eof()) { + try { + ++n; + const auto& a = p.template get_next(); + if (data.at(n - 1).s == X::empty) { + CHECK_FALSE(p.valid()); + continue; + } + i.push_back(a); + } catch (...) { + CHECK_EQ(data.at(n - 1).s, X::empty); + } + } + + CHECK_EQ(i, expected); + } +} + +TEST_CASE_TEMPLATE("test various cases with empty lines", T, + ParserOptionCombinations) { + test_ignore_empty( + {{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}}); + + test_ignore_empty( + {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}}); + + test_ignore_empty( + {{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}}); + + test_ignore_empty( + {{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}}); + + test_ignore_empty( + {{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}}); + + test_ignore_empty( + {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}}); + + test_ignore_empty( + {{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}}); + + test_ignore_empty( + {{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}}); + + test_ignore_empty({{1, 2, X::empty}, + {3, 4, X::empty}, + {9, 10, X::empty}, + {11, 12, X::empty}}); + + test_ignore_empty( + {{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}}); + + test_ignore_empty( + {{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}}); + + test_ignore_empty({{11, 12, X::empty}}); + + test_ignore_empty({}); +} diff --git a/test/test_parser2.hpp b/test/test_parser2.hpp index 6048a33..6e1b9d7 100644 --- a/test/test_parser2.hpp +++ b/test/test_parser2.hpp @@ -1,13 +1,7 @@ #include "test_helpers.hpp" -#include -#include #include -#include #include -#include #include -#include -#include #include #ifndef SEGMENT_NAME @@ -91,8 +85,8 @@ struct column { }; template -column make_column(const std::string& input_header, - const std::vector& input_fields) { +[[nodiscard]] column make_column(const std::string& input_header, + const std::vector& input_fields) { using setup = ss::setup; std::vector filtered_fields; @@ -133,8 +127,8 @@ column make_column(const std::string& input_header, } template -std::vector generate_csv_data(const std::vector& data, - const std::string& delim) { +[[nodiscard]] std::vector generate_csv_data( + const std::vector& data, const std::string& delim) { (void)delim; using setup = ss::setup; constexpr static auto escape = '\\'; @@ -333,8 +327,10 @@ void test_data_combinations(const std::vector& input_data, field_header.push_back(field{el.header}); } + std::string header_line; if (include_header) { auto header_data = generate_csv_data(field_header, delim); + header_line = merge_header(header_data, delim); if (input_data.size() == 0 && rand() % 10 == 0) { write_to_file(header_data, delim, f.name, false); } else { @@ -403,7 +399,9 @@ void test_data_combinations(const std::vector& input_data, fields.push_back(header[index]); } - p.use_fields(fields); + if constexpr (!setup::ignore_header) { + p.use_fields(fields); + } if (!p.valid()) { if constexpr (setup::string_error) { @@ -425,8 +423,19 @@ void test_data_combinations(const std::vector& input_data, } }; + auto check_header = [&p = p, &header = header, include_header, + header_line] { + if (include_header) { + if constexpr (!setup::ignore_header) { + CHECK_EQ_ARRAY(header, p.header()); + CHECK_EQ(header_line, p.raw_header()); + } + } + }; + int num_columns = layout.size(); for (size_t i = 0; i < n + 1; ++i) { + check_header(); try { switch (num_columns) { case 1: { @@ -616,7 +625,7 @@ void test_option_combinations3() { test_option_combinations2(); } -} /* namespace */ +} /* anonymous namespace */ // Tests split into multiple compilation units #if 0 diff --git a/test/test_parser2_1.cpp b/test/test_parser2_1.cpp index 55ec45a..71c3ae1 100644 --- a/test/test_parser2_1.cpp +++ b/test/test_parser2_1.cpp @@ -9,4 +9,3 @@ TEST_CASE("parser test various cases version 2 segment 1") { test_option_combinations3(); #endif } - diff --git a/test/test_parser2_2.cpp b/test/test_parser2_2.cpp index ec76aa9..b4721fb 100644 --- a/test/test_parser2_2.cpp +++ b/test/test_parser2_2.cpp @@ -10,4 +10,3 @@ TEST_CASE("parser test various cases version 2 segment 2") { test_option_combinations3(); #endif } - diff --git a/test/test_parser2_3.cpp b/test/test_parser2_3.cpp index aab905c..4b8266c 100644 --- a/test/test_parser2_3.cpp +++ b/test/test_parser2_3.cpp @@ -11,4 +11,3 @@ TEST_CASE("parser test various cases version 2 segment 3") { test_option_combinations3(); #endif } - diff --git a/test/test_parser2_4.cpp b/test/test_parser2_4.cpp index ea78aaf..65cc1cb 100644 --- a/test/test_parser2_4.cpp +++ b/test/test_parser2_4.cpp @@ -12,4 +12,3 @@ TEST_CASE("parser test various cases version 2 segment 4") { test_option_combinations3(); #endif } - diff --git a/test/test_parser2_5.cpp b/test/test_parser2_5.cpp index 3f345b0..dcb661a 100644 --- a/test/test_parser2_5.cpp +++ b/test/test_parser2_5.cpp @@ -13,4 +13,3 @@ TEST_CASE("parser test various cases version 2 segment 5") { test_option_combinations(); #endif } - diff --git a/test/test_parser2_6.cpp b/test/test_parser2_6.cpp index d6ec30f..c093143 100644 --- a/test/test_parser2_6.cpp +++ b/test/test_parser2_6.cpp @@ -8,4 +8,3 @@ TEST_CASE("parser test various cases version 2 segment 6") { test_option_combinations3(); } - diff --git a/test/test_single_header.sh b/test/test_single_header.sh index 8a2bb91..bd47386 100755 --- a/test/test_single_header.sh +++ b/test/test_single_header.sh @@ -3,12 +3,14 @@ set -x set -e -python3 script/single_header_generator.py > ssp.cpp +TMP_HDR=test_single_header.hpp +TMP_SRC=test_single_header.cpp +TMP_BIN=test_single_header -echo 'int main(){ ss::parser p{""}; p.get_next(); return 0; }' \ - >> ssp.cpp +python3 script/single_header_generator.py > ${TMP_HDR} +cat ${TMP_HDR} test/test_single_header_main.txt > ${TMP_SRC} -g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra -./ssp.bin +g++ -std=c++17 ${TMP_SRC} -o ${TMP_BIN} -Wall -Wextra +./${TMP_BIN} -rm ssp.cpp ssp.bin +rm ${TMP_HDR} ${TMP_SRC} ${TMP_BIN} diff --git a/test/test_single_header_main.txt b/test/test_single_header_main.txt new file mode 100644 index 0000000..77d8c6c --- /dev/null +++ b/test/test_single_header_main.txt @@ -0,0 +1,12 @@ +int main() { + using quote = ss::quote<'"'>; + using escape = ss::escape<'\\'>; + using trim = ss::trim<' '>; + + std::string data = "1,string,2.34,c"; + + ss::parser p{data.c_str(), data.size()}; + auto tup = p.get_next>(); + + return 0; +} diff --git a/test/test_splitter.cpp b/test/test_splitter.cpp index 201de9d..5216688 100644 --- a/test/test_splitter.cpp +++ b/test/test_splitter.cpp @@ -145,7 +145,7 @@ make_combinations(const std::vector& input, return {std::move(lines), std::move(expectations)}; } -} /* namespace */ +} /* anonymous namespace */ /* ********************************** */ /* ********************************** */ @@ -548,7 +548,7 @@ public: return splitter.size_shifted(); } }; -} /* ss */ +} /* namespace ss */ TEST_CASE("splitter test resplit unterminated quote") {