diff --git a/src/mpp/Constants.hpp b/src/mpp/Constants.hpp index 7c7fd2389..f3243229c 100644 --- a/src/mpp/Constants.hpp +++ b/src/mpp/Constants.hpp @@ -203,6 +203,12 @@ struct family_sequence { } }; +template +static constexpr auto family_sequence_populate(struct family_sequence) +{ + return family_sequence{}; +} + template std::ostream& operator<<(std::ostream& strm, family_sequence) diff --git a/src/mpp/Dec.hpp b/src/mpp/Dec.hpp index 19d17038c..e603e2dc3 100644 --- a/src/mpp/Dec.hpp +++ b/src/mpp/Dec.hpp @@ -82,6 +82,10 @@ constexpr auto detectFamily() "Can't decode to constant type"); if constexpr (is_wrapped_family_v) { return family_sequence{}; + } else if constexpr (tnt::is_optional_v) { + return family_sequence_populate(detectFamily>()); + } else if constexpr (has_dec_rule_v) { + return detectFamily())>(); } else if constexpr (std::is_same_v) { return family_sequence{}; } else if constexpr (std::is_same_v) { @@ -203,7 +207,21 @@ using path_push_t = STATIC_SIZE, STATIC_POS)>; -template +/** + * The class allows to get an object by its path. + * All object can be divided into 3 parts: visible, transparent and + * semi-transparent. For example: + * std::vector is visible - when the path points to the vector, it + * is always returned by resolver, + * class MyClass { static constexpr mpp = ...; } is transparent - resolver + * never returns an instance of MyClass itself. + * std::optional is semi-transparent - the path, that points to the + * optional, also points to the underlying int. So, when the template argument + * ShowSemiTransparent is true, resolver will return the optional itself, and + * when it is false, it will skip semi-transparent objects - in our case, + * underlying int will be returned. + */ +template struct Resolver { template static constexpr size_t ITEM = tnt::iseq::template get(); @@ -239,41 +257,69 @@ struct Resolver { return I; } else if constexpr (std::is_member_pointer_v || tnt::is_tuplish_v) { - using PrevResolver = Resolver; + /* + * If the object with dec_rule is wrapped into optional, + * the optional must be skipped. That's why PrevResolver + * must have false ShowSemiTransparent flag. + */ + using PrevResolver = Resolver; return PrevResolver::template find_obj_index(); } else { static_assert(tnt::always_false_v); } } + /** + * The method is used to get the container which contains the value the + * path points to. + */ template - static constexpr auto&& prev(T... t) + static constexpr auto&& parent_container(T... t) { - return unwrap(Resolver::get(t...)); + /* + * If the container is wrapped into optional, the optional must + * be skipped. That's why the Resolver must have false + * ShowSemiTransparent flag. + */ + return unwrap(Resolver::get(t...)); } template - static constexpr auto&& extract(T... t) + static constexpr auto&& extract_impl(T... t) { if constexpr (TYPE == PIT_STATIC_L0) { return std::get(std::tie(t...)).get(); } else if constexpr (is_path_item_static(PI)) { - return tnt::get(prev(t...)); + return tnt::get(parent_container(t...)); } else if constexpr (TYPE == PIT_DYN_POS) { constexpr size_t ARG_POS = dyn_arg_pos(); uint64_t arg = std::get(std::tie(t...)); - return std::data(prev(t...))[arg >> 32]; + return std::data(parent_container(t...))[arg >> 32]; } else if constexpr (TYPE == PIT_DYN_BACK) { - return prev(t...).back(); + return parent_container(t...).back(); } else if constexpr (TYPE == PIT_DYN_ADD) { - return prev(t...); + return parent_container(t...); } else if constexpr (TYPE == PIT_DYN_KEY) { - return prev(t...); + return parent_container(t...); } else { static_assert(tnt::always_false_v); } } + template + static constexpr auto&& extract(T... t) + { + auto &&extracted = extract_impl(t...); + auto &&unwrapped = unwrap(extracted); + using unwrapped_t = std::remove_reference_t; + if constexpr (!ShowSemiTransparent && tnt::is_optional_v) { + assert(unwrapped.has_value()); + return *unwrapped; + } else { + return extracted; + } + } + template static constexpr auto&& unrule(T... t) { @@ -309,7 +355,12 @@ struct Resolver { using R = unwrap_t; if constexpr (std::is_member_pointer_v) { constexpr size_t OBJ_I = find_obj_index(); - using Res = Resolver; + /* + * If the object with the required field is wrapped into + * optional, the optional must be skipped. That's why + * Resolver must have false ShowSemiTransparent flag. + */ + using Res = Resolver; return self_unwrap(unwrap(Res::extract(t...)).* unwrap(unrule(t...))); } else { @@ -323,18 +374,29 @@ struct Resolver { } }; +/** Resolve path, semi-transparent object are visible. */ +template +constexpr auto&& path_resolve_transparent(tnt::iseq, T... t) +{ + using Res = Resolver; + static_assert(Res::expected_arg_count() == sizeof...(T)); + return Res::get(t...); +} + +/** Resolve path, semi-transparent object are invisible. */ template constexpr auto&& path_resolve(tnt::iseq, T... t) { - using Res = Resolver; + using Res = Resolver; static_assert(Res::expected_arg_count() == sizeof...(T)); return Res::get(t...); } +/** Resolve parent path, semi-transparent object are invisible. */ template constexpr auto&& path_resolve_parent(tnt::iseq, T... t) { - using Res = Resolver; + using Res = Resolver; static_assert(Res::expected_arg_count() == sizeof...(T) - 1); return Res::get(t...); } @@ -429,6 +491,8 @@ auto read_item(BUF& buf, ITEM& item) item.resize(val); } else if constexpr (std::is_enum_v) { item = static_cast(val); + } else if constexpr (FAMILY == compact::MP_NIL && tnt::is_optional_v) { + item.reset(); } else { item = val; } @@ -641,8 +705,7 @@ struct JumpsBuilder { if constexpr (path_item_type(LAST) == PIT_DYN_SKIP) { return getFamiliesByRules(); } else if constexpr (path_item_type(LAST) == PIT_DYN_KEY) { - using R = decltype(path_resolve(PATH{}, - std::declval()...)); + using R = decltype(path_resolve(PATH{}, std::declval()...)); using DST = std::remove_reference_t; static_assert(tnt::is_tuplish_v); constexpr size_t S = tnt::tuple_size_v; @@ -655,8 +718,7 @@ struct JumpsBuilder { return keyMapKeyFamiliesFlat(is{}); } } else { - using R = decltype(path_resolve(PATH{}, - std::declval()...)); + using R = decltype(path_resolve(PATH{}, std::declval()...)); using DST = std::remove_reference_t; if constexpr (path_item_type(LAST) == PIT_DYN_ADD) { return detectFamily>(); @@ -817,7 +879,7 @@ template ; tnt::value_type_t trg; read_item(buf, trg); @@ -853,17 +915,16 @@ constexpr enum path_item_type get_next_arr_item_type() template constexpr size_t get_next_arr_static_size() { - if constexpr (TYPE <= PIT_STADYN) + if constexpr (TYPE > PIT_BAD && TYPE <= PIT_STADYN) return tnt::tuple_size_v; else return 0; } template -bool jump_read(BUF& buf, T... t) + class PATH, class BUF, class DST, class... T> +bool jump_read_impl(BUF& buf, DST &&dst, T... t) { - auto&& dst = unwrap(path_resolve(PATH{}, t...)); using dst_t = std::remove_reference_t; using RULE = rule_by_family_t; auto val = read_item(buf, dst); @@ -932,6 +993,32 @@ bool jump_read(BUF& buf, T... t) decode_next_label: return decode_next(buf, t...); + +} + +template +bool jump_read(BUF& buf, T... t) +{ + auto&& dst = unwrap(path_resolve_transparent(PATH{}, t...)); + return jump_read_impl(buf, dst, t...); +} + +template +bool jump_read_optional(BUF& buf, T... t) +{ + auto&& optional_dst = unwrap(path_resolve(PATH{}, t...)); + if constexpr (FAMILY == compact::MP_NIL) { + [[maybe_unused]] auto val = read_value(buf); + optional_dst.reset(); + return decode_next(buf, t...); + } else { + if (!optional_dst.has_value()) + optional_dst.emplace(); + auto&& dst = unwrap(path_resolve_transparent(PATH{}, t...)); + return jump_read_impl(buf, dst, t...); + } } template @@ -1012,7 +1099,7 @@ bool jump_find_key(K k, tnt::iseq, BUF& buf, T... t) { static_assert(path_item_type(PATH::last()) == PIT_DYN_KEY); - auto&& key = key_path_resolve(path_resolve(PATH{}, t...)); + auto&& key = key_path_resolve(path_resolve_transparent(PATH{}, t...)); using PAIRS_NEXT_PREPATH = path_push_t; using PAIRS_NEXT_PATH = path_push_t; using FLAT_NEXT_PATH = path_push_t; @@ -1033,7 +1120,7 @@ template ; static_assert(tnt::is_tuplish_v); @@ -1053,14 +1140,20 @@ template bool jump_common(BUF& buf, T... t) { - if constexpr (path_item_type(PATH::last()) == PIT_DYN_ADD) - return jump_add(buf, t...); - else if constexpr (path_item_type(PATH::last()) == PIT_DYN_SKIP) + if constexpr (path_item_type(PATH::last()) == PIT_DYN_SKIP) { return jump_skip(buf, t...); - else if constexpr (path_item_type(PATH::last()) == PIT_DYN_KEY) - return jump_read_key(buf, t...); - else - return jump_read(buf, t...); + } else { + auto&& dst = unwrap(path_resolve(PATH{}, t...)); + using dst_t = std::remove_reference_t; + if constexpr (tnt::is_optional_v && path_item_type(PATH::last()) != PIT_DYN_KEY) + return jump_read_optional(buf, t...); + else if constexpr (path_item_type(PATH::last()) == PIT_DYN_ADD) + return jump_add(buf, t...); + else if constexpr (path_item_type(PATH::last()) == PIT_DYN_KEY) + return jump_read_key(buf, t...); + else + return jump_read(buf, t...); + } } template diff --git a/src/mpp/Enc.hpp b/src/mpp/Enc.hpp index 894377f33..e2e532e32 100644 --- a/src/mpp/Enc.hpp +++ b/src/mpp/Enc.hpp @@ -544,6 +544,12 @@ encode(CONT &cont, tnt::CStr prefix, } if constexpr(mpp::has_enc_rule_v) { const auto& rule = mpp::get_enc_rule(); return encode(cont, prefix, ais, subst(rule, t), more...); + } else if constexpr(tnt::is_optional_v) { + static_assert(!is_wrapped_family_v && !is_wrapped_raw_v); + if (u.has_value()) + return encode(cont, prefix, ais, u.value(), more...); + else + return encode(cont, prefix, ais, nullptr, more...); } else if constexpr(is_wrapped_raw_v) { if constexpr(std::is_base_of_v) { using V = typename U::type; diff --git a/test/EncDecTest.cpp b/test/EncDecTest.cpp index e0e23472b..d45e1f214 100644 --- a/test/EncDecTest.cpp +++ b/test/EncDecTest.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "Utils/Helpers.hpp" #include "Utils/RefVector.hpp" @@ -1109,6 +1110,11 @@ struct IntegerWrapper { return i == that.i; } + bool operator<(const IntegerWrapper& that) const + { + return i < that.i; + } + static constexpr auto mpp = &IntegerWrapper::i; }; @@ -1128,6 +1134,11 @@ struct Triplet { { return std::tie(a, b, c) == std::tie(that.a, that.b, that.c); } + + bool operator<(const Triplet& that) const + { + return std::tie(a, b, c) < std::tie(that.a, that.b, that.c); + } }; template <> @@ -1152,6 +1163,11 @@ struct Error { return std::tie(code, descr) == std::tie(that.code, that.descr); } + bool operator<(const Error& that) const + { + return std::tie(code, descr) < std::tie(that.code, that.descr); + } + static constexpr auto mpp = std::make_tuple( std::make_pair(0, &Error::code), std::make_pair(1, &Error::descr)); @@ -1181,6 +1197,12 @@ struct Body { std::tie(that.str, that.num, that.triplets, that.errors); } + bool operator<(const Body& that) const + { + return std::tie(str, num, triplets, errors) < + std::tie(that.str, that.num, that.triplets, that.errors); + } + static constexpr auto mpp = std::make_tuple( std::make_pair(0, &Body::str), std::make_pair(1, &Body::num), @@ -1197,9 +1219,11 @@ test_object_codec() Buf_t buf; Body wr, rd; + std::set rds; wr.gen(); mpp::encode(buf, wr); + mpp::encode(buf, mpp::as_arr(std::forward_as_tuple(wr))); for (auto itr = buf.begin(); itr != buf.end(); ++itr) { char c = itr.get(); @@ -1214,11 +1238,214 @@ test_object_codec() auto itr = buf.begin(); mpp::decode(itr, rd); - fail_unless(rd == wr); + + mpp::decode(itr, rds); + fail_unless(rds.count(wr) > 0); + fail_unless(itr == buf.end()); } +static void +test_optional() +{ + TEST_INIT(0); + + using Buf_t = tnt::Buffer<16 * 1024>; + Buf_t buf; + bool ok; + + TEST_CASE("number"); + mpp::encode(buf, std::optional(100), std::optional(), + std::optional(42)); + + auto run = buf.begin(); + std::optional opt_num; + ok = mpp::decode(run, opt_num); + fail_unless(ok); + fail_unless(opt_num.has_value()); + fail_unless(opt_num.value() == 100); + + ok = mpp::decode(run, opt_num); + fail_unless(ok); + fail_unless(!opt_num.has_value()); + + ok = mpp::decode(run, opt_num); + fail_unless(ok); + fail_unless(opt_num.has_value()); + fail_unless(opt_num.value() == 42); + + buf.flush(); + + TEST_CASE("containers with numbers"); + int null_idx = 4; + mpp::encode(buf, std::make_optional(mpp::as_arr( + std::forward_as_tuple(0, std::make_optional(1), 2, 3, std::optional(), 5) + ))); + mpp::encode(buf, nullptr); + std::vector> opt_num_arr; + std::set> opt_num_set; + std::optional>> opt_num_opt_arr; + std::optional>> opt_num_opt_set; + + run = buf.begin(); + ok = mpp::decode(run, opt_num_arr); + fail_unless(ok); + fail_unless(opt_num_arr.size() == 6); + for (int i = 0; i < 6; i++) { + if (i == null_idx) { + fail_unless(!opt_num_arr[i].has_value()); + continue; + } + fail_unless(opt_num_arr[i].has_value()); + fail_unless(opt_num_arr[i].value() == i); + } + + run = buf.begin(); + ok = mpp::decode(run, opt_num_set); + fail_unless(ok); + fail_unless(opt_num_set.size() == 6); + for (int i = 0; i < 6; i++) { + if (i == null_idx) { + fail_unless(opt_num_set.count(i) == 0); + fail_unless(opt_num_set.count(std::nullopt) == 1); + continue; + } + fail_unless(opt_num_set.count(i) > 0); + } + + run = buf.begin(); + ok = mpp::decode(run, opt_num_opt_arr); + fail_unless(ok); + fail_unless(opt_num_opt_arr.has_value()); + fail_unless(opt_num_opt_arr->size() == 6); + for (int i = 0; i < 6; i++) { + if (i == null_idx) { + fail_unless(!opt_num_opt_arr.value()[i].has_value()); + continue; + } + fail_unless(opt_num_opt_arr.value()[i].has_value()); + fail_unless(opt_num_opt_arr.value()[i].value() == i); + } + ok = mpp::decode(run, opt_num_opt_arr); + fail_unless(ok); + fail_unless(!opt_num_opt_arr.has_value()); + + run = buf.begin(); + ok = mpp::decode(run, opt_num_opt_set); + fail_unless(ok); + fail_unless(opt_num_opt_set.has_value()); + fail_unless(opt_num_opt_set->size() == 6); + for (int i = 0; i < 6; i++) { + if (i == null_idx) { + fail_unless(opt_num_opt_set->count(i) == 0); + fail_unless(opt_num_opt_set->count(std::nullopt) == 1); + continue; + } + fail_unless(opt_num_opt_set->count(i) > 0); + } + ok = mpp::decode(run, opt_num_opt_set); + fail_unless(ok); + fail_unless(!opt_num_opt_set.has_value()); + + buf.flush(); + + TEST_CASE("objects"); + Body wr; + wr.gen(); + mpp::encode(buf, std::optional(wr), std::optional()); + + run = buf.begin(); + std::optional rd; + ok = mpp::decode(run, rd); + fail_unless(ok); + fail_unless(rd.has_value()); + fail_unless(rd.value() == wr); + + ok = mpp::decode(run, rd); + fail_unless(ok); + fail_unless(!rd.has_value()); + + buf.flush(); + + TEST_CASE("containers with objects"); + std::vector wrs; + for (size_t i = 0; i < 3; i++) { + wrs.emplace_back(); + wrs[i].gen(); + wrs[i].str += std::to_string(i); + } + null_idx = 1; + mpp::encode(buf, mpp::as_arr(std::forward_as_tuple(wrs[0], nullptr, wrs[2]))); + mpp::encode(buf, nullptr); + std::vector> opt_body_arr; + std::set> opt_body_set; + std::optional>> opt_body_opt_arr; + std::optional>> opt_body_opt_set; + + run = buf.begin(); + ok = mpp::decode(run, opt_body_arr); + fail_unless(ok); + fail_unless(opt_body_arr.size() == 3); + for (int i = 0; i < 3; i++) { + if (i == null_idx) { + fail_unless(!opt_body_arr[i].has_value()); + continue; + } + fail_unless(opt_body_arr[i].has_value()); + fail_unless(opt_body_arr[i].value() == wrs[i]); + } + + run = buf.begin(); + ok = mpp::decode(run, opt_body_set); + fail_unless(ok); + fail_unless(opt_body_set.size() == 3); + fail_unless(opt_body_set.count(std::nullopt) == 1); + for (int i = 0; i < 3; i++) { + if (i == null_idx) { + fail_unless(opt_body_set.count(wrs[i]) == 0); + continue; + } + fail_unless(opt_body_set.count(wrs[i]) > 0); + } + + run = buf.begin(); + ok = mpp::decode(run, opt_body_opt_arr); + fail_unless(ok); + fail_unless(opt_body_opt_arr.has_value()); + fail_unless(opt_body_opt_arr->size() == 3); + for (int i = 0; i < 3; i++) { + if (i == null_idx) { + fail_unless(!opt_body_opt_arr.value()[i].has_value()); + continue; + } + fail_unless(opt_body_opt_arr.value()[i].has_value()); + fail_unless(opt_body_opt_arr.value()[i].value() == wrs[i]); + } + ok = mpp::decode(run, opt_body_opt_arr); + fail_unless(ok); + fail_unless(!opt_body_opt_arr.has_value()); + + run = buf.begin(); + ok = mpp::decode(run, opt_body_opt_set); + fail_unless(ok); + fail_unless(opt_body_opt_set.has_value()); + fail_unless(opt_body_opt_set->size() == 3); + fail_unless(opt_body_opt_set->count(std::nullopt) == 1); + for (int i = 0; i < 3; i++) { + if (i == null_idx) { + fail_unless(opt_body_opt_set->count(wrs[i]) == 0); + continue; + } + fail_unless(opt_body_opt_set->count(wrs[i]) > 0); + } + ok = mpp::decode(run, opt_body_opt_set); + fail_unless(ok); + fail_unless(!opt_body_opt_set.has_value()); + + buf.flush(); +} + int main() { test_under_ints(); @@ -1227,4 +1454,5 @@ int main() test_basic(); test_class_rules(); test_object_codec(); + test_optional(); }