From 7393e8422cfad0ec47a1150075a5a3c6d43ae7e1 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 11 Feb 2020 02:18:14 -0400 Subject: [PATCH] Improve on-the-fly bt deserialization interface This separates the dict traversal into `next_...` functions that return a key-value pair, and `consume_...` that return just the value. The latter is particularly useful when using `skip_until` to position yourself at a known key. --- lokimq/bt_serialize.cpp | 9 +++++++ lokimq/bt_serialize.h | 54 +++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/lokimq/bt_serialize.cpp b/lokimq/bt_serialize.cpp index 2f7b5b9..3c0d2cf 100644 --- a/lokimq/bt_serialize.cpp +++ b/lokimq/bt_serialize.cpp @@ -216,5 +216,14 @@ bool bt_dict_consumer::consume_key() { return true; } +std::pair bt_dict_consumer::next_string() { + if (!is_string()) + throw bt_deserialize_invalid_type{"expected a string, but found "s + data.front()}; + std::pair ret; + ret.second = bt_list_consumer::consume_string(); + ret.first = flush_key(); + return ret; +} + } // namespace lokimq diff --git a/lokimq/bt_serialize.h b/lokimq/bt_serialize.h index 46790ed..f4f7633 100644 --- a/lokimq/bt_serialize.h +++ b/lokimq/bt_serialize.h @@ -693,12 +693,12 @@ public: /// Attempt to parse the next value as a string->string pair (and advance just past it). Throws /// if the next value is not a string. - std::pair consume_string(); + std::pair next_string(); /// Attempts to parse the next value as an string->integer pair (and advance just past it). /// Throws if the next value is not an integer. template - std::pair consume_integer() { + std::pair next_integer() { if (!is_integer()) throw bt_deserialize_invalid_type{"next bt dict value is not an integer"}; std::pair ret; ret.second = bt_list_consumer::consume_integer(); @@ -711,7 +711,7 @@ public: /// which allows alloc-free traversal, but requires parsing twice (if the contents are to be /// used). template - std::pair consume_list() { + std::pair next_list() { std::pair pair; pair.first = consume_list(pair.second); return pair; @@ -719,7 +719,7 @@ public: /// Same as above, but takes a pre-existing list-like data type. Returns the key. template - string_view consume_list(T& list) { + string_view next_list(T& list) { if (!is_list()) throw bt_deserialize_invalid_type{"next bt value is not a list"}; bt_list_consumer::consume_list(list); return flush_key(); @@ -730,7 +730,7 @@ public: /// which allows alloc-free traversal, but requires parsing twice (if the contents are to be /// used). template - std::pair consume_dict() { + std::pair next_dict() { std::pair pair; pair.first = consume_dict(pair.second); return pair; @@ -738,7 +738,7 @@ public: /// Same as above, but takes a pre-existing dict-like data type. Returns the key. template - string_view consume_dict(T& dict) { + string_view next_dict(T& dict) { if (!is_dict()) throw bt_deserialize_invalid_type{"next bt value is not a dict"}; bt_list_consumer::consume_dict(dict); return flush_key(); @@ -748,20 +748,26 @@ public: /// contains the entire thing. This is recursive into both lists and dicts and likely to be /// quite inefficient for large, nested structures (unless the values only need to be skipped /// but aren't separately needed). This, however, does not require dynamic memory allocation. - std::pair consume_list_data() { + std::pair next_list_data() { if (data.size() < 2 || !is_list()) throw bt_deserialize_invalid_type{"next bt dict value is not a list"}; return {flush_key(), bt_list_consumer::consume_list_data()}; } + /// Same as next_list_data(), but wraps the value in a bt_list_consumer for convenience + std::pair next_list_consumer() { return next_list_data(); } + /// Attempts to parse the next value as a string->dict pair and returns the string_view that /// contains the entire thing. This is recursive into both lists and dicts and likely to be /// quite inefficient for large, nested structures (unless the values only need to be skipped /// but aren't separately needed). This, however, does not require dynamic memory allocation. - std::pair consume_dict_data() { + std::pair next_dict_data() { if (data.size() < 2 || !is_dict()) throw bt_deserialize_invalid_type{"next bt dict value is not a dict"}; return {flush_key(), bt_list_consumer::consume_dict_data()}; } + /// Same as next_dict_data(), but wraps the value in a bt_dict_consumer for convenience + std::pair next_dict_consumer() { return next_dict_data(); } + /// Skips ahead until we find the first key >= the given key or reach the end of the dict. /// Returns true if we found an exact match, false if we reached some greater value or the end. /// If we didn't hit the end, the next `consumer_*()` call will return the key-value pair we @@ -782,6 +788,38 @@ public: } return key_ == find; } + + /// The `consume_*` functions are wrappers around next_whatever that discard the returned key. + /// + /// Intended for use with skip_until such as: + /// + /// std::string value; + /// if (d.skip_until("key")) + /// value = d.consume_string(); + /// + + auto consume_string() { return next_string().second; } + + template + auto consume_integer() { return next_integer().second; } + + template + auto consume_list() { return next_list().second; } + + template + void consume_list(T& list) { next_list(list); } + + template + auto consume_dict() { return next_dict().second; } + + template + void consume_dict(T& dict) { next_dict(dict); } + + string_view consume_list_data() { return next_list_data().second; } + string_view consume_dict_data() { return next_dict_data().second; } + + bt_list_consumer consume_list_consumer() { return consume_list_data(); } + bt_dict_consumer consume_dict_consumer() { return consume_dict_data(); } };