From 12a9e0fc11f5cf5c2f8e200ab0f916a43bc62e76 Mon Sep 17 00:00:00 2001 From: Andrey Saranchin Date: Thu, 23 Nov 2023 11:11:27 +0300 Subject: [PATCH] Dec: allow to save iterator to value instead of decoding The commit adds support of `mpp::as_raw` to new decoder. Wrapped value must be a pair of iterators (it can be wrapped to optional). When it is used, corresponding value is skipped but an iterator pointing to the value in buffer is saved. --- src/mpp/Dec.hpp | 133 +++++++++++++++++++++++++++++------- test/EncDecTest.cpp | 161 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 25 deletions(-) diff --git a/src/mpp/Dec.hpp b/src/mpp/Dec.hpp index f4d4200b0..761ca99e7 100644 --- a/src/mpp/Dec.hpp +++ b/src/mpp/Dec.hpp @@ -57,6 +57,18 @@ constexpr bool is_any_putable_v = tnt::is_emplacable_v || tnt::is_back_emplacable_v || tnt::is_back_pushable_v || tnt::is_insertable_v; +/** + * If it is true, the object of type T will not be decoded - raw data will + * be saved to it. + * + * Now it supports only a pair of iterators (probably, wrapped with + * mpp::as_raw). The check implicilty implies that BUF is an iterator, not + * buffer - it would be strange to pass a pair of buffer to decoder. + */ +template +constexpr bool is_raw_decoded_v = + is_wrapped_raw_v || tnt::is_pairish_of_v, BUF, BUF>; + template void put_to_putable(T& t, U&& u) @@ -74,18 +86,34 @@ put_to_putable(T& t, U&& u) } } +template +constexpr auto getFamiliesByRules(tnt::iseq) +{ + return family_sequence::family...>{}; +} + template +constexpr auto getFamiliesByRules() +{ + return getFamiliesByRules(tnt::tuple_iseq{}); +} + +template constexpr auto detectFamily() { using U = unwrap_t; static_assert(!std::is_const_v || tnt::is_tuplish_v, "Can't decode to constant type"); - if constexpr (is_wrapped_family_v) { + if constexpr (is_raw_decoded_v) { + static_assert(!is_wrapped_family_v); + return getFamiliesByRules(); + } else if constexpr (is_wrapped_family_v) { return family_sequence{}; } else if constexpr (has_dec_rule_v) { - return detectFamily())>(); + return detectFamily())>(); } else if constexpr (tnt::is_optional_v) { - return family_sequence_populate(detectFamily>()); + return family_sequence_populate( + detectFamily>()); } else if constexpr (std::is_same_v) { return family_sequence{}; } else if constexpr (std::is_same_v) { @@ -117,31 +145,19 @@ constexpr auto detectFamily() } } -template -constexpr auto getFamiliesByRules(tnt::iseq) -{ - return family_sequence::family...>{}; -} - -template -constexpr auto getFamiliesByRules() -{ - return getFamiliesByRules(tnt::tuple_iseq{}); -} - template constexpr bool hasChildren(family_sequence) { return (rule_by_family_t::has_children || ...); } -template +template constexpr auto hasChildren() { if constexpr (std::is_same_v) return false; else - return hasChildren(detectFamily()); + return hasChildren(detectFamily()); } enum path_item_type { @@ -157,6 +173,7 @@ enum path_item_type { PIT_DYN_KEY, PIT_DYN_SKIP, PIT_OPTIONAL, + PIT_RAW, }; constexpr size_t PATH_ITEM_MULT = 1000000; @@ -670,9 +687,9 @@ struct JumpsBuilder { std::declval()...)); using DST = std::remove_reference_t; if constexpr (path_item_type(LAST) == PIT_DYN_ADD) { - return detectFamily>(); + return detectFamily>(); } else { - return detectFamily(); + return detectFamily(); } } } @@ -688,9 +705,15 @@ struct JumpsBuilder { } }; +template +bool decode_impl(BUF& buf, T... t); + +/** + * Bulids a jump table and jumps by a current byte in the buffer. + */ template bool -decode_impl(BUF& buf, T... t) +decode_jump(BUF& buf, T... t) { static_assert(path_item_type(PATH::last()) != PIT_BAD); static constexpr auto jumps = JumpsBuilder::build(); @@ -698,6 +721,53 @@ decode_impl(BUF& buf, T... t) return jumps.data[tag](buf, t...); } +/** + * Saves an iterator to the beginning of object and modifies path to rewind + * buf to the end of current object and save an iterator to it. + */ +template +bool +decode_raw(BUF& buf, T... t) +{ + auto&& dst = unwrap(path_resolve(PATH{}, t...)); + using dst_t = std::remove_reference_t; + if constexpr (tnt::is_pairish_of_v) + dst.first = buf; + else + static_assert(tnt::always_false_v); + /* + * Let's populate path with PIT_RAW to save the second + * iterator when it will be popped and with PIT_DYN_SKIP + * to actually skip the current object. + */ + using RAW_PATH = path_push_t; + using RAW_SKIP_PATH = path_push_t; + return decode_impl(buf, t..., size_t(1)); +} + +/** + * A central function of the decoder. + * The decoding of each object starts from here. + */ +template +bool +decode_impl(BUF& buf, T... t) +{ + static_assert(path_item_type(PATH::last()) != PIT_BAD); + if constexpr (path_item_type(PATH::last()) != PIT_DYN_SKIP && + path_item_type(PATH::last()) != PIT_RAW) { + auto&& wrapped_dst = path_resolve(PATH{}, t...); + using wrapped_dst_t = std::remove_reference_t; + if constexpr (is_raw_decoded_v) { + return decode_raw(buf, t...); + } else { + return decode_jump(buf, t...); + } + } else { + return decode_jump(buf, t...); + } +} + template bool decode(BUF& buf, T&&... t) @@ -727,7 +797,20 @@ bool decode_next(BUF& buf, T... t) { if constexpr (PATH::size() == 0) return true; - else { + else if constexpr (path_item_type(PATH::last()) == PIT_RAW) { + using POP_PATH = typename PATH::pop_back_t; + auto&& wrapped_dst = path_resolve(POP_PATH{}, t...); + using wrapped_dst_t = std::remove_reference_t; + static_assert(is_raw_decoded_v); + auto&& dst = unwrap(wrapped_dst); + using dst_t = std::remove_reference_t; + if constexpr (tnt::is_pairish_of_v) { + dst.second = buf; + } else { + static_assert(tnt::always_false_v); + } + return decode_next(buf, t...); + } else { constexpr size_t LAST = PATH::last(); constexpr enum path_item_type LAST_TYPE = path_item_type(LAST); constexpr bool has_static = is_path_item_static(LAST); @@ -842,13 +925,13 @@ bool jump_add(BUF& buf, T... t) return decode_next(buf, t...); } -template +template constexpr enum path_item_type get_next_arr_item_type() { if constexpr (tnt::is_contiguous_v) { return PIT_DYN_POS; } else if constexpr (is_any_putable_v && - !hasChildren>()) { + !hasChildren>()) { return PIT_DYN_ADD; } else if constexpr (is_any_putable_v && tnt::is_back_accessible_v) { @@ -884,7 +967,7 @@ bool jump_read(BUF& buf, T... t) goto decode_next_label; if constexpr (FAMILY == compact::MP_ARR) { - constexpr auto NT = get_next_arr_item_type(); + constexpr auto NT = get_next_arr_item_type(); constexpr size_t NS = get_next_arr_static_size(); using NEXT_PATH = path_push_t; if constexpr (NT == PIT_BAD) { @@ -1077,7 +1160,7 @@ bool jump_read_optional(BUF& buf, T... t) if (!dst.has_value()) dst.emplace(); using NEXT_PATH = path_push_t; - return jump_read(buf, t...); + return decode_impl(buf, t...); } } diff --git a/test/EncDecTest.cpp b/test/EncDecTest.cpp index 18e6d7731..fda2a20ce 100644 --- a/test/EncDecTest.cpp +++ b/test/EncDecTest.cpp @@ -1446,6 +1446,166 @@ test_optional() buf.flush(); } +static void +test_raw() +{ + TEST_INIT(0); + using Buf_t = tnt::Buffer<16 * 1024>; + Buf_t buf; + std::string msg("Hello, test!"); + + mpp::encode(buf, 10); + + mpp::encode(buf, msg); + + std::array add_arr = {1, 2, 3}; + mpp::encode(buf, add_arr); + + std::map add_map = {{1, 2}, {3, 4}}; + mpp::encode(buf, add_map); + + using it_t = Buf_t::iterator_common; + it_t run = buf.begin(); + + std::pair to_wrap; + auto raw_decoders = std::make_tuple( + std::pair(), + std::optional>(), + mpp::as_raw(to_wrap) + ); + + auto check_each_raw_decoder = [&](auto& begin, auto& end) { + auto &dec0 = std::get<0>(raw_decoders); + fail_if(dec0.first != begin); + fail_if(dec0.second != end); + auto &dec1 = std::get<1>(raw_decoders); + fail_if(!dec1.has_value()); + fail_if(dec1->first != begin); + fail_if(dec1->second != end); + auto &dec2 = mpp::unwrap(std::get<2>(raw_decoders)); + fail_if(dec2.first != begin); + fail_if(dec2.second != end); + }; + + auto check_raw_decoders = [&](auto& begin, auto& end) { + std::apply([&](auto&... decs){( + ..., + [&](auto& dec) { + auto it = begin; + bool ok = mpp::decode(it, dec); + fail_if(!ok); + fail_if(it != end); + }(decs)); + }, raw_decoders); + check_each_raw_decoder(begin, end); + }; + + TEST_CASE("decode num"); + auto begin = run; + int num; + mpp::decode(run, num); + check_raw_decoders(begin, run); + + TEST_CASE("decode string"); + begin = run; + std::string str; + mpp::decode(run, str); + fail_if(str != msg); + check_raw_decoders(begin, run); + + auto svp = run; + TEST_CASE("decode the whole array"); + begin = run; + std::array arr; + mpp::decode(run, arr); + fail_if(arr != add_arr); + check_raw_decoders(begin, run); + const auto arr_end = run; + TEST_CASE("decode the first element of array"); + run = svp; + begin = run; + mpp::decode(run, mpp::as_arr(std::forward_as_tuple(num))); + fail_if(num != arr[0]); + fail_if(run != arr_end); + std::apply([&](auto&... decs){( + ..., + [&](auto& dec) { + auto it = begin; + bool ok = mpp::decode(it, + mpp::as_arr(std::forward_as_tuple(dec))); + fail_if(!ok); + fail_if(it != run); + }(decs)); + }, raw_decoders); + // Array is small - its header occupies one byte. + auto elem_begin = begin + 1; + auto elem_end = elem_begin; + mpp::decode(elem_end, num); + check_each_raw_decoder(elem_begin, elem_end); + TEST_CASE("decode the array key by key"); + run = svp; + // Array is small - its header occupies one byte. + run.read({1}); + for (size_t i = 0; i < std::size(arr); i++) { + int val = 0; + begin = run; + mpp::decode(run, val); + fail_if(val != static_cast(i) + 1); + check_raw_decoders(begin, run); + } + + TEST_CASE("decode the whole map"); + svp = run; + begin = run; + int v1 = 0, v3 = 0; + mpp::decode(run, mpp::as_map(std::forward_as_tuple(1, v1, 3, v3))); + fail_if(v1 != 2); + fail_if(v3 != 4); + check_raw_decoders(begin, run); + const auto map_end = run; + TEST_CASE("decode one value from map"); + run = svp; + begin = run; + mpp::decode(run, mpp::as_map(std::forward_as_tuple(1, num))); + fail_if(run != map_end); + fail_if(num != 2); + std::apply([&](auto&... decs){( + ..., + [&](auto& dec) { + auto it = begin; + bool ok = mpp::decode(it, + mpp::as_map(std::forward_as_tuple(1, dec))); + fail_if(!ok); + fail_if(it != run); + }(decs)); + }, raw_decoders); + // Map is small - its header occupies one byte. + elem_begin = begin + 1; + // Skip key. + mpp::decode(elem_begin, num); + elem_end = elem_begin; + // Skip value. + mpp::decode(elem_end, num); + check_each_raw_decoder(elem_begin, elem_end); + TEST_CASE("decode the map key by key"); + run = svp; + // Map is small - its header occupies one byte. + run.read({1}); + for (size_t i = 0; i < std::size(add_map); i++) { + begin = run; + int key = 0; + mpp::decode(run, key); + fail_if(key != 1 && key != 3); + check_raw_decoders(begin, run); + + begin = run; + int value = 0; + mpp::decode(run, value); + fail_if(value - key != 1); + check_raw_decoders(begin, run); + } +} + int main() { test_under_ints(); @@ -1455,4 +1615,5 @@ int main() test_class_rules(); test_object_codec(); test_optional(); + test_raw(); }