summaryrefslogtreecommitdiffstats
path: root/Source/cmJSONHelpers.h
diff options
context:
space:
mode:
authorAlex Turbov <i.zaufi@gmail.com>2024-08-14 16:15:34 (GMT)
committerAlex Turbov <i.zaufi@gmail.com>2024-08-26 15:01:48 (GMT)
commit0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7 (patch)
tree00bebd0062f297cc31d9cbfeda97d26141e3c402 /Source/cmJSONHelpers.h
parentcd4210eb6d721a1b75e739e81250d1ac5a35b99f (diff)
downloadCMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.zip
CMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.tar.gz
CMake-0c17cc9ef572c6d5b2de019e29f12cfeaec65aa7.tar.bz2
cmJSONHelpers.h: Add `FilteredObject` helper
Iterate over the object's members and call a filter callable to decide what to do with the current key/value. A filter returns one of the `FilterResult` values. A container type is an associative or a sequence container of pairs (key, value). Refactor `MapFilter()` and `Map()` to use `FilteredObject()`. Moreover, for C++ >= 17 implementation is more optimized depending on the given filter object type and capable of detecting and properly calling the filter callable using 1 or 3 arguments, up to totally eliminate any checking (even dummy) in the generated code. Supported container types, used to append key/value items, aren't limited to `std::map` only and can be any associative container or a sequenced one with pairs of key/value as elements.
Diffstat (limited to 'Source/cmJSONHelpers.h')
-rw-r--r--Source/cmJSONHelpers.h130
1 files changed, 115 insertions, 15 deletions
diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h
index 0bb32f4..369c0b7 100644
--- a/Source/cmJSONHelpers.h
+++ b/Source/cmJSONHelpers.h
@@ -7,8 +7,10 @@
#include <algorithm>
#include <functional>
#include <iostream>
+#include <iterator>
#include <map>
#include <string>
+#include <type_traits>
#include <utility>
#include <vector>
@@ -60,9 +62,28 @@ ErrorGenerator INVALID_NAMED_OBJECT_KEY(
ObjectError errorType, const Json::Value::Members& extraFields);
}
-struct cmJSONHelperBuilder
+#if __cplusplus >= 201703L
+namespace details {
+// A meta-function to check if a given callable type
+// can be called with the only string ref arg.
+template <typename F, typename Enable = void>
+struct is_bool_filter
+{
+ static constexpr bool value = false;
+};
+
+template <typename F>
+struct is_bool_filter<F,
+ std::enable_if_t<std::is_same_v<
+ std::invoke_result_t<F, const std::string&>, bool>>>
{
+ static constexpr bool value = true;
+};
+}
+#endif
+struct cmJSONHelperBuilder
+{
template <typename T>
class Object
{
@@ -323,13 +344,28 @@ struct cmJSONHelperBuilder
[](const T&) { return true; });
}
- template <typename T, typename F, typename Filter>
- static cmJSONHelper<std::map<std::string, T>> MapFilter(
+ enum class FilterResult
+ {
+ Continue, ///< A filter has accepted a given key (and value)
+ Skip, ///< A filter has rejected a given key (or value)
+ Error ///< A filter has found and reported an error
+ };
+
+ /// Iterate over the object's members and call a filter callable to
+ /// decide what to do with the current key/value.
+ /// A filter returns one of the `FilterResult` values.
+ /// A container type is an associative or a sequence
+ /// container of pairs (key, value).
+ template <typename Container, typename F, typename Filter>
+ static cmJSONHelper<Container> FilteredObject(
const JsonErrors::ErrorGenerator& error, F func, Filter filter)
{
- return [error, func, filter](std::map<std::string, T>& out,
- const Json::Value* value,
+ return [error, func, filter](Container& out, const Json::Value* value,
cmJSONState* state) -> bool {
+ // NOTE Some compile-time code path don't use `filter` at all.
+ // So, suppress "unused lambda capture" warning is needed.
+ static_cast<void>(filter);
+
if (!value) {
out.clear();
return true;
@@ -339,30 +375,94 @@ struct cmJSONHelperBuilder
return false;
}
out.clear();
+ auto outIt = std::inserter(out, out.end());
bool success = true;
for (auto const& key : value->getMemberNames()) {
state->push_stack(key, &(*value)[key]);
- if (!filter(key)) {
- state->pop_stack();
- continue;
- }
- T t;
- if (!func(t, &(*value)[key], state)) {
- success = false;
+#if __cplusplus >= 201703L
+ if constexpr (std::is_same_v<Filter, std::true_type>) {
+ // Filtering functionality isn't needed at all...
+ } else if constexpr (details::is_bool_filter<Filter>::value) {
+ // A given `Filter` is `bool(const std::string&)` callable.
+ if (!filter(key)) {
+ state->pop_stack();
+ continue;
+ }
+ } else {
+#endif
+ // A full-featured `Filter` has been given
+ auto res = filter(key, &(*value)[key], state);
+ if (res == FilterResult::Skip) {
+ state->pop_stack();
+ continue;
+ }
+ if (res == FilterResult::Error) {
+ state->pop_stack();
+ success = false;
+ break;
+ }
+#if __cplusplus >= 201703L
}
- out.emplace(key, std::move(t));
+#endif
+ typename Container::value_type::second_type t;
+ // ATTENTION Call the function first (for it's side-effects),
+ // then accumulate the result!
+ success = func(t, &(*value)[key], state) && success;
+ outIt = typename Container::value_type{ key, std::move(t) };
state->pop_stack();
}
return success;
};
}
+ template <typename T, typename F, typename Filter>
+ static cmJSONHelper<std::map<std::string, T>> MapFilter(
+ const JsonErrors::ErrorGenerator& error, F func, Filter filter)
+ {
+ // clang-format off
+ return FilteredObject<std::map<std::string, T>>(
+ error, func,
+#if __cplusplus >= 201703L
+ // In C++ 17 a filter callable can be passed as is.
+ // Depending on its type `FilteredObject()` will call
+ // it with a key only (backward compatible behavior)
+ // or with 3 args supported by the full-featured
+ // filtering feature.
+ filter
+#else
+ // For C++14 and below, to keep backward compatibility
+ // with CMake Presets code, `MapFilter()` can accept only
+ // `bool(const std::string&)` callables.
+ [filter](const std::string &key, const Json::Value * /*value*/,
+ cmJSONState * /*state*/) -> FilterResult {
+ // Simple adaptor to translate `bool` to `FilterResult`
+ return filter(key) ? FilterResult::Continue : FilterResult::Skip;
+ }
+#endif
+ );
+ // clang-format on
+ }
+
template <typename T, typename F>
static cmJSONHelper<std::map<std::string, T>> Map(
const JsonErrors::ErrorGenerator& error, F func)
{
- return MapFilter<T, F>(error, func,
- [](const std::string&) { return true; });
+ // clang-format off
+ return FilteredObject<std::map<std::string, T>>(
+ error, func,
+#if __cplusplus >= 201703L
+ // With C++ 17 and above, pass a marker type, that no
+ // filtering is needed at all.
+ std::true_type()
+#else
+ // In C++ 14 and below, pass an always-true dummy functor.
+ [](const std::string& /*key*/, const Json::Value* /*value*/,
+ cmJSONState* /*state*/) -> FilterResult {
+ return FilterResult::Continue;
+ }
+#endif
+ );
+ // clang-format on
}
template <typename T, typename F>