Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ $[rng(0, 10)]
$[rng(0, 10, pat::mod::open)]
```

`PTN_BIND` supports one to five names and can be declared at namespace or block
`PTN_BIND` supports one to ten names and can be declared at namespace or block
scope. Use callables for domain logic that does not read naturally as `_` or a
short named-placeholder expression.

Expand Down
71 changes: 48 additions & 23 deletions include/ptn/patternia.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,38 +110,58 @@ namespace ptn {
// A future reflection-based variant may validate this at compile
// time.
//
// Supports 1 to 5 member names.
#define PTN_BIND_1(Type, m0) \
constexpr ::ptn::pat::mod::arg_t<0> m0 { \
}
// Supports 1 to 10 member names.
//
// Implementation notes:
// - PTN_BIND_DECL declares a single named placeholder.
// - PTN_BIND_N is defined by chaining: it expands to
// PTN_BIND_{N-1} plus one more declaration, so adding a new
// arity only costs one short macro instead of a full rewrite.
#define PTN_BIND_DECL(Index, name) \
constexpr ::ptn::pat::mod::arg_t<Index> name{};

#define PTN_BIND_1(Type, m0) PTN_BIND_DECL(0, m0)

#define PTN_BIND_2(Type, m0, m1) \
constexpr ::ptn::pat::mod::arg_t<0> m0{}; \
constexpr ::ptn::pat::mod::arg_t<1> m1 { \
}
PTN_BIND_EXPAND(PTN_BIND_1(Type, m0)) \
PTN_BIND_DECL(1, m1)

#define PTN_BIND_3(Type, m0, m1, m2) \
constexpr ::ptn::pat::mod::arg_t<0> m0{}; \
constexpr ::ptn::pat::mod::arg_t<1> m1{}; \
constexpr ::ptn::pat::mod::arg_t<2> m2 { \
}
PTN_BIND_EXPAND(PTN_BIND_2(Type, m0, m1)) \
PTN_BIND_DECL(2, m2)

#define PTN_BIND_4(Type, m0, m1, m2, m3) \
constexpr ::ptn::pat::mod::arg_t<0> m0{}; \
constexpr ::ptn::pat::mod::arg_t<1> m1{}; \
constexpr ::ptn::pat::mod::arg_t<2> m2{}; \
constexpr ::ptn::pat::mod::arg_t<3> m3 { \
}
PTN_BIND_EXPAND(PTN_BIND_3(Type, m0, m1, m2)) \
PTN_BIND_DECL(3, m3)

#define PTN_BIND_5(Type, m0, m1, m2, m3, m4) \
constexpr ::ptn::pat::mod::arg_t<0> m0{}; \
constexpr ::ptn::pat::mod::arg_t<1> m1{}; \
constexpr ::ptn::pat::mod::arg_t<2> m2{}; \
constexpr ::ptn::pat::mod::arg_t<3> m3{}; \
constexpr ::ptn::pat::mod::arg_t<4> m4 { \
}
PTN_BIND_EXPAND(PTN_BIND_4(Type, m0, m1, m2, m3)) \
PTN_BIND_DECL(4, m4)

#define PTN_BIND_6(Type, m0, m1, m2, m3, m4, m5) \
PTN_BIND_EXPAND(PTN_BIND_5(Type, m0, m1, m2, m3, m4)) \
PTN_BIND_DECL(5, m5)

#define PTN_BIND_7(Type, m0, m1, m2, m3, m4, m5, m6) \
PTN_BIND_EXPAND(PTN_BIND_6(Type, m0, m1, m2, m3, m4, m5)) \
PTN_BIND_DECL(6, m6)

#define PTN_BIND_8(Type, m0, m1, m2, m3, m4, m5, m6, m7) \
PTN_BIND_EXPAND(PTN_BIND_7(Type, m0, m1, m2, m3, m4, m5, m6)) \
PTN_BIND_DECL(7, m7)

#define PTN_BIND_9(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8) \
PTN_BIND_EXPAND(PTN_BIND_8(Type, m0, m1, m2, m3, m4, m5, m6, m7)) \
PTN_BIND_DECL(8, m8)

#define PTN_BIND_10(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9) \
PTN_BIND_EXPAND( \
PTN_BIND_9(Type, m0, m1, m2, m3, m4, m5, m6, m7, m8)) \
PTN_BIND_DECL(9, m9)

#define PTN_BIND_PICK(_1, _2, _3, _4, _5, NAME, ...) NAME
#define PTN_BIND_PICK( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, NAME, ...) \
NAME

// Extra indirection layers for MSVC traditional preprocessor
// compatibility. Without these, MSVC treats __VA_ARGS__ as a single
Expand All @@ -154,6 +174,11 @@ namespace ptn {
#ifndef PTN_BIND
#define PTN_BIND(Type, ...) \
PTN_BIND_DISPATCH(PTN_BIND_EXPAND(PTN_BIND_PICK(__VA_ARGS__, \
PTN_BIND_10, \
PTN_BIND_9, \
PTN_BIND_8, \
PTN_BIND_7, \
PTN_BIND_6, \
PTN_BIND_5, \
PTN_BIND_4, \
PTN_BIND_3, \
Expand Down
70 changes: 70 additions & 0 deletions tests/tests_named_placeholder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ namespace {
int e;
};

struct Deca {
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
};

// Declare named placeholders for each struct.
// These are constexpr arg_t<N> objects.
PTN_BIND(Point, x, y);
Expand All @@ -54,6 +67,7 @@ namespace {
PTN_BIND(Single, sv);
PTN_BIND(Quad, qa, qb, qc, qd);
PTN_BIND(Penta, pa, pb, pc, pd, pe);
PTN_BIND(Deca, da, db, dc, dd, de, df, dg, dh, di, dj);

} // namespace

Expand Down Expand Up @@ -381,3 +395,59 @@ TEST(NamedPlaceholder, BindFourArgGuardFails) {
ptn::_ >> 0);
EXPECT_EQ(result, 0);
}

// =========================================================================
// PTN_BIND arity coverage: 10 member struct (chained expansion).
// =========================================================================

TEST(NamedPlaceholder, TenMemberTypeCorrect) {
static_assert(std::is_same_v<std::decay_t<decltype(da)>,
ptn::pat::mod::arg_t<0>>);
static_assert(std::is_same_v<std::decay_t<decltype(dj)>,
ptn::pat::mod::arg_t<9>>);
}

TEST(NamedPlaceholder, TenMemberGuard) {
// a + b + ... + i == j
Deca d{1, 1, 1, 1, 1, 1, 1, 1, 1, 9};
auto result = ptn::match(d)
| PTN_ON(
ptn::$(
ptn::has<&Deca::a,
&Deca::b,
&Deca::c,
&Deca::d,
&Deca::e,
&Deca::f,
&Deca::g,
&Deca::h,
&Deca::i,
&Deca::j>)[da + db + dc + dd + de
+ df + dg + dh + di
== dj]
>> 1,
ptn::_ >> 0);
EXPECT_EQ(result, 1);
}

TEST(NamedPlaceholder, TenMemberGuardFails) {
Deca d{1, 1, 1, 1, 1, 1, 1, 1, 1, 10};
auto result = ptn::match(d)
| PTN_ON(
ptn::$(
ptn::has<&Deca::a,
&Deca::b,
&Deca::c,
&Deca::d,
&Deca::e,
&Deca::f,
&Deca::g,
&Deca::h,
&Deca::i,
&Deca::j>)[da + db + dc + dd + de
+ df + dg + dh + di
== dj]
>> 1,
ptn::_ >> 0);
EXPECT_EQ(result, 0);
}
Loading