#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include "revng/ADT/KeyedObjectContainer.h" #include "revng/ADT/UpcastablePointer.h" #include "revng/Support/YAMLTraits.h" #include "revng/TupleTree/TupleLikeTraits.h" #include "revng/TupleTree/TupleTreeCompatible.h" #include "revng/TupleTree/TupleTreePath.h" template struct TupleTreeVisitor; // // visitTupleTree implementation // namespace tupletree::detail { template void visitTuple(Visitor &V, T &Obj) { if constexpr (I < std::tuple_size_v) { // Visit the field visitTupleTree(V, get(Obj)); // Visit next element in tuple visitTuple(V, Obj); } } } // namespace tupletree::detail // UpcastablePointerLike-like template void visitTupleTree(Visitor &V, T &Obj) { if (Obj != nullptr) { V.PreVisit(Obj); upcast(Obj, [&V](auto &Upcasted) { visitTupleTree(V, Upcasted); }); V.PostVisit(Obj); } } // Tuple-like template void visitTupleTree(Visitor &V, T &Obj) { V.PreVisit(Obj); tupletree::detail::visitTuple(V, Obj); V.PostVisit(Obj); } // Container-like template void visitTupleTree(Visitor &V, T &Obj) { V.PreVisit(Obj); using value_type = typename T::value_type; for (value_type &Element : Obj) { visitTupleTree(V, Element); } V.PostVisit(Obj); } // All the others template void visitTupleTree(Visitor &V, T &Element) { V.PreVisit(Element); V.PostVisit(Element); } template void visitTupleTree(T &Element, const Pre &PreVisitor, const Post &PostVisitor) { struct { const Pre &PreVisit; const Post &PostVisit; } Visitor{ PreVisitor, PostVisitor }; visitTupleTree(Visitor, Element); } // // `callOnPathSteps` without an instance // template bool callOnPathSteps(Visitor &, llvm::ArrayRef Path) { if (Path.empty()) return true; revng_abort("Unsupported step"); } namespace tupletree::detail { template bool polymorphicTupleImpl(Visitor &V, llvm::ArrayRef Path, KindT Kind) { if constexpr (I < std::tuple_size_v) { if (Path[0].get() == I) { if constexpr (std::is_same_v) V.template visitTupleElement(); else V.template visitPolymorphicElement(Kind); using next_type = typename std::tuple_element::type; return callOnPathSteps(V, Path.slice(1)); } else { return polymorphicTupleImpl(V, Path, Kind); } } return false; } template bool tupleImpl(Visitor &V, llvm::ArrayRef Path) { return polymorphicTupleImpl(V, Path, 0); } } // namespace tupletree::detail template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path) { if (Path.empty()) return true; revng_assert(Path.size() > 1); using KindType = std::decay_t()->Kind())>; KindType Kind = Path[0].get(); auto Dispatcher = [&V, &Path, &Kind](UT &) -> bool { return tupletree::detail::polymorphicTupleImpl(V, Path.slice(1), Kind); }; // Technically changing kind manually is unsafe, but since no-one is ever // going to touch the object (we only care about the type), this is an easy // way to trick "upcast" into doing what we want without introducing more // complexity (or trying to fill in the rest of the key) to do this properly. std::decay_t Temporary; Temporary.Kind() = Kind; return upcast(&Temporary, Dispatcher, false); } template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path) { if (Path.empty()) return true; return tupletree::detail::tupleImpl(V, Path); } template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path) { if (Path.empty()) return true; using value_type = typename RootT::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); auto TargetKey = Path[0].get(); V.template visitContainerElement(TargetKey); using NextStep = std::conditional_t, const typename RootT::value_type, typename RootT::value_type>; return callOnPathSteps(V, Path.slice(1)); } // // `callOnPathSteps` with an instance // template bool callOnPathSteps(Visitor &, llvm::ArrayRef Path, RootT &) { if (Path.empty()) return true; revng_abort("Unsupported step"); } namespace tupletree::detail { template bool polymorphicTupleImpl(Visitor &V, llvm::ArrayRef Path, RootT &M, KindT Kind) { if constexpr (I < std::tuple_size_v) { if (Path[0].get() == I) { auto &Element = get(M); if constexpr (std::is_same_v) V.template visitTupleElement(Element); else V.template visitPolymorphicElement(Kind, Element); using next_type = typename std::tuple_element::type; return callOnPathSteps(V, Path.slice(1), Element); } else { return polymorphicTupleImpl(V, Path, M, Kind); } } return false; } template bool tupleImpl(Visitor &V, llvm::ArrayRef Path, RootT &M) { return polymorphicTupleImpl(V, Path, M, 0); } } // namespace tupletree::detail template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path, RootT &M) { auto Dispatcher = [&V, &Path](UT &Upcasted) -> bool { if (Path.empty()) return true; using KindType = std::decay_t()->Kind())>; return tupletree::detail::polymorphicTupleImpl(V, Path.slice(1), Upcasted, Path[0].get()); }; return upcast(M, Dispatcher, false); } template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path, RootT &M) { if (Path.empty()) return true; return tupletree::detail::tupleImpl(V, Path, M); } template concept HasTryGet = requires(T a) { { &a.tryGet }; }; template bool callOnPathSteps(Visitor &V, llvm::ArrayRef Path, RootT &M) { if (Path.empty()) return true; using value_type = typename RootT::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); auto TargetKey = Path[0].get(); decltype(&*M.find(TargetKey)) Entry; if constexpr (HasTryGet) Entry = M.tryGet(TargetKey); else if (auto Iter = M.find(TargetKey); Iter != M.end()) Entry = &*Iter; else return false; V.template visitContainerElement(TargetKey, *Entry); using NextStep = std::conditional_t, const typename RootT::value_type, typename RootT::value_type>; return callOnPathSteps(V, Path.slice(1), *Entry); } // // `callByPath` without an instance // namespace tupletree::detail { template struct CallByPathVisitor { size_t PathSize; Visitor &V; template void visitTupleElement() { --PathSize; if (PathSize == 0) V.template visitTupleElement(); } template void visitPolymorphicElement(KindType Kind) { PathSize -= 2; if (PathSize == 0) V.template visitPolymorphicElement(Kind); } template void visitContainerElement(KeyT Key) { PathSize -= 1; if (PathSize == 0) V.template visitContainerElement(Key); } }; } // namespace tupletree::detail template bool callByPath(Visitor &V, const TupleTreePath &Path) { using namespace tupletree::detail; CallByPathVisitor CBPV{ Path.size(), V }; return callOnPathSteps(CBPV, Path.toArrayRef()); } // // `callByPath` with an instance // namespace tupletree::detail { template struct CallByPathVisitorWithInstance { size_t PathSize; Visitor &V; template void visitTupleElement(K &Element) { --PathSize; if (PathSize == 0) V.template visitTupleElement(Element); } template void visitPolymorphicElement(KindType Kind, K &Element) { PathSize -= 2; if (PathSize == 0) V.template visitPolymorphicElement(Kind, Element); } template K, typename KeyT> void visitContainerElement(KeyT Key, K &Element) { --PathSize; if (PathSize == 0) V.template visitContainerElement(Key, *Element.get()); } template requires(not StrictSpecializationOf) void visitContainerElement(KeyT Key, K &Element) { --PathSize; if (PathSize == 0) V.template visitContainerElement(Key, Element); } }; } // namespace tupletree::detail template bool callByPath(Visitor &V, const TupleTreePath &Path, RootT &M) { using namespace tupletree::detail; CallByPathVisitorWithInstance CBPV{ Path.size(), V }; return callOnPathSteps(CBPV, Path.toArrayRef(), M); } // // getByPath // namespace tupletree::detail { template constexpr bool IsConst = std::is_const_v>; template using getByPathRV = std::conditional_t, const ResultT, ResultT>; } // namespace tupletree::detail template tupletree::detail::getByPathRV * getByPath(const TupleTreePath &Path, RootT &M); // // pathAsString // namespace tupletree::detail { class DumpPathVisitor { private: llvm::raw_string_ostream Stream; public: DumpPathVisitor(std::string &Result) : Stream(Result) {} template void visitTupleElement() { Stream << "/" << TupleLikeTraits::FieldNames[I]; } template requires(std::is_enum_v) void visitPolymorphicElement(KindType Kind) { Stream << "/" << toString(Kind) << "::" << TupleLikeTraits::FieldNames[I]; } template void visitContainerElement(KeyT Key) { Stream << "/" << getNameFromYAMLScalar(Key); } }; } // namespace tupletree::detail template std::optional pathAsString(const TupleTreePath &Path); // // Path matcher // class PathMatcher { private: TupleTreePath Path; std::vector Free; private: PathMatcher() = default; public: template static std::optional create(llvm::StringRef Path); public: const TupleTreePath &path() const { return Path; } public: template TupleTreePath apply(Ts... Args) const { revng_assert(sizeof...(Args) == Free.size()); TupleTreePath Result = Path; applyImpl<0, Ts...>(Result, Args...); return Result; } template std::optional> match(const TupleTreePath &Search) { revng_assert(sizeof...(Args) == Free.size()); if (Path.size() != Search.size()) return {}; // // Check non-variable parts match // std::vector Terminator{ Path.size() }; size_t LastEnd = 0; for (auto Index : llvm::concat(Free, Terminator)) { for (size_t I = LastEnd; I < Index; ++I) { if (Search[I] != Path[I]) return {}; } LastEnd = Index + 1; } // // Check variable parts match // for (auto I : Free) if (not Path[I].matches(Search[I])) return {}; // // Compute result // std::tuple Result; extractKeys(Search, Result); return Result; } private: template void depositKey(TupleTreePath &Result, T Arg) const { auto Index = Free.at(I); Result[Index] = TupleTreeKeyWrapper(T(Arg)); } template void applyImpl(TupleTreePath &Result, T Arg) const { depositKey(Result, Arg); } template void applyImpl(TupleTreePath &Result, T Arg, Ts... Args) const { depositKey(Result, Arg); applyImpl(Result, Args...); } template void extractKeys(const TupleTreePath &Search, T &Tuple) const { if constexpr (I < std::tuple_size_v) { using element = std::tuple_element_t; std::get(Tuple) = Search[Free[I]].get(); extractKeys(Search, Tuple); } } private: template static bool visitTuple(llvm::StringRef Current, llvm::StringRef Rest, PathMatcher &Result); template static bool visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result); template static bool visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result); template requires(std::is_enum_v) static bool visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result, KindType Kind); template static bool visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result); template static bool visitTupleTreeNode(llvm::StringRef Path, PathMatcher &Result); }; namespace tupletree::details { template bool selectOptionImpl(llvm::StringRef Kind, Visitor &V) { if constexpr (I < std::tuple_size_v) { using ElementT = std::tuple_element_t; if (TupleLikeTraits::Name == Kind) return V.template operator()(); else return selectOptionImpl(Kind, V); } return false; } } // namespace tupletree::details template bool PathMatcher::visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result) { if (String.size() == 0) return true; auto &&[Kind, RHS] = String.split("::"); auto Dispatch = [&RHS, &Result]() { auto &&[Before, After] = RHS.split('/'); return PathMatcher::visitTuple(Before, After, Result); }; using ElementType = typename T::element_type; using KindType = std::decay_t().Kind())>; Result.Path.push_back(getValueFromYAMLScalar(Kind)); using Options = typename concrete_types_traits::type; return tupletree::details::selectOptionImpl(Kind, Dispatch); } template bool PathMatcher::visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result) { if (String.size() == 0) return true; auto &&[Before, After] = String.split('/'); return visitTuple(Before, After, Result); } template bool PathMatcher::visitTupleTreeNode(llvm::StringRef String, PathMatcher &Result) { if (String.size() == 0) return true; auto &&[Before, After] = String.split('/'); using Key = std::remove_cv_t; using Value = typename T::value_type; if constexpr (StrictSpecializationOf) { auto &&[PreDash, PostDash] = Before.split("-"); if (PreDash == "*") { // Mark as free Result.Free.push_back(Result.Path.size()); // // Extract the Kind of the abstract type in // the `model::UpcastableTypeDefinition` // // TODO: Consider using the kind from the next step instead. // Get the kind type for the abstract type using Kind = typename Value::element_type::TypeOfKind; // Extract Kind from "Kind-*" and deserialize it Kind MatcherKind = getValueFromYAMLScalar(PostDash); static_assert(std::is_enum_v>); // Push in Path a Key initializing only the first field (the kind) Key Component; auto &TheKind = std::get - 1>(Component); using KindType = decltype(TheKind); static_assert(std::is_enum_v>); TheKind = MatcherKind; static_assert(Key::LastFieldIsKind); Result.Path.emplace_back(Component); } else { Result.Path.push_back(getValueFromYAMLScalar(Before)); } } else { if (Before == "*") { Result.Free.push_back(Result.Path.size()); Result.Path.emplace_back(); } else { Result.Path.push_back(getValueFromYAMLScalar(Before)); } } return visitTupleTreeNode(After, Result); } template bool PathMatcher::visitTupleTreeNode(llvm::StringRef Path, PathMatcher &Result) { return Path.empty(); } template bool PathMatcher::visitTuple(llvm::StringRef Current, llvm::StringRef Rest, PathMatcher &Result) { if constexpr (I < std::tuple_size_v) { if (TupleLikeTraits::FieldNames[I] == Current) { Result.Path.push_back(size_t(I)); using element = typename std::tuple_element_t; return PathMatcher::visitTupleTreeNode(Rest, Result); } else { return visitTuple(Current, Rest, Result); } } else { // Not found return false; } } template std::optional stringAsPath(llvm::StringRef Path); template ResultT *getByPath(llvm::StringRef Path, RootT &M) { auto MaybeKeyVector = stringAsPath(Path); if (not MaybeKeyVector) return {}; else return getByPath(*MaybeKeyVector, M); } // // Validation // template concept TupleTreeScalar = not TupleSizeCompatible and not KeyedObjectContainer and not UpcastablePointerLike; template constexpr bool validateTupleTree(L); template constexpr bool validateTupleTree(L); template constexpr bool validateTupleTree(L); template constexpr bool validateTupleTree(L); template constexpr bool validateTupleTree(L Check) { // TODO: is there a better way to limit it? return Check((T *) nullptr); } template constexpr bool validateTupleTree(L Check) { return Check((T *) nullptr) and validateTupleTree(Check); } template constexpr bool validateTupleTree(L Check) { return Check((std::remove_const_t *) nullptr); } template constexpr bool validateTupleTree(L Check) { if constexpr (I == 0 and not Check((T *) nullptr)) return false; if constexpr (I < std::tuple_size_v) { if constexpr (not validateTupleTree>(Check)) return false; return validateTupleTree(Check); } return true; } namespace revng { template concept SetOrKOC = StrictSpecializationOf || KeyedObjectContainer; } // namespace revng