From 67553ee7c11be489f6511f1de162f0482612cf78 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:23:45 +0000 Subject: [PATCH] wip transform Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Change-Id: I5db39a2437f1e957d1444cffbc0aecd78e9ad0f5 --- sel/BUILD.bazel | 10 +++ sel/detail/BUILD.bazel | 5 ++ sel/detail/tuple_transform.hpp | 25 ++++++++ sel/detail/tuple_transform_reduce.hpp | 26 ++++---- sel/test/BUILD.bazel | 15 +++++ sel/test/transform_test.cpp | 84 +++++++++++++++++++++++++ sel/transform.hpp | 91 +++++++++++++++++++++++++++ sel/tree.hpp | 15 ++++- 8 files changed, 258 insertions(+), 13 deletions(-) create mode 100644 sel/detail/tuple_transform.hpp create mode 100644 sel/test/transform_test.cpp create mode 100644 sel/transform.hpp diff --git a/sel/BUILD.bazel b/sel/BUILD.bazel index f25b5ab..277c6bb 100644 --- a/sel/BUILD.bazel +++ b/sel/BUILD.bazel @@ -76,6 +76,16 @@ cc_library( ], ) +cc_library( + name = "transform", + hdrs = ["transform.hpp"], + deps = [ + ":tree", + "//sel/detail:tuple_transform", + ], +) + + cc_library( name = "tree", hdrs = ["tree.hpp"], diff --git a/sel/detail/BUILD.bazel b/sel/detail/BUILD.bazel index 9c2a16e..ccca8a3 100644 --- a/sel/detail/BUILD.bazel +++ b/sel/detail/BUILD.bazel @@ -18,6 +18,11 @@ cc_library( hdrs = ["tuple_ref.hpp"], ) +cc_library( + name = "tuple_transform", + hdrs = ["tuple_transform.hpp"], +) + cc_library( name = "tuple_transform_reduce", hdrs = ["tuple_transform_reduce.hpp"], diff --git a/sel/detail/tuple_transform.hpp b/sel/detail/tuple_transform.hpp new file mode 100644 index 0000000..57a8e9a --- /dev/null +++ b/sel/detail/tuple_transform.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +namespace sel::detail { + +/// applies an invocable to each element of a tuple +inline constexpr struct +{ + template + [[nodiscard]] + static constexpr auto operator()(Tuple&& tup, auto transform) + { + using tuple_type = std::remove_cvref_t; + + return [&](std::index_sequence) { + return std::tuple{transform(std::get(std::forward(tup)))...}; + }(std::make_index_sequence>{}); + } +} tuple_transform_reduce{}; + +} // namespace sel::detail diff --git a/sel/detail/tuple_transform_reduce.hpp b/sel/detail/tuple_transform_reduce.hpp index 0bab68a..ee9aa11 100644 --- a/sel/detail/tuple_transform_reduce.hpp +++ b/sel/detail/tuple_transform_reduce.hpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace sel::detail { @@ -9,28 +10,31 @@ namespace sel::detail { /// applies an invocable to each element of two tuples, then reduces inline constexpr struct { - template - requires (sizeof...(T1) == sizeof...(T2)) + template + requires ( + std::tuple_size_v> == + std::tuple_size_v> + ) [[nodiscard]] - static constexpr auto operator()( - const std::tuple& tup1, - const std::tuple& tup2, - T init, - auto reduce, - auto transform - ) -> T + static constexpr auto + operator()(Tuple1&& tup1, Tuple2&& tup2, T init, auto reduce, auto transform) + -> T { return [&](std::index_sequence) { auto _ = {( init = reduce( std::move(init), // - transform(std::get(tup1), std::get(tup2)) + transform( + std::get(std::forward(tup1)), + std::get(std::forward(tup2)) + ) ), true )...}; return init; - }(std::index_sequence_for{}); + }(std::make_index_sequence< + std::tuple_size_v>>{}); } } tuple_transform_reduce{}; diff --git a/sel/test/BUILD.bazel b/sel/test/BUILD.bazel index 0152419..ce64ffe 100644 --- a/sel/test/BUILD.bazel +++ b/sel/test/BUILD.bazel @@ -23,6 +23,21 @@ cc_test( ], ) +cc_test( + name = "transform_test", + size = "small", + srcs = ["transform_test.cpp"], + deps = [ + "//sel:tree", + "//sel:transform", + "//sel:constant", + "//sel:variable", + "//sel:plus", + "//sel:multiplies", + "@skytest", + ], +) + cc_test( name = "tree_test", size = "small", diff --git a/sel/test/transform_test.cpp b/sel/test/transform_test.cpp new file mode 100644 index 0000000..46c4f95 --- /dev/null +++ b/sel/test/transform_test.cpp @@ -0,0 +1,84 @@ +#include "sel/constant.hpp" +#include "sel/multiplies.hpp" +#include "sel/operation.hpp" +#include "sel/plus.hpp" +#include "sel/transform.hpp" +#include "sel/tree.hpp" +#include "sel/variable.hpp" +#include "skytest/skytest.hpp" + +#include +#include + +auto main() -> int +{ + using namespace skytest::literals; + using namespace skytest; + + static constexpr auto x = sel::variable{"x"}; + static constexpr auto y = sel::variable{"y"}; + + static constexpr auto a = sel::constant{1}; + static constexpr auto b = sel::constant{2}; + + "replaces variables with constants"_ctest = [] { + return expect( + eq(a + b, + sel::transform( + sel::tree{x + y}, + sel::equal_to{x + y}.then([](const auto&) { return a + b; }) + )) and // + eq(a + b, + sel::transform(sel::tree{x + y}, sel::equal_to{x + y}.then(a + b))) + ); + }; + + "replaces subexpressions"_ctest = [] { + static constexpr auto f = sel::multiplies{x, x}; + static constexpr auto g = sel::multiplies{x, y}; + return expect(eq( + a + a + g, + sel::transform( + f + f + g, // + sel::equal_to{f}.then(a) + ) + )); + }; + + // "replaces operation types"_ctest = [] { + // return expect(eq( + // sel::plus{sel::plus{sel::plus{sel::constant{0}}}}, + // sel::transform( + // sel::multiplies{sel::plus{sel::multiplies{sel::constant{0}}}}, + // {// + // &sel::tree::is_node, + // [](this const auto& self, const sel::tree::node& + // node) { + // return sel::tree + // { + // sel::node_tag, + // std::from_range, + // node.args | std::views::transform([](const ::sel::tree& + // subtree) { + // return sel::transform(subtree, + // {&sel::tree::is_node, self}); + // }) + // }; + // } + // } + // ) + // )); + // }; + + "expands an expression"_ctest = [] { + static constexpr auto f = x * x; + + return expect(eq( + a + f, + sel::transform( + a + b, // + sel::equal_to{b}.then(f) + ) + )); + }; +} diff --git a/sel/transform.hpp b/sel/transform.hpp new file mode 100644 index 0000000..a4b0d49 --- /dev/null +++ b/sel/transform.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include "sel/leaf.hpp" +#include "sel/operation.hpp" +#include "sel/term.hpp" +#include "sel/tree.hpp" + +#include +#include +#include +#include +#include + +namespace sel { + +template +struct overloads : Ts... +{ + using Ts::operator()...; +}; + +/// apply a transformation to a `tree` +/// @param expr `tree` to transform +/// @param func transformation function +/// @return copy of `expr` with `func` applied to it +/// +/// Applies transformation function `func` to `term`. +/// +/// If `func(expr)` is a valid expression, returns `func(expr)`; otherwise if +/// `expr` is an `operation`,returns `operation{func(args)...}` where `args...` +/// is a pack introduced from `expr.args`. +/// +inline constexpr struct +{ + [[nodiscard]] + constexpr auto + operator()(this const auto& self, const tree& expr, auto func) -> tree + { + if (func.pred(expr)) { + return func.transform(expr); + } + + return expr.node().visit(overloads( + [](const leaf auto& node) -> tree { return node; }, + [transform = std::views::transform(std::bind_back(self, func))]< + class OpNode>(const OpNode& op) -> tree { + return { + std::in_place_type, + std::from_range, + op.args | transform + }; + } + )); + } +} transform{}; + +template +struct conditional_transformer +{ + Pred pred; + Transform transform_; + + [[nodiscard]] + constexpr auto transform(const tree& expr) const -> tree + { + if constexpr (std::convertible_to) { + return transform_; + } else { + return transform_(expr); + } + } +}; + +template +struct equal_to +{ + T value; + + template + [[nodiscard]] + constexpr auto then(const U& transform_) const + { + return conditional_transformer{ + // TODO __cpp_lib_bind_back >= 202306L + std::bind_back(std::equal_to{}, value), // + transform_ + }; + } +}; + +} // namespace sel diff --git a/sel/tree.hpp b/sel/tree.hpp index 27ef06b..44c692d 100644 --- a/sel/tree.hpp +++ b/sel/tree.hpp @@ -30,6 +30,8 @@ class tree template struct op_node { + using op_tag_type = OpTag; + std::vector args; template @@ -68,7 +70,7 @@ class tree /// construct from a `leaf` template requires leaf> - constexpr explicit tree(T&& value) + constexpr tree(T&& value) : value_{std::forward(value)} {} @@ -76,7 +78,7 @@ class tree template requires (not std::same_as>) and operation> - constexpr explicit tree(Op&& expr) + constexpr tree(Op&& expr) : value_{std::make_from_tuple::op_tag>>(std::forward(expr).args)} {} @@ -105,6 +107,15 @@ class tree {} /// @} + /// apply a visitor to the tree node + template + [[nodiscard]] + constexpr auto + node(this Self&& self) -> decltype(std::forward(self).value_) + { + return std::forward(self).value_; + } + /// compare a `tree` with a `leaf` [[nodiscard]] constexpr auto operator==(const leaf auto& other) const -> bool