diff --git a/asibfm700/tests/cfg_test.cpp b/asibfm700/tests/cfg_test.cpp index 9407272..341fb13 100644 --- a/asibfm700/tests/cfg_test.cpp +++ b/asibfm700/tests/cfg_test.cpp @@ -68,5 +68,15 @@ int main() std::cout << "refr w: " << e.value_or(0.0) << "\n"; std::cout << "refr w: " << acfg.refractWavelength << "\n"; + + mcc::utils::KeyValueHolder kvh(desc); + err = kvh.setValue("C", "ewlkjfde"); + if (err) { + std::cout << "cannot set value: " << err.message() << "\n"; + } else { + auto vs = kvh.getValue("C"); + std::cout << "kvh[C] = " << vs.value_or("") << "\n"; + } + return 0; } diff --git a/mcc/mcc_utils.h b/mcc/mcc_utils.h index 57b48b9..89ad893 100644 --- a/mcc/mcc_utils.h +++ b/mcc/mcc_utils.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -399,4 +400,167 @@ static constexpr size_t FNV1aHash(std::forward_iterator auto begin, std::sentine return hash; } + +/* key-value pair holder */ + +// to follow std::variant requirements (not references, not array, not void) +template +concept variant_valid_type_c = requires { !std::is_array_v && !std::is_void_v && !std::is_reference_v; }; + + +template +concept keyvalue_record_c = requires(T t) { + requires std::same_as; + requires variant_valid_type_c; +}; + + +template +concept keyvalue_desc_c = requires(T t) { [](std::tuple) {}(t); }; + + +template +class KeyValueHolder +{ +public: + static constexpr std::string_view COMMENT_SEQ{"#"}; + static constexpr std::string_view KEY_VALUE_DELIM{"="}; + static constexpr std::string_view VALUE_ARRAY_DELIM{","}; + + KeyValueHolder(DESCR_T desc) : _keyValue(desc) + { + [this](std::index_sequence) { + ((_hashes[I] = FNV1aHash(std::get(_keyValue).key)), ...); + }(std::make_index_sequence>()); + } + + template + std::expected getValue(std::string_view key) + { + T v; + auto err = forKey(key, [&v](const VT& val) { + if constexpr (std::convertible_to) { + v = val; + return std::error_code(); + } else { + return std::make_error_code(std::errc::invalid_argument); + } + }); + + if (err) { + return std::unexpected(err); + } else { + return v; + } + } + + template + std::error_code setValue(std::string_view key, const T& value) + { + return forKey(key, [value](VT& val) { + if constexpr (std::convertible_to) { + val = value; + return std::error_code(); + } else { + return std::make_error_code(std::errc::invalid_argument); + } + }); + } + + + template + std::error_code fromCharRange(const R& buffer, + DeserFuncT&& deser_func, + RecDelimT rec_delim = std::string_view("\n")) + { + if constexpr (std::is_array_v>) { // char*, const char* + if constexpr (std::is_array_v>) { + return fromCharRange(std::string_view{buffer}, std::forward(deser_func), + std::string_view(rec_delim)); + } else { + return fromCharRange(std::string_view{buffer}, std::forward(deser_func), + std::move(rec_delim)); + } + } else { + if constexpr (std::is_array_v>) { + return fromCharRange(buffer, std::forward(deser_func), std::string_view(rec_delim)); + } + } + + auto parse_rec = [this, &deser_func](std::string_view rec) -> std::error_code { + std::string_view key, value; + + if (rec.size()) { + auto found = std::ranges::search(rec, COMMENT_SEQ); + if (found.begin() != rec.end()) { // there was the comment sequence in record + rec = rec.substr(std::distance(rec.begin(), found.begin())); + } + + if (rec.size()) { + found = std::ranges::search(rec, KEY_VALUE_DELIM); + if (found.begin() != rec.begin()) { // ignore an empty key + key = trimSpaces(std::string_view(rec.begin(), found.begin()), TrimType::TRIM_RIGHT); + value = std::string_view(found.end(), rec.end()); + + return forKey(key, [value, &deser_func](VT& v) { return deser_func(value, v); }); + } + } + } + }; + + auto curr_buffer = std::string_view(buffer.begin(), buffer.end()); + bool buffer_end = false; + std::error_code ec; + std::string_view rec; + + auto delim_size = std::ranges::size(rec_delim); + if (delim_size == 0) { // just one record + return parse_rec(trimSpaces(buffer, TrimType::TRIM_LEFT)); + } + + do { + auto found = std::ranges::search(curr_buffer, rec_delim); + if (found.begin() == curr_buffer.end()) { + buffer_end = true; + } + + rec = mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), found.begin()), TrimType::TRIM_LEFT); + + curr_buffer = {found.end(), curr_buffer.end()}; + + ec = parse_rec(rec); + } while (!buffer_end || !ec); + + return ec; + } + +protected: + DESCR_T _keyValue; + + std::array> _hashes; + + + std::error_code forKey(std::string_view key, auto&& func) + { + return forHash<0>(FNV1aHash(key), std::forward(func)); + } + + + template + std::error_code forHash(size_t hash, auto&& func) + { + if constexpr (I < std::tuple_size_v) { + if (hash == _hashes[I]) { + return std::forward(func)(std::get(_keyValue).value); + } else { + return forHash(hash, std::forward(func)); + } + } + + return std::make_error_code(std::errc::argument_out_of_domain); + } +}; + } // namespace mcc::utils