Warning, file /plasma/lancelot/src/voy/dsl.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2018 Ivan Čukić <ivan.cukic(at)kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #ifndef VOY_DSL_H
0008 #define VOY_DSL_H
0009 
0010 // STL
0011 #include <memory>
0012 #include <tuple>
0013 
0014 // Self
0015 #include "utils.h"
0016 #include "dsl/node_tags.h"
0017 #include "dsl/node_traits.h"
0018 #include "engine/event_loop.h"
0019 
0020 namespace voy::dsl {
0021 
0022 using node_traits::is_connection_expr,
0023       node_traits::is_node,
0024       node_traits::is_source,
0025       node_traits::is_sink,
0026       node_traits::node_category,
0027       node_traits::has_with_continuation;
0028 
0029 // We need to be able to store the connected graph paths (pipelines)
0030 // inside of ordinary classes, so we will return a type-erased pipeline
0031 // to the caller once the path is complete
0032 class pipeline {
0033 public:
0034     virtual ~pipeline() {}
0035     virtual void init() = 0;
0036 
0037     using ptr = std::unique_ptr<pipeline>;
0038 };
0039 
0040 namespace detail {
0041     // The actual type of a pipeline
0042     template <typename T>
0043     struct pipeline_impl: pipeline {
0044         explicit pipeline_impl(T&& content) noexcept
0045             : node{std::move(content.node)}
0046         {
0047         }
0048 
0049         void init() override
0050         {
0051             node.init();
0052         }
0053 
0054         T node;
0055     };
0056 
0057 
0058 
0059     // In order to use the fold expressions, we need to provide an operator
0060     // on which to fold. We can create a wrapper type with operator%
0061     // defined for it
0062     template <typename Node>
0063     struct node_wrapper: utils::non_copyable {
0064         voy_assert_value_type(Node);
0065 
0066         node_wrapper(Node node)
0067             : node{std::move(node)}
0068         {
0069         }
0070 
0071         node_wrapper(node_wrapper&& other) = delete;
0072         void operator=(node_wrapper&&) = delete;
0073 
0074         void init()
0075         {
0076             node.init();
0077         }
0078 
0079         Node node;
0080     };
0081 
0082 
0083     template <typename Node>
0084     auto make_node_wrapper(Node&& node)
0085     {
0086         voy_assert_value_type(Node);
0087         return node_wrapper<Node>{std::move(node)};
0088     }
0089 
0090 
0091     template <typename Left, typename Right>
0092     decltype(auto) operator% (node_wrapper<Left>&& receiver,
0093                               node_wrapper<Right>&& sender)
0094     {
0095         voy_assert_value_type(Left);
0096         voy_assert_value_type(Right);
0097 
0098         return make_node_wrapper(
0099                 std::move(sender.node).with_continuation(std::move(receiver.node))
0100             );
0101     }
0102 
0103 
0104     // Goes through all the items in a tuple, and connects one by one
0105     template <typename... Args, size_t... Idx>
0106     pipeline::ptr connect_all(std::tuple<Args...> items,
0107                               std::index_sequence<Idx...>)
0108     {
0109         return std::make_unique<
0110                 decltype(pipeline_impl((... % make_node_wrapper(std::get<Idx>(std::move(items))))))
0111             > ((... % make_node_wrapper(std::get<Idx>(std::move(items)))));
0112     }
0113 
0114 
0115 
0116     // The connection_expr class represents one pipe operation
0117     // in the AST where the left and right arguments can be either
0118     // compound expressions themselves, or single nodes
0119     template <typename NodeCategory, typename LeftGraph, typename RightGraph>
0120     struct connection_expr {
0121         voy_assert_value_type(LeftGraph);
0122         voy_assert_value_type(RightGraph);
0123 
0124         LeftGraph left;
0125         RightGraph right;
0126 
0127         // An expression is also a graph node
0128         using node_category = NodeCategory;
0129         // ... but we still need to be able to differentiate it from normal nodes
0130         using connection_expr_tag = node_category;
0131 
0132         // Generates a tuple of all nodes in an expression from right to left,
0133         // that is, from the sink to the source
0134         auto collect_graph_nodes()
0135         {
0136             auto collect_left_graph_nodes = [this]
0137             {
0138                 if constexpr (is_connection_expr<LeftGraph>) {
0139                     return left.collect_graph_nodes();
0140                 } else {
0141                     return std::make_tuple(std::move(left));
0142                 }
0143             };
0144 
0145             auto collect_right_graph_nodes = [this]
0146             {
0147                 if constexpr (is_connection_expr<RightGraph>) {
0148                     return right.collect_graph_nodes();
0149                 } else {
0150                     return std::make_tuple(std::move(right));
0151                 }
0152             };
0153 
0154             return std::tuple_cat(collect_right_graph_nodes(),
0155                                   collect_left_graph_nodes());
0156         }
0157 
0158         connection_expr(LeftGraph left, RightGraph right)
0159             : left{std::move(left)}
0160             , right{std::move(right)}
0161         {
0162         }
0163 
0164         auto evaluate()
0165         {
0166             if constexpr (std::is_same_v<void, node_category>) {
0167                 auto sink_to_source_items = collect_graph_nodes();
0168 
0169                 auto result = connect_all(std::move(sink_to_source_items),
0170                         std::make_index_sequence<
0171                             std::tuple_size_v<
0172                                 std::decay_t<decltype(sink_to_source_items)>
0173                             >
0174                         >());
0175 
0176                 voy::event_loop::invoke_later([result = result.get()] {
0177                         result->init();
0178                     });
0179 
0180                 return result;
0181 
0182             } else {
0183                 voy_fail(node_category, "'evaluate' can only be called on a complete path");
0184             }
0185         }
0186     };
0187 
0188 
0189     template < typename NodeCategory
0190              , typename LeftGraph
0191              , typename RightGraph
0192              >
0193     auto make_connection_expr(LeftGraph&& left, RightGraph&& right)
0194     {
0195         return connection_expr < NodeCategory
0196                                , traits::remove_cvref_t<LeftGraph>
0197                                , traits::remove_cvref_t<RightGraph>
0198                                > { voy_fwd(left), voy_fwd(right) };
0199     }
0200 
0201 
0202     template < typename Left
0203              , typename Right
0204              , typename LeftVal  = traits::remove_cvref_t<Left>
0205              , typename RightVal = traits::remove_cvref_t<Right>
0206              >
0207     decltype(auto) connect_streams(Left&& left, Right&& right)
0208     {
0209         static_assert(is_node<Left>, "The left needs to be a node");
0210         static_assert(is_node<Right>, "The right needs to be a node");
0211         static_assert(
0212                 is_connection_expr<Left> ||
0213                 has_with_continuation<Left>,
0214                 "The left node needs to be a connection expression, or to have with_continuation member function");
0215 
0216         #define MAKE(Type)                                                     \
0217             make_connection_expr<Type, LeftVal, RightVal>(std::move(left),     \
0218                                                           std::move(right))
0219 
0220         if constexpr (!is_source<Left> && !is_sink<Right>) {
0221 
0222             return MAKE(transformation_node_tag);
0223 
0224         } else if constexpr (is_source<Left> && !is_sink<Right>) {
0225 
0226             return MAKE(node_category<Left>);
0227 
0228         } else if constexpr (!is_source<Left> && is_sink<Right>) {
0229 
0230             return MAKE(node_category<Right>);
0231 
0232         } else {
0233 
0234             // If we have both a sink and a source, we can connect the
0235             // nodes in this path of the graph
0236             return MAKE(void).evaluate();
0237 
0238         }
0239 
0240         #undef MAKE
0241     }
0242 } // namespace detail
0243 
0244 
0245 template < typename Left
0246          , typename Right
0247          , voy_require(
0248                is_node<Left> && is_node<Right>
0249            )
0250          >
0251 [[nodiscard]] decltype(auto) operator| (Left&& left, Right&& right)
0252 {
0253     return detail::connect_streams(voy_fwd(left), voy_fwd(right));
0254 }
0255 
0256 
0257 } // namespace voy::dsl
0258 
0259 #endif // include guard
0260