From 5e88d825e0cfcdf9a615c92418220c87f2c7ff22 Mon Sep 17 00:00:00 2001 From: sentomk Date: Sat, 1 Aug 2026 13:36:40 +0800 Subject: [PATCH] feat(dsl): extend PTN_BIND to 10 names via chained macros Problem - PTN_BIND supported at most 5 member names, which is tight for wider records such as protocol headers. - Each PTN_BIND_N macro restated all declarations, so raising the arity limit meant rewriting every previous level. Implementation - Add PTN_BIND_DECL(Index, name) as the single declaration generator, terminating each declaration itself. - Redefine PTN_BIND_1..PTN_BIND_10 by chaining: PTN_BIND_N expands to PTN_BIND_{N-1} plus one new declaration, so a new arity now costs one short macro instead of a full rewrite. - Extend PTN_BIND_PICK and the count-dispatch table to 10 entries. - Keep PTN_BIND_EXPAND wrapping at every indirection layer for MSVC traditional preprocessor compatibility. Tests - Add Deca fixture covering PTN_BIND at arity 10: placeholder type correctness (arg_t<0>/arg_t<9>) plus pass/fail guard evaluation. - Full suite passes: 182/182, including compile_fail cases. - clang-format check clean on touched files. Notes - No DSL surface change for arity 1-5; existing code keeps working. - docs/api.md updated to state the new one-to-ten limit. --- docs/api.md | 2 +- include/ptn/patternia.hpp | 71 +++++++++++++++++++++---------- tests/tests_named_placeholder.cpp | 70 ++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/docs/api.md b/docs/api.md index 41e7e20..6ec6a53 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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. diff --git a/include/ptn/patternia.hpp b/include/ptn/patternia.hpp index 259c1de..1120a2e 100644 --- a/include/ptn/patternia.hpp +++ b/include/ptn/patternia.hpp @@ -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 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 @@ -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, \ diff --git a/tests/tests_named_placeholder.cpp b/tests/tests_named_placeholder.cpp index b66717a..9703d41 100644 --- a/tests/tests_named_placeholder.cpp +++ b/tests/tests_named_placeholder.cpp @@ -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 objects. PTN_BIND(Point, x, y); @@ -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 @@ -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, + ptn::pat::mod::arg_t<0>>); + static_assert(std::is_same_v, + 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); +}