Skip to content

Commit 782a64b

Browse files
committed
✨ Add a predicate matcher to fields
Problem: - Custom predicates on fields can be difficult to apply. - Match machinery is not SFINAE-friendly. - Message callbacks require a user-supplied matcher which is often just `always`. Solution: - Allow `with_predicate` on fields which receives the field value, rather than needing to receive the message and extract the field. - Apply SFINAE-friendly return types to `and`, `or` and `not` match combinators. - Allow `msg::callback` to be constructed without an extra matcher, just using the message's built-in matcher.
1 parent 56ed298 commit 782a64b

File tree

8 files changed

+117
-17
lines changed

8 files changed

+117
-17
lines changed

include/match/and.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace match {
1515
template <matcher, matcher> struct or_t;
1616

1717
template <matcher L, matcher R> struct and_t : bin_op_t<and_t, "and", L, R> {
18-
[[nodiscard]] constexpr auto operator()(auto const &event) const -> bool {
18+
[[nodiscard]] constexpr auto operator()(auto const &event) const
19+
-> decltype(this->lhs(event) and this->rhs(event)) {
1920
return this->lhs(event) and this->rhs(event);
2021
}
2122

include/match/not.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ template <matcher M> struct not_t {
2020
using is_matcher = void;
2121
[[no_unique_address]] M m;
2222

23-
[[nodiscard]] constexpr auto operator()(auto const &event) const -> bool {
23+
[[nodiscard]] constexpr auto operator()(auto const &event) const
24+
-> decltype(not m(event)) {
2425
return not m(event);
2526
}
2627

include/match/or.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
#pragma once
23

34
#include <match/bin_op.hpp>
@@ -14,7 +15,8 @@ namespace match {
1415
template <matcher, matcher> struct and_t;
1516

1617
template <matcher L, matcher R> struct or_t : bin_op_t<or_t, "or", L, R> {
17-
[[nodiscard]] constexpr auto operator()(auto const &event) const -> bool {
18+
[[nodiscard]] constexpr auto operator()(auto const &event) const
19+
-> decltype(this->lhs(event) or this->rhs(event)) {
1820
return this->lhs(event) or this->rhs(event);
1921
}
2022

include/msg/callback.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ template <stdx::ct_string Name, typename Msg> struct callback_construct_t {
8686
std::forward<F>(f));
8787
}
8888

89+
template <stdx::callable F>
90+
[[nodiscard]] constexpr auto operator()(F &&f) const {
91+
using matcher_t = typename Msg::matcher_t;
92+
return callback<Name, Msg, matcher_t, std::remove_cvref_t<F>>{
93+
matcher_t{}, std::forward<F>(f)};
94+
}
95+
8996
private:
9097
template <typename N> struct matching_name {
9198
template <typename Field>

include/msg/field.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,10 @@ class field_t : public field_spec_t<Name, T, detail::field_size<Ats...>>,
527527
field_t<Name, T, Default, msg::less_than_or_equal_to_t<field_t, V>,
528528
Ats...>;
529529

530+
template <auto P>
531+
using with_predicate =
532+
field_t<Name, T, Default, msg::pred_matcher_t<field_t, P>, Ats...>;
533+
530534
// ======================================================================
531535
// "const value" for construction and matching
532536
template <T V>

include/msg/field_matchers.hpp

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ template <typename RelOp> constexpr auto to_string() {
130130
return "!="_ctst;
131131
}
132132
}
133+
134+
template <typename Field, typename Msg>
135+
[[nodiscard]] constexpr static auto extract_field(Msg const &msg) {
136+
if constexpr (stdx::range<Msg>) {
137+
return Field::extract(msg);
138+
} else {
139+
return msg.get(Field{});
140+
}
141+
}
133142
} // namespace detail
134143

135144
template <typename RelOp, typename Field, typename Field::type ExpectedValue>
@@ -138,7 +147,7 @@ struct rel_matcher_t {
138147

139148
template <typename MsgType>
140149
[[nodiscard]] constexpr auto operator()(MsgType const &msg) const -> bool {
141-
return RelOp{}(extract_field(msg), ExpectedValue);
150+
return RelOp{}(detail::extract_field<Field>(msg), ExpectedValue);
142151
}
143152

144153
[[nodiscard]] constexpr auto describe() const {
@@ -157,12 +166,12 @@ struct rel_matcher_t {
157166
[[nodiscard]] constexpr auto describe_match(MsgType const &msg) const {
158167
if constexpr (std::integral<typename Field::type>) {
159168
return stdx::ct_format<"{} (0x{:x}) {} 0x{:x}">(
160-
Field::name, extract_field(msg), detail::to_string<RelOp>(),
161-
stdx::ct<ExpectedValue>());
169+
Field::name, detail::extract_field<Field>(msg),
170+
detail::to_string<RelOp>(), stdx::ct<ExpectedValue>());
162171
} else {
163172
return stdx::ct_format<"{} ({}) {} {}">(
164-
Field::name, extract_field(msg), detail::to_string<RelOp>(),
165-
stdx::ct<ExpectedValue>());
173+
Field::name, detail::extract_field<Field>(msg),
174+
detail::to_string<RelOp>(), stdx::ct<ExpectedValue>());
166175
}
167176
}
168177

@@ -180,15 +189,6 @@ struct rel_matcher_t {
180189
return ExpectedValue == OtherValue or
181190
RelOp{}(ExpectedValue, OtherValue);
182191
}
183-
184-
template <typename Msg>
185-
[[nodiscard]] constexpr static auto extract_field(Msg const &msg) {
186-
if constexpr (stdx::range<Msg>) {
187-
return Field::extract(msg);
188-
} else {
189-
return msg.get(Field{});
190-
}
191-
}
192192
};
193193

194194
template <typename Field, auto ExpectedValue>
@@ -341,4 +341,31 @@ template <typename Field>
341341
using equal_default_t = equal_to_t<Field, Field::default_value>;
342342
template <typename Field>
343343
constexpr auto equal_default = equal_default_t<Field>{};
344+
345+
template <typename Field, auto P> struct pred_matcher_t {
346+
using is_matcher = void;
347+
348+
template <typename MsgType>
349+
[[nodiscard]] constexpr auto operator()(MsgType const &msg) const -> bool {
350+
return P(detail::extract_field<Field>(msg));
351+
}
352+
353+
[[nodiscard]] constexpr auto describe() const {
354+
return stdx::ct_format<"<predicate>({})">(Field::name);
355+
}
356+
357+
template <typename MsgType>
358+
[[nodiscard]] constexpr auto describe_match(MsgType const &msg) const {
359+
if constexpr (std::integral<typename Field::type>) {
360+
return stdx::ct_format<"<predicate>({}(0x{:x}))">(
361+
Field::name, detail::extract_field<Field>(msg));
362+
} else {
363+
return stdx::ct_format<"<predicate>({}({}))">(
364+
Field::name, detail::extract_field<Field>(msg));
365+
}
366+
}
367+
};
368+
369+
template <typename Field, auto P>
370+
constexpr auto pred_matcher = pred_matcher_t<Field, P>{};
344371
} // namespace msg

test/msg/callback.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,30 @@ TEST_CASE("alternative matcher syntax (or-combine matcher with matcher maker)",
344344
CHECK(cb2.handle(msg_match));
345345
CHECK(dispatched);
346346
}
347+
348+
namespace {
349+
using extended_msg_defn =
350+
message<"msg", id_field::with_equal_to<0x80>, field1, field2, field3>;
351+
352+
constexpr auto pred = match::predicate([](msg::viewlike auto) { return true; });
353+
} // namespace
354+
355+
TEST_CASE("callback matches message by predicates with different constraints",
356+
"[callback]") {
357+
auto callback = msg::callback<"cb", extended_msg_defn>(pred, [] {});
358+
auto const msg_match = msg::owning<extended_msg_defn>{"id"_field = 0x80};
359+
CHECK(callback.is_match(msg_match));
360+
}
361+
362+
namespace {
363+
constexpr auto field_pred = [](std::uint32_t id) { return id == 0x80; };
364+
} // namespace
365+
366+
TEST_CASE("callback matches message by custom predicate on field",
367+
"[callback]") {
368+
using defn = message<"msg", id_field::with_predicate<field_pred>, field1,
369+
field2, field3>;
370+
auto callback = msg::callback<"cb", defn>([] {});
371+
auto const msg_match = msg::owning<defn>{"id"_field = 0x80};
372+
CHECK(callback.is_match(msg_match));
373+
}

test/msg/field_matchers.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,34 @@ TEST_CASE("not_equal_to X and less_than Y is less_than Y (X >= Y)",
236236
STATIC_REQUIRE(
237237
std::is_same_v<decltype(m), msg::less_than_t<test_field, 6> const>);
238238
}
239+
240+
TEST_CASE("predicate matcher description", "[field matchers]") {
241+
using namespace stdx::literals;
242+
constexpr auto m =
243+
msg::pred_matcher_t<test_field, [](std::uint32_t) { return true; }>{};
244+
constexpr auto desc = m.describe();
245+
STATIC_REQUIRE(desc.str == "<predicate>(test_field)"_ctst);
246+
}
247+
248+
TEST_CASE("predicate matcher description of match", "[field matchers]") {
249+
using namespace stdx::literals;
250+
using msg_data = std::array<std::uint32_t, 1>;
251+
252+
constexpr auto m =
253+
msg::pred_matcher_t<test_field, [](std::uint32_t) { return true; }>{};
254+
constexpr auto desc = m.describe_match(msg_data{0x01ff'ffff});
255+
STATIC_REQUIRE(desc.str == "<predicate>(test_field(0x{:x}))"_ctst);
256+
STATIC_REQUIRE(desc.args == stdx::tuple{1});
257+
}
258+
259+
TEST_CASE("predicate matcher description of match (enum field)",
260+
"[field matchers]") {
261+
using namespace stdx::literals;
262+
using msg_data = std::array<std::uint32_t, 1>;
263+
264+
constexpr auto m =
265+
msg::pred_matcher_t<test_enum_field, [](auto) { return true; }>{};
266+
constexpr auto desc = m.describe_match(msg_data{0x01ff'ffff});
267+
STATIC_REQUIRE(desc.str == "<predicate>(enum_field({}))"_ctst);
268+
STATIC_REQUIRE(desc.args == stdx::tuple{E::B});
269+
}

0 commit comments

Comments
 (0)