From 3dbe4c12b71c1f8161ed6141f1171f77d9841ec6 Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Sat, 16 May 2026 15:08:09 +0300 Subject: [PATCH] mcc_keyvalue.h: developing ... --- CMakeLists.txt | 3 + include/mcc/mcc_epoch.h | 3 +- include/mcc/mcc_keyvalue.h | 179 +++++++++++++++++++++++++++++++----- tests/mcc_keyvalue_test.cpp | 62 +++++++++++++ 4 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 tests/mcc_keyvalue_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ffc17a..2ba3501 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,6 +355,9 @@ if(BUILD_TESTS) add_executable(mcc_pcm_z1000_test tests/mcc_pcm_z1000_test.cpp) target_link_libraries(mcc_pcm_z1000_test PRIVATE ${PROJECT_NAME}) + + add_executable(mcc_keyvalue_test tests/mcc_keyvalue_test.cpp) + target_link_libraries(mcc_keyvalue_test PRIVATE ${PROJECT_NAME}) else() # This is just a stub to allow access to the path and library settings for the ${PROJECT_NAME} target during development add_executable(just_stub EXCLUDE_FROM_ALL main.cpp) diff --git a/include/mcc/mcc_epoch.h b/include/mcc/mcc_epoch.h index 2d069ef..1493365 100644 --- a/include/mcc/mcc_epoch.h +++ b/include/mcc/mcc_epoch.h @@ -407,6 +407,5 @@ protected: static_assert(mcc_coord_epoch_c, "!!!"); - - +static_assert(requires(MccCelestialCoordEpoch ep) { ep = std::chrono::system_clock::now(); }); } // namespace mcc::impl diff --git a/include/mcc/mcc_keyvalue.h b/include/mcc/mcc_keyvalue.h index e8cd209..24088bc 100644 --- a/include/mcc/mcc_keyvalue.h +++ b/include/mcc/mcc_keyvalue.h @@ -11,6 +11,8 @@ ****************************************************************************************/ +#include + #include "mcc_constants.h" #include "mcc_deserializer.h" #include "mcc_serializer.h" @@ -19,7 +21,13 @@ namespace mcc::impl { -enum class MccKeyValueHolderErrorCode : int { ERROR_OK, ERROR_INVALID_KEY, ERROR_INCOMPATIBLE_TYPE, ERROR_DESERIAL }; +enum class MccKeyValueHolderErrorCode : int { + ERROR_OK, + ERROR_INVALID_KEY, + ERROR_INCOMPATIBLE_TYPE, + ERROR_DESERIAL, + ERROR_SERIAL +}; } // namespace mcc::impl @@ -59,6 +67,8 @@ struct MccKeyValueHolderCategory : public std::error_category { return "incompatible type"; case MccKeyValueHolderErrorCode::ERROR_DESERIAL: return "deserialization error"; + case MccKeyValueHolderErrorCode::ERROR_SERIAL: + return "serialization error"; default: return "UNKNOWN"; } @@ -85,7 +95,7 @@ concept mcc_variant_valid_type_c = requires { !std::is_array_v && !std::is_vo template concept mcc_keyvalue_record_c = requires(T t) { - requires std::same_as; + requires std::same_as; // immutable key!!! requires mcc_variant_valid_type_c; }; @@ -133,6 +143,12 @@ public: } + template + std::expected getValue(R const& key) const + { + return getValue(std::string_view{key}); + } + template std::expected getValue(std::string_view key) const { @@ -156,29 +172,59 @@ public: } } + template + std::error_code setValue(R const& key, const T& value) + { + return setValue(std::string_view{key}, value); + } + template std::error_code setValue(std::string_view key, const T& value) { - return forKey(key, [value](VT& val) -> std::error_code { - if constexpr (std::convertible_to) { + using r_t = std::invoke_result_t; + // static_assert(std::convertible_to); + // static_assert(std::assignable_from); + // static_assert(std::constructible_from); + // static_assert(std::destructible); + + return forKey(key, [value, key](VT& val) -> std::error_code { + if constexpr (requires(std::decay_t v) { v = value; }) { val = value; - } else if constexpr (std::constructible_from) { - val = VT(value); - } else { - return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE; } + std::println("FORKEY_FUN: {} = {}", key, value); + // if constexpr (std::convertible_to) { + // val = value; + // } else if constexpr (std::constructible_from) { + // val = VT(value); + // } else if constexpr (std::assignable_from) { + // val = value; + // } else { + // return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE; + // } return MccKeyValueHolderErrorCode::ERROR_OK; + // if constexpr (std::convertible_to) { + // val = value; + // } else if constexpr (std::constructible_from) { + // val = VT(value); + // } else if constexpr (std::assignable_from) { + // val = value; + // } else { + // return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE; + // } + + // return MccKeyValueHolderErrorCode::ERROR_OK; }); } - template std::error_code fromCharRange(const R& buffer, - RecDelimT rec_delim = std::string_view("\n"), + RecDelimT rec_delim = std::string_view("\n"), // records delimiter SerParamsT const& ser_params = mcc_serialization_params_t{}) + requires std::ranges::contiguous_range { if constexpr (std::is_array_v>) { // char*, const char* if constexpr (std::is_array_v>) { @@ -193,50 +239,134 @@ public: } std::error_code ec{}; - std::string_view rec, key, value; - + std::string_view rec, key, value, inline_comm; + std::vector head_comm; + size_t key_hash; auto recs = std::views::split(buffer, std::move(rec_delim)); + for (auto const& el : recs) { rec = mcc::utils::trimSpaces(el, utils::TrimType::TRIM_LEFT); if (rec.size()) { auto found = std::ranges::search(rec, COMMENT_SEQ); - if (found.begin() != rec.end()) { // there was the comment sequence in record + if (found.begin() != rec.end()) { // there was the comment sequence in record + if (found.begin() != rec.begin()) { // inline comment + inline_comm = + std::string_view{found.begin() + COMMENT_SEQ.size(), rec.end()}; // mark inline comment + } else { // head comment + head_comm.push_back({found.end(), rec.end()}); + std::println("COMM: <{}>", head_comm.back()); + } + rec = std::string_view(rec.begin(), found.begin()); } if (rec.size()) { found = std::ranges::search(rec, KEY_VALUE_DELIM); - if (found.begin() != rec.begin()) { // ignore an empty key + if (found.begin() != rec.begin()) { key = trimSpaces(std::string_view(rec.begin(), found.begin()), utils::TrimType::TRIM_RIGHT); value = std::string_view(found.end(), rec.end()); - ec = forKey(key, [value, &ser_params](VT& v) { - auto err = MccDeserializer(value, v, ser_params); + key_hash = utils::FNV1aHash(key); + + // ec = forKey(key, [value, &ser_params](VT& v) { + // auto err = MccDeserializer{}(value, v, ser_params); + // if (err) { + // return MccKeyValueHolderErrorCode::ERROR_DESERIAL; + // } + + // return MccKeyValueHolderErrorCode::ERROR_OK; + // }); + + ec = forHash(key_hash, [value, &ser_params](VT& v) { + auto err = MccDeserializer{}(value, v, ser_params); if (err) { return MccKeyValueHolderErrorCode::ERROR_DESERIAL; } return MccKeyValueHolderErrorCode::ERROR_OK; }); + + if (ec) { // exit in the case of error + break; + } + + // save head comment + _headComment[key_hash] = head_comm; + head_comm.clear(); + _inlineComment[key_hash] = inline_comm; + } else { // an empty key + ec = MccKeyValueHolderErrorCode::ERROR_INVALID_KEY; + break; } } // just comment string starting from the beginning, just skip it (no error) - } // empty record, just skip it (no error) - - if (ec) { - break; + } else { // empty record, just skip it (no error) + head_comm.push_back({el.begin(), el.end()}); + std::println("EMPTY COMM: <{}>", head_comm.back()); } } return ec; } + template + std::error_code toCharRange(R& output_buffer, + RecDelimT rec_delim = std::string_view{"\n"}, + SerParamsT const& ser_params = mcc_serialization_params_t{}) + { + std::error_code ec{}; + + auto write_rec = [&output_buffer, &rec_delim, &ec, &ser_params, this]() { + auto key = std::get(_keyValue).key; + auto val = std::get(_keyValue).value; + using val_t = std::remove_cvref_t; + + std::string buff; + auto err = MccSerializer{}(buff, val, ser_params); + if (err) { + ec = MccKeyValueHolderErrorCode::ERROR_SERIAL; + } else { + size_t hash = _hashes[I]; + // write head comment + if (_headComment[hash].size()) { + for (auto const& comm : _headComment[hash]) { + // if (comm.size()) { + std::format_to(std::back_inserter(output_buffer), "{}{}", COMM_SEQ, comm); + // } + std::ranges::copy(rec_delim, std::back_inserter(output_buffer)); + } + } + + // key and value + std::format_to(std::back_inserter(output_buffer), "{}{}{}{}", rec_delim, key, KEY_VALUE_DELIM, buff); + + // inline comment + if (_inlineComment[hash].size()) { + std::format_to(std::back_inserter(output_buffer), " {}{}", COMMENT_SEQ, _inlineComment[hash]); + } + + // record delimiter + std::ranges::copy(rec_delim, std::back_inserter(output_buffer)); + } + }; + + [&write_rec](std::index_sequence) { + (write_rec.template operator()(), ...); + }(std::make_index_sequence>()); + + return ec; + } + protected: DESCR_T _keyValue; - std::array> _hashes; + std::array> _hashes; // key hashes + std::unordered_map> _headComment; // comment string/strings before key + std::unordered_map _inlineComment; // inline (after key=value pair) comment // // NOTE: deduced this is needed here to use "forKey" method in getter and setter (const and non-const contexts)!!! @@ -266,5 +396,10 @@ protected: } }; +template +struct mcc_simple_kv_record_t { + const std::string_view key; + T value; +}; } // namespace mcc::impl \ No newline at end of file diff --git a/tests/mcc_keyvalue_test.cpp b/tests/mcc_keyvalue_test.cpp new file mode 100644 index 0000000..ac4f399 --- /dev/null +++ b/tests/mcc_keyvalue_test.cpp @@ -0,0 +1,62 @@ +#include + +#include + +using namespace mcc::impl; + +static auto kv_desc = std::make_tuple(mcc_simple_kv_record_t{"bb", MccAngle{11.5_degs}}, + mcc_simple_kv_record_t{"aaa", std::string("AAA")}, + mcc_simple_kv_record_t{"cc", MccCelestialCoordEpoch{}}); + + + +static std::string STR = R"--( + +aaa = dewl_ewkj23+23998 + +# +# this is angle +bb=1.24534 +cc= 2026-05-15T05:53:20.921723918 # date UTC + +)--"; + +int main() +{ + // MccKeyValueHolder kv(kv_desc); + MccKeyValueHolder kv(kv_desc); + + auto err = kv.fromCharRange(STR); + if (err) { + std::println("ERR = {}", err); + return 1; + } + + std::println("{} = {}", "bb", kv.getValue(std::string{"bb"}).value().sexagesimal()); + std::println("{} = {}", "cc", kv.getValue("cc").value().UTC()); + + err = kv.setValue("cc", std::chrono::system_clock::now()); + if (err) { + std::println("ERR = {}", err); + // return 1; + } + err = kv.setValue("aaa", "OK"); + if (err) { + std::println("ERR = {}", err); + // return 1; + } + + std::println("------------------------------------"); + + std::string buff; + + err = kv.toCharRange(buff); + if (err) { + std::println("ERR = {}", err); + return 1; + } + + std::println("{}", buff); + + return 0; +} \ No newline at end of file