Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sel/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
5 changes: 5 additions & 0 deletions sel/detail/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
25 changes: 25 additions & 0 deletions sel/detail/tuple_transform.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

namespace sel::detail {

/// applies an invocable to each element of a tuple
inline constexpr struct
{
template <class Tuple>
[[nodiscard]]
static constexpr auto operator()(Tuple&& tup, auto transform)
{
using tuple_type = std::remove_cvref_t<Tuple>;

return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return std::tuple{transform(std::get<Is>(std::forward<Tuple>(tup)))...};
}(std::make_index_sequence<std::tuple_size_v<tuple_type>>{});
}
} tuple_transform_reduce{};

} // namespace sel::detail
26 changes: 15 additions & 11 deletions sel/detail/tuple_transform_reduce.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,39 @@

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

namespace sel::detail {

/// applies an invocable to each element of two tuples, then reduces
inline constexpr struct
{
template <class... T1, class... T2, class T>
requires (sizeof...(T1) == sizeof...(T2))
template <class Tuple1, class Tuple2, class T>
requires (
std::tuple_size_v<std::remove_cvref_t<Tuple1>> ==
std::tuple_size_v<std::remove_cvref_t<Tuple2>>
)
[[nodiscard]]
static constexpr auto operator()(
const std::tuple<T1...>& tup1,
const std::tuple<T2...>& 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::size_t... Is>(std::index_sequence<Is...>) {
auto _ = {(
init = reduce(
std::move(init), //
transform(std::get<Is>(tup1), std::get<Is>(tup2))
transform(
std::get<Is>(std::forward<Tuple1>(tup1)),
std::get<Is>(std::forward<Tuple2>(tup2))
)
),
true
)...};

return init;
}(std::index_sequence_for<T1...>{});
}(std::make_index_sequence<
std::tuple_size_v<std::remove_cvref_t<Tuple1>>>{});
}
} tuple_transform_reduce{};

Expand Down
15 changes: 15 additions & 0 deletions sel/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
84 changes: 84 additions & 0 deletions sel/test/transform_test.cpp
Original file line number Diff line number Diff line change
@@ -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 <cstddef>
#include <utility>

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<sel::plus>,
// [](this const auto& self, const sel::tree::node<sel::plus>&
// node) {
// return sel::tree
// {
// sel::node_tag<sel::plus>,
// std::from_range,
// node.args | std::views::transform([](const ::sel::tree&
// subtree) {
// return sel::transform(subtree,
// {&sel::tree::is_node<sel::plus>, 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)
)
));
};
}
91 changes: 91 additions & 0 deletions sel/transform.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#pragma once

#include "sel/leaf.hpp"
#include "sel/operation.hpp"
#include "sel/term.hpp"
#include "sel/tree.hpp"

#include <concepts>
#include <cstddef>
#include <functional>
#include <ranges>
#include <tuple>

namespace sel {

template <class... Ts>
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<typename OpNode::op_tag_type>,
std::from_range,
op.args | transform
};
}
));
}
} transform{};

template <class Pred, class Transform>
struct conditional_transformer
{
Pred pred;
Transform transform_;

[[nodiscard]]
constexpr auto transform(const tree& expr) const -> tree
{
if constexpr (std::convertible_to<Transform, tree>) {
return transform_;
} else {
return transform_(expr);
}
}
};

template <class T>
struct equal_to
{
T value;

template <class U>
[[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
15 changes: 13 additions & 2 deletions sel/tree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class tree
template <class OpTag>
struct op_node
{
using op_tag_type = OpTag;

std::vector<tree> args;

template <class... Args>
Expand Down Expand Up @@ -68,15 +70,15 @@ class tree
/// construct from a `leaf`
template <class T>
requires leaf<std::remove_cvref_t<T>>
constexpr explicit tree(T&& value)
constexpr tree(T&& value)
: value_{std::forward<T>(value)}
{}

/// construct from an `operation`
template <class Op>
requires (not std::same_as<tree, std::remove_cvref_t<Op>>) and
operation<std::remove_cvref_t<Op>>
constexpr explicit tree(Op&& expr)
constexpr tree(Op&& expr)
: value_{std::make_from_tuple<op_node<typename std::remove_cvref_t<
Op>::op_tag>>(std::forward<Op>(expr).args)}
{}
Expand Down Expand Up @@ -105,6 +107,15 @@ class tree
{}
/// @}

/// apply a visitor to the tree node
template <class Self>
[[nodiscard]]
constexpr auto
node(this Self&& self) -> decltype(std::forward<Self>(self).value_)
{
return std::forward<Self>(self).value_;
}

/// compare a `tree` with a `leaf`
[[nodiscard]]
constexpr auto operator==(const leaf auto& other) const -> bool
Expand Down
Loading