diff --git a/CMakeLists.txt b/CMakeLists.txt index f00c8b8b..e6d40262 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,9 @@ PTN_JOIN_HEADERS(PTN_HEADERS include/ptn/pattern/structural.hpp include/ptn/pattern/combinator.hpp include/ptn/pattern/pred.hpp + include/ptn/pattern/negation.hpp include/ptn/pattern/modifiers/guard.hpp + include/ptn/pattern/modifiers/placeholder.hpp include/ptn/pattern/modifiers/fwd.h ) diff --git a/README.md b/README.md index 5e02ae1b..bad8c67b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ int classify(int x) { - Literal, structural, and `std::variant` matching in one DSL. - Explicit binding through `$` and `$(...)`. -- Declarative guards via `PTN_LET`, `PTN_WHERE`, `_0`, `arg`, `rng(...)`, and callables. +- Declarative guards via `_`, `PTN_BIND`, `rng(...)`, and callables. - No RTTI, no virtual dispatch, no heap allocation. - Static literal and variant dispatch lowering for hot paths. @@ -70,8 +70,8 @@ using namespace ptn; const char *bucket(int x) { return match(x) | on( - $[PTN_LET(value, value < 0)] >> "negative", - $[PTN_LET(value, value < 10)] >> "small", + $[_ < 0] >> "negative", + $[_ < 10] >> "small", _ >> "large" ); } diff --git a/bench/bench_scale.cpp b/bench/bench_scale.cpp index 0b049f61..a545bd11 100644 --- a/bench/bench_scale.cpp +++ b/bench/bench_scale.cpp @@ -216,7 +216,7 @@ namespace { is() >> 2, is() >> 3, is() >> 4, - __ >> 0); + _ >> 0); } int scaleN_ptn8(const Evt8 &e) { @@ -231,7 +231,7 @@ namespace { is() >> 6, is() >> 7, is() >> 8, - __ >> 0); + _ >> 0); } int scaleN_ptn16(const Evt16 &e) { @@ -254,7 +254,7 @@ namespace { is() >> 14, is() >> 15, is() >> 16, - __ >> 0); + _ >> 0); } int scaleN_ptn32(const Evt32 &e) { @@ -293,7 +293,7 @@ namespace { is() >> 30, is() >> 31, is() >> 32, - __ >> 0); + _ >> 0); } // ---- StdVisit dispatchers ---- diff --git a/bench/bench_suite.cpp b/bench/bench_suite.cpp index e0cda2fa..e5fa56db 100644 --- a/bench/bench_suite.cpp +++ b/bench/bench_suite.cpp @@ -194,7 +194,7 @@ namespace { using ptn::pat::is; return match(v) - | on(is() >> 1, is() >> 2, __ >> 0); + | on(is() >> 1, is() >> 2, _ >> 0); } static int patternia_pipe_variant_route(const V &v) { @@ -202,13 +202,13 @@ namespace { using ptn::pat::is; return match(v) - | on(is() >> 1, is() >> 2, __ >> 0); + | on(is() >> 1, is() >> 2, _ >> 0); } static int patternia_pipe_variant_alt_route(const V &v) { using namespace ptn; - return match(v) | on(alt<0>() >> 1, alt<1>() >> 2, __ >> 0); + return match(v) | on(alt<0>() >> 1, alt<1>() >> 2, _ >> 0); } static int patternia_pipe_variant_alt_32_route(const VAlt32 &v) { @@ -247,7 +247,7 @@ namespace { alt<29>() >> 30, alt<30>() >> 31, alt<31>() >> 32, - __ >> 0); + _ >> 0); } static int @@ -286,7 +286,7 @@ namespace { alt<29>() >> 30, alt<30>() >> 31, alt<31>() >> 32, - __ >> 0); + _ >> 0); return match(v) | cases; } @@ -407,11 +407,11 @@ namespace { }; return match(v) - | on($(is())[_0 > 100] >> 10, + | on($(is())[_ > 100] >> 10, is() >> 1, $(is())[long_string] >> 20, is() >> 2, - __ >> 0); + _ >> 0); } static int patternia_pipe_variant_guarded_route(const V &v) { @@ -421,11 +421,11 @@ namespace { }; return match(v) - | on($(is())[_0 > 100] >> 10, + | on($(is())[_ > 100] >> 10, is() >> 1, $(is())[long_string] >> 20, is() >> 2, - __ >> 0); + _ >> 0); } static int std_visit_variant_guarded_route(const V &v) { @@ -510,7 +510,7 @@ namespace { is() >> 3, $(is())[control_ack] >> 44, is() >> 4, - __ >> 0); + _ >> 0); } static int patternia_pipe_protocol_router(const ProtocolMsg &msg) { @@ -533,7 +533,7 @@ namespace { is() >> 3, $(is())[control_ack] >> 44, is() >> 4, - __ >> 0); + _ >> 0); } static int if_else_protocol_router(const ProtocolMsg &msg) { @@ -634,7 +634,7 @@ namespace { is() >> 300, $(is())[wide_scan] >> 401, is() >> 400, - __ >> 0); + _ >> 0); } static int patternia_pipe_command_parser(const CommandMsg &msg) { @@ -657,7 +657,7 @@ namespace { is() >> 300, $(is())[wide_scan] >> 401, is() >> 400, - __ >> 0); + _ >> 0); } static int if_else_command_parser(const CommandMsg &msg) { @@ -750,7 +750,7 @@ namespace { lit(6) >> 6, lit(7) >> 7, lit(8) >> 8, - __ >> 0); + _ >> 0); } static int patternia_literal_match_route(int x) { @@ -765,7 +765,7 @@ namespace { lit(6) >> 6, lit(7) >> 7, lit(8) >> 8, - __ >> 0); + _ >> 0); } static int if_else_literal_match_route(int x) { @@ -877,7 +877,7 @@ namespace { static int patternia_pipe_literal_match_16_route(int x) { using namespace ptn; - return match(x) | on(PTN_RT_LIT_BLOCK_16(1), __ >> 0); + return match(x) | on(PTN_RT_LIT_BLOCK_16(1), _ >> 0); } static int if_else_literal_match_16_route(int x) { @@ -900,7 +900,7 @@ namespace { return match(x) | on(PTN_RT_LIT_BLOCK_16(1), PTN_RT_LIT_BLOCK_16(17), - __ >> 0); + _ >> 0); } static int if_else_literal_match_32_route(int x) { @@ -927,7 +927,7 @@ namespace { PTN_RT_LIT_BLOCK_16(17), PTN_RT_LIT_BLOCK_16(33), PTN_RT_LIT_BLOCK_16(49), - __ >> 0); + _ >> 0); } static int if_else_literal_match_64_route(int x) { @@ -971,7 +971,7 @@ namespace { ptn::lit(84) >> 84, ptn::lit(91) >> 91, ptn::lit(98) >> 98, - __ >> 0); + _ >> 0); } static int if_else_literal_match_rdense_route(int x) { @@ -1090,7 +1090,7 @@ namespace { PTN_LIT_BLOCK_16(81), PTN_LIT_BLOCK_16(97), PTN_LIT_BLOCK_16(113), - __ >> 0); + _ >> 0); return match(x) | cases; } @@ -1107,7 +1107,7 @@ namespace { PTN_LIT_BLOCK_16(81), PTN_LIT_BLOCK_16(97), PTN_LIT_BLOCK_16(113), - __ >> 0); + _ >> 0); } static int patternia_pipe_literal_match_128_on_macro_route(int x) { @@ -1122,7 +1122,7 @@ namespace { PTN_LIT_BLOCK_16(81), PTN_LIT_BLOCK_16(97), PTN_LIT_BLOCK_16(113), - __ >> 0); + _ >> 0); } static int switch_literal_match_128_route(int x) { @@ -1242,7 +1242,7 @@ namespace { [is_valid_data_packet] >> 2, $(has<&Packet::type>)[is_error_packet] >> 3, - __ >> 0); + _ >> 0); } static int patternia_pipe_packet_route(const Packet &pkt) { @@ -1272,7 +1272,7 @@ namespace { [is_valid_data_packet] >> 2, $(has<&Packet::type>)[is_error_packet] >> 3, - __ >> 0); + _ >> 0); } static int switch_packet_route(const Packet &pkt) { @@ -1335,7 +1335,7 @@ namespace { $(has<&Packet::type, &Packet::payload>)[is_error_packet] >> 3, - __ >> 0); + _ >> 0); } static int @@ -1375,7 +1375,7 @@ namespace { $(has<&Packet::type, &Packet::payload>)[is_error_packet] >> 3, - __ >> 0); + _ >> 0); } static int switch_packet_heavy_bind_route(const Packet &pkt) { diff --git a/bench/compile-time/CMakeLists.txt b/bench/compile-time/CMakeLists.txt index 7b912040..6676afd7 100644 --- a/bench/compile-time/CMakeLists.txt +++ b/bench/compile-time/CMakeLists.txt @@ -26,7 +26,7 @@ function(_ptn_ct_add_lit_tu N) foreach(_i RANGE 0 ${_last} 1) string(APPEND _cases " ptn::lit(${_i}) >> ${_i},\n") endforeach() - string(APPEND _cases " __ >> 0);\n") + string(APPEND _cases " _ >> 0);\n") file(APPEND "${_tufile}" "${_cases}") file(APPEND "${_tufile}" "}\n\n") diff --git a/bench/compile-time/ct_bench_compound.cpp b/bench/compile-time/ct_bench_compound.cpp index 6512fdcf..8de33678 100644 --- a/bench/compile-time/ct_bench_compound.cpp +++ b/bench/compile-time/ct_bench_compound.cpp @@ -14,13 +14,13 @@ namespace { ptn::lit(2) >> 2, ptn::lit(3) >> 3, ptn::lit(4) >> 4, - __ >> 0); + _ >> 0); } int ct_combo_variant(const VariantT &v) { using namespace ptn; return match(v) - | on(is() >> 100, is() >> 200, __ >> 0); + | on(is() >> 100, is() >> 200, _ >> 0); } int (*volatile sink_lit)(int) = ct_combo_lit; diff --git a/bench/compile-time/ct_bench_lit_128.cpp b/bench/compile-time/ct_bench_lit_128.cpp index c7754b9d..7bae0fc7 100644 --- a/bench/compile-time/ct_bench_lit_128.cpp +++ b/bench/compile-time/ct_bench_lit_128.cpp @@ -134,7 +134,7 @@ constexpr int ct_lit_128(int v) noexcept { ptn::lit(125) >> 125, ptn::lit(126) >> 126, ptn::lit(127) >> 127, - __ >> 0); + _ >> 0); } static_assert(ct_lit_128(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_16.cpp b/bench/compile-time/ct_bench_lit_16.cpp index 36b9d696..ee50d4f9 100644 --- a/bench/compile-time/ct_bench_lit_16.cpp +++ b/bench/compile-time/ct_bench_lit_16.cpp @@ -22,7 +22,7 @@ constexpr int ct_lit_16(int v) noexcept { ptn::lit(13) >> 13, ptn::lit(14) >> 14, ptn::lit(15) >> 15, - __ >> 0); + _ >> 0); } static_assert(ct_lit_16(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_32.cpp b/bench/compile-time/ct_bench_lit_32.cpp index 3221391b..c5222047 100644 --- a/bench/compile-time/ct_bench_lit_32.cpp +++ b/bench/compile-time/ct_bench_lit_32.cpp @@ -38,7 +38,7 @@ constexpr int ct_lit_32(int v) noexcept { ptn::lit(29) >> 29, ptn::lit(30) >> 30, ptn::lit(31) >> 31, - __ >> 0); + _ >> 0); } static_assert(ct_lit_32(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_64.cpp b/bench/compile-time/ct_bench_lit_64.cpp index fe78b188..61c8ba92 100644 --- a/bench/compile-time/ct_bench_lit_64.cpp +++ b/bench/compile-time/ct_bench_lit_64.cpp @@ -70,7 +70,7 @@ constexpr int ct_lit_64(int v) noexcept { ptn::lit(61) >> 61, ptn::lit(62) >> 62, ptn::lit(63) >> 63, - __ >> 0); + _ >> 0); } static_assert(ct_lit_64(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_8.cpp b/bench/compile-time/ct_bench_lit_8.cpp index 752afae4..fdfad301 100644 --- a/bench/compile-time/ct_bench_lit_8.cpp +++ b/bench/compile-time/ct_bench_lit_8.cpp @@ -14,7 +14,7 @@ constexpr int ct_lit_8(int v) noexcept { ptn::lit(5) >> 5, ptn::lit(6) >> 6, ptn::lit(7) >> 7, - __ >> 0); + _ >> 0); } static_assert(ct_lit_8(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_rdense.cpp b/bench/compile-time/ct_bench_lit_rdense.cpp index a8fbc55c..8060c539 100644 --- a/bench/compile-time/ct_bench_lit_rdense.cpp +++ b/bench/compile-time/ct_bench_lit_rdense.cpp @@ -19,7 +19,7 @@ namespace { ptn::lit(84) >> 84, ptn::lit(91) >> 91, ptn::lit(98) >> 98, - __ >> 0); + _ >> 0); } static_assert(ct_lit_rdense(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_variant_32.cpp b/bench/compile-time/ct_bench_variant_32.cpp index ae7382dd..db86cc59 100644 --- a/bench/compile-time/ct_bench_variant_32.cpp +++ b/bench/compile-time/ct_bench_variant_32.cpp @@ -77,7 +77,7 @@ namespace { alt<29>() >> 30, alt<30>() >> 31, alt<31>() >> 32, - __ >> 0); + _ >> 0); } int (*volatile sink)(const VAlt32 &) = ct_var_alt_route; diff --git a/docs/api.md b/docs/api.md index 8b9b43fe..41e7e204 100644 --- a/docs/api.md +++ b/docs/api.md @@ -174,7 +174,7 @@ Attach a guard to a binding pattern with `pattern[guard]`. ```cpp match(x) | on( - $[PTN_LET(value, value > 0 && value < 10)] >> "small", + $[_ > 0 && _ < 10] >> "small", _ >> "other" ); ``` @@ -192,23 +192,26 @@ A guard failure only rejects the current case. ## Guard Helpers {#guard-helpers} -### `_0` +### `_` Placeholder alias for a single bound value. ```cpp -$[_0 > 5] +$[_ > 5] ``` -Use `_0` when one binding is enough and the predicate reads clearly without an +Use `_` when one binding is enough and the predicate reads clearly without an explicit name. -### `arg` +### `PTN_BIND(Type, names...)` -Indexed placeholder for general multi-binding guards. +Declares readable placeholders for multi-binding guards. List the names in the +same order as the members passed to `has<>`. ```cpp -$(has<&Point::x, &Point::y>)[arg<0> * arg<0> + arg<1> * arg<1> == 25] +PTN_BIND(Point, x, y); + +$(has<&Point::x, &Point::y>)[x * x + y * y == 25] ``` ### `rng(lo, hi, mode)` @@ -220,38 +223,25 @@ $[rng(0, 10)] $[rng(0, 10, pat::mod::open)] ``` -Use callables for domain logic that does not read naturally as `_0`, -`arg`, or `PTN_WHERE(...)`. +`PTN_BIND` supports one to five 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. -### `PTN_WHERE((names...), expr)` +### Migration from positional guard APIs -Use named guard parameters without writing the lambda yourself. -The macro currently supports 1 to 5 names. +The guard surface now uses `_` for one bound value and `PTN_BIND` names for +multiple bound values. The former `__`, `_0`, `arg`, `PTN_LET`, and +`PTN_WHERE` spellings have been removed rather than retained as aliases. ```cpp -match(p) | on( - $(has<&Point::x, &Point::y>)[PTN_WHERE((x, y), x == y)] >> "diagonal", - _ >> "other" -); -``` - -`PTN_WHERE(...)` expands to a guard callable and composes with `&&` / `||` -like other guard predicates. - -### `PTN_LET(name, expr)` +// Single value +$[_ > 0] -Use the single-value form when a guard binds exactly one value and you want to - name it explicitly. - -```cpp -match(x) | on( - $[PTN_LET(value, value > 0 && value < 10)] >> "small", - _ >> "other" -); +// Multiple values +PTN_BIND(Point, x, y); +$(has<&Point::x, &Point::y>)[x < y] ``` -`PTN_LET(name, expr)` is equivalent to `PTN_WHERE((name), expr)`. - --- ## Structural Matching `has<&T::member...>` {#structural-matching} @@ -411,17 +401,18 @@ The factory must be stateless. ## Namespace Summary {#namespace-summary} -The public surface is re-exported through `namespace ptn`: +The public surface is available through `namespace ptn`, plus the macros listed +below: - `match` - `on` - `lit`, `val`, `lit_ci` - `$` -- `_` -- `_0`, `arg`, `rng` +- `_`, `rng` - `has` - `is`, `alt` - `any`, `all`, `neg` +- `PTN_ON`, `PTN_BIND` (macros) --- diff --git a/docs/changelog/v0.5.3.md b/docs/changelog/v0.5.3.md index 2eceda41..b7a352dd 100644 --- a/docs/changelog/v0.5.3.md +++ b/docs/changelog/v0.5.3.md @@ -101,5 +101,5 @@ const char *classify(int value) { ## Historical Note The original v0.5.3 note referenced the earlier single-value guard placeholder -form. Current code should use `_0` for single-value guards and `arg` for -multi-value guards. +form. Current code should use `_` for single-value guards and `PTN_BIND` names +for multi-value guards. diff --git a/docs/design-overview.md b/docs/design-overview.md index 3f09bae4..be87958f 100644 --- a/docs/design-overview.md +++ b/docs/design-overview.md @@ -85,7 +85,7 @@ Guards apply after a binding pattern succeeds. ```cpp match(x) | on( - $[PTN_LET(value, value > 0 && value < 10)] >> "small", + $[_ > 0 && _ < 10] >> "small", _ >> "other" ); ``` @@ -128,7 +128,7 @@ match(value) | on( $(has<&Point::x, &Point::y>) >> [](int x, int y) { return x + y > 0 ? "point" : "origin-side"; }, - $(is)[PTN_LET(text, text != "")] >> "string", + $(is)[_ != ""] >> "string", _ >> "other" ); ``` diff --git a/docs/guide/common-mistakes.md b/docs/guide/common-mistakes.md index 50e6ea39..8875d73b 100644 --- a/docs/guide/common-mistakes.md +++ b/docs/guide/common-mistakes.md @@ -65,7 +65,7 @@ The `PTN_ON` macro caches the entire matcher in a function-local static variable ```cpp int search(int x, int limit) { return match(x) | PTN_ON( - $[PTN_LET(v, v < limit)] >> [] { return true; }, // Error: 'limit' cannot be captured + $[_ < limit] >> [] { return true; }, // Error: 'limit' cannot be captured _ >> [] { return false; } ); } @@ -78,7 +78,7 @@ int search(int x, int limit) { ```cpp return match(x) | on( - $[PTN_LET(v, v < limit)] >> [] { return true; }, + $[_ < limit] >> [] { return true; }, _ >> [] { return false; } ); ``` diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index f8ebe5b5..17e0709b 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -98,13 +98,13 @@ using namespace ptn; const char *bucket(int x) { return match(x) | on( - $[PTN_LET(value, value > 0 && value < 10)] >> "small", + $[_ > 0 && _ < 10] >> "small", _ >> "other" ); } ``` -For multiple bound values, use `arg` or `PTN_WHERE((...), expr)`: +For multiple bound values, declare readable placeholders with `PTN_BIND`: ```cpp using namespace ptn; @@ -114,21 +114,18 @@ struct Point { int y; }; +PTN_BIND(Point, x, y); + bool on_unit_circle(const Point &p) { return match(p) | on( - $(has<&Point::x, &Point::y>)[arg<0> * arg<0> + arg<1> * arg<1> == 1] - >> true, + $(has<&Point::x, &Point::y>)[x * x + y * y == 1] >> true, _ >> false ); } -``` - -```cpp -using namespace ptn; bool on_diagonal(const Point &p) { return match(p) | on( - $(has<&Point::x, &Point::y>)[PTN_WHERE((x, y), x == y)] >> true, + $(has<&Point::x, &Point::y>)[x == y] >> true, _ >> false ); } diff --git a/docs/guide/performance-tuning.md b/docs/guide/performance-tuning.md index fce39e86..2401291b 100644 --- a/docs/guide/performance-tuning.md +++ b/docs/guide/performance-tuning.md @@ -10,7 +10,7 @@ Example: int classify(int x) { return match(x) | PTN_ON( lit(0) >> 0, lit(1) >> 1, lit(2) >> 2, - __ >> -1 + _ >> -1 ); } ``` @@ -29,7 +29,7 @@ auto get_matcher() { return on( lit("start") >> Action::Start, lit("stop") >> Action::Stop, - __ >> Action::Unknown + _ >> Action::Unknown ); }); } diff --git a/docs/performance/v0.8.2.md b/docs/performance/v0.8.2.md index 5b818ac2..49f7f96c 100644 --- a/docs/performance/v0.8.2.md +++ b/docs/performance/v0.8.2.md @@ -264,7 +264,7 @@ maps to multiple residual checks: ```cpp match(v) | on( - $(is)[_0 > 100] >> 10, + $(is)[_ > 100] >> 10, is >> 1, is >> 2, _ >> 0 diff --git a/docs/tutorials/from-control-flow.md b/docs/tutorials/from-control-flow.md index f6fa788a..4233c11d 100644 --- a/docs/tutorials/from-control-flow.md +++ b/docs/tutorials/from-control-flow.md @@ -29,9 +29,9 @@ auto bucket(int x) { using namespace ptn; return match(x) | on( - $[_0 < 0] >> 0, - $[_0 < 10] >> 1, - $[_0 < 100] >> 2, + $[_ < 0] >> 0, + $[_ < 10] >> 1, + $[_ < 100] >> 2, _ >> 3 ); } @@ -51,8 +51,8 @@ auto label(const User &u) { using namespace ptn; return match(u) | on( - $(has<&User::active>)[arg<0> == false] >> "inactive", - $(has<&User::age>)[arg<0> < 18] >> "minor", + $(has<&User::active>)[_ == false] >> "inactive", + $(has<&User::age>)[_ < 18] >> "minor", _ >> "adult" ); } diff --git a/docs/tutorials/other-languages.md b/docs/tutorials/other-languages.md index 072e77fa..fbf70591 100644 --- a/docs/tutorials/other-languages.md +++ b/docs/tutorials/other-languages.md @@ -48,7 +48,7 @@ Patternia guards refine a bound case: ```cpp auto label = match(x) | on( - $[PTN_LET(value, value > 0 && value < 10)] >> "small", + $[_ > 0 && _ < 10] >> "small", _ >> "other" ); ``` @@ -58,8 +58,10 @@ auto label = match(x) | on( Without native language destructuring, Patternia uses explicit member pointers: ```cpp +PTN_BIND(User, age, active); + auto label = match(user) | on( - $(has<&User::age, &User::active>)[arg<0> < 18 && arg<1> == true] + $(has<&User::age, &User::active>)[age < 18 && active == true] >> "minor", _ >> "adult" ); diff --git a/docs/tutorials/policy-constraint.md b/docs/tutorials/policy-constraint.md index 6caf264e..bd5661bd 100644 --- a/docs/tutorials/policy-constraint.md +++ b/docs/tutorials/policy-constraint.md @@ -30,13 +30,14 @@ auto is_public_resource = [](int id) { ```cpp bool allowed(const Request &req) { using namespace ptn; + PTN_BIND(Request, role, resource_id, read_only); return match(req) | on( - $(has<&Request::role>)[arg<0> == Role::Admin] >> true, + $(has<&Request::role>)[_ == Role::Admin] >> true, $(has<&Request::role, &Request::resource_id, &Request::read_only>) - [arg<0> == Role::User && arg<1> >= 0 && arg<2> == true] >> true, + [role == Role::User && resource_id >= 0 && read_only == true] >> true, $(has<&Request::role, &Request::resource_id>) - [arg<0> == Role::Guest && is_public_resource] >> true, + [role == Role::Guest && is_public_resource] >> true, _ >> false ); } diff --git a/docs/tutorials/predicate-guards.md b/docs/tutorials/predicate-guards.md index ae2ff4b3..d065da16 100644 --- a/docs/tutorials/predicate-guards.md +++ b/docs/tutorials/predicate-guards.md @@ -5,27 +5,27 @@ In Patternia, they live inside the case definition: ```cpp match(x) | on( - $[_0 > 0 && _0 < 10] >> "small", + $[_ > 0 && _ < 10] >> "small", _ >> "other" ); ``` ## Single-Value Guards -Use `_0` for a case that binds exactly one value. +Use `_` for a case that binds exactly one value. ```cpp match(x) | on( - $[_0 == 0 || _0 == 1] >> "edge", + $[_ == 0 || _ == 1] >> "edge", _ >> "other" ); ``` -If you want a name instead of `_0`, use `PTN_LET(name, expr)`: +For a single bound value, `_` is the guard placeholder: ```cpp match(x) | on( - $[PTN_LET(value, value == 0 || value == 1)] >> "edge", + $[_ == 0 || _ == 1] >> "edge", _ >> "other" ); ``` @@ -42,7 +42,7 @@ match(x) | on( ## Multi-Value Guards -Use `arg` when a pattern binds multiple values. +Use `PTN_BIND` to give multiple bound values readable names. ```cpp struct Point { @@ -50,17 +50,10 @@ struct Point { int y; }; -match(p) | on( - $(has<&Point::x, &Point::y>)[arg<0> == arg<1>] >> "diagonal", - _ >> "other" -); -``` - -If you prefer named guard parameters, use `PTN_WHERE((...), expr)`: +PTN_BIND(Point, x, y); -```cpp match(p) | on( - $(has<&Point::x, &Point::y>)[PTN_WHERE((x, y), x == y)] >> "diagonal", + $(has<&Point::x, &Point::y>)[x == y] >> "diagonal", _ >> "other" ); ``` @@ -96,7 +89,7 @@ auto valid_id = [](int v) { }; match(x) | on( - $[_0 > 0 && valid_id] >> "valid", + $[_ > 0 && valid_id] >> "valid", _ >> "invalid" ); ``` diff --git a/docs/tutorials/sphere-constraint.md b/docs/tutorials/sphere-constraint.md index 35f3ac08..8174dc99 100644 --- a/docs/tutorials/sphere-constraint.md +++ b/docs/tutorials/sphere-constraint.md @@ -34,19 +34,21 @@ const char *classify(const Vec3 &p) { ## Refining the Constraint -You can combine a named predicate with `arg` for simple extra checks: +You can combine a named predicate with a `PTN_BIND` placeholder for simple +extra checks: ```cpp const char *classify_upper(const Vec3 &p) { using namespace ptn; + PTN_BIND(Vec3, x, y, z); return match(p) | on( - $(has<&Vec3::x, &Vec3::y, &Vec3::z>)[arg<2> >= 0 && inside_unit_sphere] + $(has<&Vec3::x, &Vec3::y, &Vec3::z>)[z >= 0 && inside_unit_sphere] >> "inside", _ >> "outside" ); } ``` -For multi-value guards, prefer explicit `arg` references or a callable that -accepts all bound values. +For multi-value guards, prefer `PTN_BIND` names or a callable that accepts all +bound values. diff --git a/include/ptn/core/common/common_traits.hpp b/include/ptn/core/common/common_traits.hpp index 500fdec0..13ef371f 100644 --- a/include/ptn/core/common/common_traits.hpp +++ b/include/ptn/core/common/common_traits.hpp @@ -2,9 +2,9 @@ // Core type traits used by the matching engine. // -// This header provides fundamental type traits and utilities for pattern -// matching, including case expression detection, handler invocability checks, -// and result type deduction. +// This header provides fundamental type traits and utilities for +// pattern matching, including case expression detection, handler +// invocability checks, and result type deduction. #include "ptn/pattern/base/fwd.h" #include @@ -70,7 +70,7 @@ namespace ptn::core::traits { enum class fallback_level { none, - pattern, // e.g. wildcard '__' + pattern, // e.g. wildcard '_' match // e.g. otherwise(...) }; @@ -80,32 +80,39 @@ namespace ptn::core::traits { static constexpr fallback_level level = fallback_level::none; }; - // Pattern-level fallback: wildcard pattern '__' - // NOTE: adjust the wildcard type name if your wildcard type lives elsewhere. + // Pattern-level fallback: wildcard pattern '_' + // NOTE: adjust the wildcard type name if your wildcard type lives + // elsewhere. template <> struct fallback_semantics { static constexpr fallback_level level = fallback_level::pattern; }; template - inline constexpr bool is_pattern_fallback_v = - fallback_semantics>::level == fallback_level::pattern; + inline constexpr bool + is_pattern_fallback_v = fallback_semantics< + std::decay_t>::level + == fallback_level::pattern; template - inline constexpr bool is_match_fallback_v = - fallback_semantics>::level == fallback_level::match; + inline constexpr bool + is_match_fallback_v = fallback_semantics< + std::decay_t>::level + == fallback_level::match; - // Case-level helper: does this case's pattern act as pattern-level fallback? + // Case-level helper: does this case's pattern act as pattern-level + // fallback? template - inline constexpr bool is_fallback_case_v = - is_pattern_fallback_v>; + inline constexpr bool is_fallback_case_v = is_pattern_fallback_v< + case_pattern_t>; // Handler Invocability Check (C++17) namespace detail { template static constexpr std::true_type is_applicable_impl( - decltype(std::apply(std::declval(), std::declval())) *); + decltype(std::apply(std::declval(), + std::declval())) *); template static constexpr std::false_type is_applicable_impl(...); @@ -117,27 +124,33 @@ namespace ptn::core::traits { using D = std::decay_t; template - static auto test_op(int) -> decltype(&U::operator(), std::true_type{}); - - // Generic lambdas have a templated operator(). Taking the address of - // `U::operator()` is ill-formed in that case, so we probe a few common - // template arities explicitly. This allows us to correctly classify - // generic lambdas (e.g. [](auto x){...}) as handler-like. + static auto test_op(int) -> decltype(&U::operator(), + std::true_type{}); + + // Generic lambdas have a templated operator(). Taking the + // address of `U::operator()` is ill-formed in that case, so we + // probe a few common template arities explicitly. This allows + // us to correctly classify generic lambdas (e.g. [](auto + // x){...}) as handler-like. template - static auto test_op_t1(int) - -> decltype(&U::template operator(), std::true_type{}); + static auto + test_op_t1(int) -> decltype(&U::template operator(), + std::true_type{}); template - static auto test_op_t2(int) - -> decltype(&U::template operator(), std::true_type{}); + static auto + test_op_t2(int) -> decltype(&U::template operator(), + std::true_type{}); template static auto test_op_t3(int) - -> decltype(&U::template operator(), std::true_type{}); + -> decltype(&U::template operator(), + std::true_type{}); template static auto test_op_t4(int) - -> decltype(&U::template operator(), std::true_type{}); + -> decltype(&U::template operator(), + std::true_type{}); template static auto test_op(...) -> std::false_type; @@ -154,36 +167,43 @@ namespace ptn::core::traits { template static auto test_op_t4(...) -> std::false_type; - static constexpr bool has_call_operator = - decltype(test_op(0))::value || decltype(test_op_t1(0))::value || - decltype(test_op_t2(0))::value || - decltype(test_op_t3(0))::value || - decltype(test_op_t4(0))::value; + static constexpr bool + has_call_operator = decltype(test_op(0))::value + || decltype(test_op_t1(0))::value + || decltype(test_op_t2(0))::value + || decltype(test_op_t3(0))::value + || decltype(test_op_t4(0))::value; static constexpr bool is_function_type = std::is_function_v; - static constexpr bool is_function_pointer = - std::is_pointer_v && std::is_function_v>; + static constexpr bool + is_function_pointer = std::is_pointer_v + && std::is_function_v< + std::remove_pointer_t>; - static constexpr bool is_function_reference = - std::is_reference_v && - std::is_function_v>; + static constexpr bool + is_function_reference = std::is_reference_v + && std::is_function_v< + std::remove_reference_t>; public: - static constexpr bool value = has_call_operator || is_function_type || - is_function_pointer || - is_function_reference; + static constexpr bool value = has_call_operator + || is_function_type + || is_function_pointer + || is_function_reference; }; template - inline constexpr bool is_handler_like_v = is_handler_like_impl::value; + inline constexpr bool + is_handler_like_v = is_handler_like_impl::value; // "Value-like" is everything that is not "handler-like". template inline constexpr bool is_value_like_v = !is_handler_like_v; } // namespace detail - // Checks whether a case's handler is invocable for a given subject type. + // Checks whether a case's handler is invocable for a given subject + // type. // // NOTE (Design A): // Handler arguments are exactly the pattern's bound values: @@ -194,22 +214,25 @@ namespace ptn::core::traits { private: using handler_type = case_handler_t; using pattern_type = case_pattern_t; - using bound_args_tuple = pat::base::binding_args_t; + using bound_args_tuple = pat::base::binding_args_t; - // Under Design A, the full argument tuple is exactly the bound args tuple. + // Under Design A, the full argument tuple is exactly the bound + // args tuple. using full_invoke_args_tuple = bound_args_tuple; public: - static constexpr bool value = - decltype(detail:: - is_applicable_impl( - nullptr))::value || - std::is_invocable_v; + static constexpr bool + value = decltype(detail::is_applicable_impl< + handler_type, + full_invoke_args_tuple>(nullptr))::value + || std::is_invocable_v; }; template - inline constexpr bool is_handler_invocable_v = - is_handler_invocable::value; + inline constexpr bool + is_handler_invocable_v = is_handler_invocable::value; // Case and Match Result Types @@ -222,23 +245,29 @@ namespace ptn::core::traits { using pattern_type = case_pattern_t; // Get the tuple of bound arguments from pattern matching. - using bound_args_tuple = pat::base::binding_args_t; + using bound_args_tuple = pat::base::binding_args_t; - // Under Design A, handler is invoked with exactly the bound arguments. + // Under Design A, handler is invoked with exactly the bound + // arguments. using full_invoke_args_tuple = bound_args_tuple; public: // Helper to deduce the result type of a case handler. // // Design note: - // - If the handler is invocable with the bound arguments, use that. - // - Otherwise, if it is invocable with no arguments, treat it as + // - If the handler is invocable with the bound arguments, use + // that. + // - Otherwise, if it is invocable with no arguments, treat it + // as // intentionally ignoring bound values. - // - Otherwise, let diagnostics/static-asserts report the mismatch. + // - Otherwise, let diagnostics/static-asserts report the + // mismatch. template - static constexpr auto - invoke_result_with_tuple_impl(std::index_sequence) - -> std::invoke_result_t...>; + static constexpr auto invoke_result_with_tuple_impl( + std::index_sequence) + -> std::invoke_result_t...>; template using invoke_result_with_tuple_t = @@ -250,12 +279,14 @@ namespace ptn::core::traits { -> invoke_result_with_tuple_t; template - static constexpr auto get_case_result_impl(...) -> std::invoke_result_t; + static constexpr auto + get_case_result_impl(...) -> std::invoke_result_t; public: // The result type when the handler is invoked. - using type = - decltype(get_case_result_impl(0)); + using type = decltype(get_case_result_impl< + handler_type, + full_invoke_args_tuple>(0)); }; template @@ -271,12 +302,12 @@ namespace ptn::core::traits { // Second overload: handler takes no parameters. template - static constexpr auto get_otherwise_result_impl(...) - -> decltype(std::declval()()); + static constexpr auto + get_otherwise_result_impl(...) -> decltype(std::declval()()); // A marker type used for "logically unreachable" fallback paths. - // It participates in std::common_type so that it never pollutes the final - // match result type computation. + // It participates in std::common_type so that it never pollutes + // the final match result type computation. struct unreachable_t { template [[noreturn]] constexpr operator T() const noexcept { @@ -295,7 +326,8 @@ namespace ptn::core::traits { template using otherwise_result_t = - decltype(detail::get_otherwise_result_impl(0)); + decltype(detail::get_otherwise_result_impl( + 0)); // Computes the common result type of the entire match expression. template @@ -306,24 +338,29 @@ namespace ptn::core::traits { using otherwise_result = otherwise_result_t; // Tuple containing result types of all case expressions. - using cases_result_tuple = std::tuple...>; + using cases_result_tuple = std::tuple< + case_result_t...>; // Helper function to check if all case results are void type. template - static constexpr bool all_cases_void_impl(std::index_sequence) { - return (std::is_void_v> && ...); + static constexpr bool + all_cases_void_impl(std::index_sequence) { + return (std::is_void_v> + && ...); } // Check if all case handlers return void. - static constexpr bool all_cases_void = - all_cases_void_impl( + static constexpr bool + all_cases_void = all_cases_void_impl( std::make_index_sequence{}); // Check if the otherwise handler returns void. - static constexpr bool otherwise_is_void = std::is_void_v; + static constexpr bool + otherwise_is_void = std::is_void_v; - // Helper to select result type without instantiating std::common_type_t - // on void-returning (statement-style) matches. + // Helper to select result type without instantiating + // std::common_type_t on void-returning (statement-style) + // matches. template struct match_result_impl; @@ -334,26 +371,30 @@ namespace ptn::core::traits { template struct match_result_impl { - using type = std:: - common_type_t..., otherwise_result>; + using type = std::common_type_t< + case_result_t..., + otherwise_result>; }; public: // Determine the common result type: - // - If all handlers return void, the match expression returns void. + // - If all handlers return void, the match expression returns + // void. // - Otherwise, use the common type of all handler results. - using type = - typename match_result_impl::type; + using type = typename match_result_impl< + all_cases_void && otherwise_is_void>::type; }; template - using match_result_t = - typename match_result::type; + using match_result_t = typename match_result::type; // Type trait to detect if a type should be treated as void-like. // - // This is used to determine if a type should be considered equivalent - // to void for the purposes of return type deduction and validation. + // This is used to determine if a type should be considered + // equivalent to void for the purposes of return type deduction and + // validation. template struct is_void_like : std::false_type {}; @@ -376,9 +417,8 @@ namespace ptn::core::traits { namespace std { template <> - struct common_type< - ptn::core::traits::detail::unreachable_t, - ptn::core::traits::detail::unreachable_t> { + struct common_type { using type = ptn::core::traits::detail::unreachable_t; }; diff --git a/include/ptn/core/common/diagnostics.hpp b/include/ptn/core/common/diagnostics.hpp index a4bda272..e1af05d3 100644 --- a/include/ptn/core/common/diagnostics.hpp +++ b/include/ptn/core/common/diagnostics.hpp @@ -2,8 +2,9 @@ // Compile-time diagnostics and static assertions for Patternia. // -// This header provides validation utilities that detect common pattern matching -// errors at compile time, providing clear error messages to guide developers. +// This header provides validation utilities that detect common +// pattern matching errors at compile time, providing clear error +// messages to guide developers. // #include #include @@ -34,34 +35,44 @@ namespace ptn::core::common { // ------------------------------------------------------------ // Validates the entire match expression for consistency. - // Ensures all handlers are invocable and have compatible return types. + // Ensures all handlers are invocable and have compatible return + // types. template constexpr void static_assert_valid_match() { - // (A) Each case handler must accept its pattern's bound arguments. - constexpr bool handlers_ok = - (traits::is_handler_invocable_v && ...); - static_assert( - handlers_ok, - "[Patternia.match] At least one case's handler cannot be invoked with " - "the arguments bound by its pattern. " - "Please check the handler's signature against the pattern's expected " - "bindings. Tip: ensure handler parameter types match the pattern's " - "bindings."); - - // (B) The otherwise handler must be callable with Subject or no args. - constexpr bool otherwise_ok = std::is_invocable_v || - std::is_invocable_v; - static_assert( - otherwise_ok, - "[Patternia.match] The `otherwise` handler has an invalid signature. " - "It should be either callable with the subject value or callable with " - "no arguments. Tip: make it callable with Subject or with no args."); + // (A) Each case handler must accept its pattern's bound + // arguments. + constexpr bool + handlers_ok = (traits::is_handler_invocable_v + && ...); + static_assert(handlers_ok, + "[Patternia.match] At least one case's handler " + "cannot be invoked with " + "the arguments bound by its pattern. " + "Please check the handler's signature against the " + "pattern's expected " + "bindings. Tip: ensure handler parameter types " + "match the pattern's " + "bindings."); + + // (B) The otherwise handler must be callable with Subject or no + // args. + constexpr bool + otherwise_ok = std::is_invocable_v + || std::is_invocable_v; + static_assert(otherwise_ok, + "[Patternia.match] The `otherwise` handler has an " + "invalid signature. " + "It should be either callable with the subject " + "value or callable with " + "no arguments. Tip: make it callable with Subject " + "or with no args."); // (C) All handlers must share a common return type. using common_return_type = traits:: match_result_t; - // Force instantiation to catch potential type errors (skip for void) + // Force instantiation to catch potential type errors (skip for + // void) if constexpr (!std::is_void_v) { (void) sizeof(common_return_type); } @@ -71,15 +82,17 @@ namespace ptn::core::common { template constexpr void static_assert_valid_handler() { // (A) Handler must be invocable with the bound values. - constexpr bool handler_invocable = - traits::is_handler_invocable_v; - static_assert( - handler_invocable, - "[Patternia.match] Handler signature does not match the pattern's " - "binding result."); + constexpr bool + handler_invocable = traits::is_handler_invocable_v; + static_assert(handler_invocable, + "[Patternia.match] Handler signature does not " + "match the pattern's " + "binding result."); } - // Validates a single case expression structure and handler compatibility. + // Validates a single case expression structure and handler + // compatibility. template constexpr void static_assert_valid_case() { // (A) Match inputs must be case expressions. @@ -96,11 +109,13 @@ namespace ptn::core::common { template constexpr void static_assert_valid_pattern() { // (A) Provided type must satisfy Patternia's pattern contract. - constexpr bool is_pattern_type = ptn::pat::traits::is_pattern_v; - static_assert( - is_pattern_type, - "[Patternia.match] The provided type is not a valid pattern. " - "A pattern must be invocable with a subject and return a boolean."); + constexpr bool + is_pattern_type = ptn::pat::traits::is_pattern_v; + static_assert(is_pattern_type, + "[Patternia.match] The provided type is not a " + "valid pattern. " + "A pattern must be invocable with a subject and " + "return a boolean."); } namespace detail { @@ -112,26 +127,30 @@ namespace ptn::core::common { template struct fallback_is_last - : std::conditional_t>, - std::false_type, - fallback_is_last> {}; + : std::conditional_t< + ptn::core::traits::is_pattern_fallback_v< + ptn::core::traits::case_pattern_t>, + std::false_type, + fallback_is_last> {}; } // namespace detail // Detects unreachable cases in pattern matching. namespace detail { - inline constexpr std::size_t alt_npos = static_cast(-1); + inline constexpr std::size_t alt_npos = static_cast( + -1); template - struct alt_pattern_index : std::integral_constant {}; + struct alt_pattern_index + : std::integral_constant {}; template - struct alt_pattern_index> : std::integral_constant {}; + struct alt_pattern_index< + ptn::pat::detail::type_alt_pattern> + : std::integral_constant {}; template - struct alt_pattern_index> + struct alt_pattern_index< + ptn::pat::mod::guarded_pattern> : alt_pattern_index {}; template @@ -139,18 +158,20 @@ namespace ptn::core::common { : std::integral_constant {}; template - struct plain_alt_pattern_index> + struct plain_alt_pattern_index< + ptn::pat::detail:: + type_alt_pattern> : std::integral_constant {}; template - inline constexpr std::size_t alt_case_index_v = - alt_pattern_index>::value; + inline constexpr std::size_t + alt_case_index_v = alt_pattern_index< + ptn::core::traits::case_pattern_t>::value; template - inline constexpr std::size_t plain_alt_case_index_v = - plain_alt_pattern_index>::value; + inline constexpr std::size_t + plain_alt_case_index_v = plain_alt_pattern_index< + ptn::core::traits::case_pattern_t>::value; template struct alt_index_set {}; @@ -174,19 +195,22 @@ namespace ptn::core::common { }; template - struct has_unreachable_alt_after_plain_alt_from : std::false_type {}; + struct has_unreachable_alt_after_plain_alt_from + : std::false_type {}; - template + template struct has_unreachable_alt_after_plain_alt_from< alt_index_set, First, Rest...> : std::bool_constant< - (((alt_case_index_v != alt_npos) && - alt_index_set_contains< + (((alt_case_index_v != alt_npos) + && alt_index_set_contains< alt_case_index_v, - alt_index_set>::value)) || - has_unreachable_alt_after_plain_alt_from< + alt_index_set>::value)) + || has_unreachable_alt_after_plain_alt_from< std::conditional_t< (plain_alt_case_index_v == alt_npos), alt_index_set, @@ -197,24 +221,24 @@ namespace ptn::core::common { template struct has_unreachable_alt_after_plain_alt - : has_unreachable_alt_after_plain_alt_from, Cases...> {}; + : has_unreachable_alt_after_plain_alt_from, + Cases...> {}; template - struct is_tail_case_unreachable_after_plain_alt : std::false_type {}; + struct is_tail_case_unreachable_after_plain_alt + : std::false_type {}; template struct is_tail_case_unreachable_after_plain_alt - : std::bool_constant< - ((alt_case_index_v != alt_npos) && - alt_index_set_contains< - alt_case_index_v, - CoveredSet>::value)> {}; - - template < - std::size_t... Covered, - typename First, - typename Next, - typename... Rest> + : std::bool_constant<( + (alt_case_index_v != alt_npos) + && alt_index_set_contains, + CoveredSet>::value)> {}; + + template struct is_tail_case_unreachable_after_plain_alt< alt_index_set, First, @@ -232,37 +256,42 @@ namespace ptn::core::common { template struct is_new_case_unreachable_after_plain_alt - : is_tail_case_unreachable_after_plain_alt, Cases...> { - }; + : is_tail_case_unreachable_after_plain_alt, + Cases...> {}; } // namespace detail template struct has_unreachable_case : std::bool_constant< - !detail::fallback_is_last::value || - detail::has_unreachable_alt_after_plain_alt::value> {}; + !detail::fallback_is_last::value + || detail::has_unreachable_alt_after_plain_alt< + Cases...>::value> {}; // Convenience variable template for unreachable case detection. template inline constexpr bool has_unreachable_case_v = has_unreachable_case::value; - // Emits diagnostic for deterministic unreachable alt(...) cases caused by - // an earlier plain alt() that already covers the same alternative. + // Emits diagnostic for deterministic unreachable alt(...) cases + // caused by an earlier plain alt() that already covers the same + // alternative. template constexpr void static_assert_no_unreachable_alt_after_plain_alt() { constexpr bool no_unreachable_alt_after_plain_alt = - !detail::has_unreachable_alt_after_plain_alt::value; + !detail::has_unreachable_alt_after_plain_alt< + Cases...>::value; static_assert( no_unreachable_alt_after_plain_alt, "[Patternia.alt]: case is unreachable because an earlier " - "plain alt() already covers this alternative. Tip: remove " + "plain alt() already covers this alternative. Tip: " + "remove " "the later alt(...) case, or reorder cases."); } - // Checks if the subject type is valid for pattern matching (must be an lvalue - // reference). This is a variable template because class bodies can only use - // static_assert with constant boolean conditions, not function calls. + // Checks if the subject type is valid for pattern matching (must + // be an lvalue reference). This is a variable template because + // class bodies can only use static_assert with constant boolean + // conditions, not function calls. template inline constexpr bool is_subject_type_valid_v = std::is_lvalue_reference_v; @@ -270,10 +299,12 @@ namespace ptn::core::common { // Emits a diagnostic if subject is not an lvalue reference. template struct subject_type_validator { - static constexpr bool is_lvalue_ref = is_subject_type_valid_v; - static_assert(is_lvalue_ref, - "[Patternia.match]: subject must be an lvalue reference. " - "Tip: pass an lvalue (match(v)), not std::move(v)."); + static constexpr bool + is_lvalue_ref = is_subject_type_valid_v; + static_assert( + is_lvalue_ref, + "[Patternia.match]: subject must be an lvalue reference. " + "Tip: pass an lvalue (match(v)), not std::move(v)."); }; // ------------------------------------------------------------ @@ -285,24 +316,27 @@ namespace ptn::core::common { constexpr void static_assert_literal_store_type() { // (A) Literal storage type cannot be void. constexpr bool not_void = !std::is_void_v; - static_assert(not_void, - "[Patternia.lit]: Literal value cannot be of type void."); + static_assert( + not_void, + "[Patternia.lit]: Literal value cannot be of type void."); // (B) Literal storage must be a value type (not a reference). constexpr bool not_ref = !std::is_reference_v; - static_assert( - not_ref, - "[Patternia.lit]: Literal value must be a value type (non-reference)."); + static_assert(not_ref, + "[Patternia.lit]: Literal value must be a value " + "type (non-reference)."); // (C) Literal storage must be movable. constexpr bool movable = std::is_move_constructible_v; static_assert(movable, - "[Patternia.lit]: Literal value must be move-constructible."); + "[Patternia.lit]: Literal value must be " + "move-constructible."); // (D) Literal storage must support equality comparison. - constexpr bool comparable = - std::is_constructible_v() - == std::declval())>; + constexpr bool comparable = std::is_constructible_v< + bool, + decltype(std::declval() + == std::declval())>; static_assert(comparable, - "[Patternia.lit]: Literal value type must support operator==."); + "[Patternia.lit]: Literal value type must support " + "operator==."); } // ------------------------------------------------------------ @@ -313,11 +347,13 @@ namespace ptn::core::common { template constexpr void static_assert_structural_elements() { // (A) has<...> only accepts member pointers or placeholders. - constexpr bool all_elements_valid = - (ptn::pat::traits::is_structural_element_v && ...); - static_assert( - all_elements_valid, - "[Patternia.has]: only data member pointers or nullptr/_ign allowed."); + constexpr bool + all_elements_valid = (ptn::pat::traits:: + is_structural_element_v + && ...); + static_assert(all_elements_valid, + "[Patternia.has]: only data member pointers or " + "nullptr/_ign allowed."); } // Checks whether a subject exposes the requested member pointer. @@ -330,47 +366,50 @@ namespace ptn::core::common { struct has_member_access< Subject, M, - std::void_t().*M)>> : std::true_type { - }; + std::void_t().*M)>> + : std::true_type {}; } // namespace detail - // Validates that a subject type provides all requested members in has<...>. + // Validates that a subject type provides all requested members in + // has<...>. template constexpr void static_assert_structural_accessible() { constexpr bool all_members_accessible = - ((ptn::pat::traits::is_nullptr_placeholder_v || - detail::has_member_access::value) && - ...); - static_assert( - all_members_accessible, - "[Patternia.has]: Subject does not expose one or more requested " - "members."); + ((ptn::pat::traits::is_nullptr_placeholder_v + || detail::has_member_access::value) + && ...); + static_assert(all_members_accessible, + "[Patternia.has]: Subject does not expose one or " + "more requested " + "members."); } // ------------------------------------------------------------ // Guard Predicate Validation // ------------------------------------------------------------ - // Ensures arg references are within the bound tuple range. + // Ensures placeholder references are within the bound tuple range. template constexpr void static_assert_tuple_guard_index() { - // (A) arg must reference an existing bound value. + // (A) A placeholder must reference an existing bound value. constexpr bool guard_index_in_range = MaxIndex < N; - static_assert( - guard_index_in_range, - "[Patternia.guard]: arg is out of range for the bound values."); + static_assert(guard_index_in_range, + "[Patternia.guard]: placeholder is out of range " + "for the bound values."); } - // Ensures unary guard predicates are only used with single bindings. + // Ensures unary guard predicates are only used with single + // bindings. template constexpr void static_assert_unary_guard_arity() { // (A) Unary guards require exactly one bound value. constexpr bool unary_guard_requires_one_binding = N == 1; - static_assert( - unary_guard_requires_one_binding, - "[Patternia.guard]: Unary guard predicates (_ / rng / && / ||) " - "require the pattern to bind exactly ONE value. " - "For multi-value guards, use a callable predicate (lambda / where)."); + static_assert(unary_guard_requires_one_binding, + "[Patternia.guard]: Unary guard predicates (_ / " + "rng / && / ||) " + "require the pattern to bind exactly ONE value. " + "For multi-value guards, declare names with " + "PTN_BIND or use a callable predicate (lambda)."); } // ------------------------------------------------------------ @@ -382,10 +421,10 @@ namespace ptn::core::common { constexpr void static_assert_pattern_has_bind() { // (A) Patterns used in handlers must provide bind(subject). constexpr bool pattern_has_bind_method = HasBindMember; - static_assert( - pattern_has_bind_method, - "[Patternia.match] Pattern must have a 'bind(subject)' method that " - "returns a tuple of bound values."); + static_assert(pattern_has_bind_method, + "[Patternia.match] Pattern must have a " + "'bind(subject)' method that " + "returns a tuple of bound values."); } // ------------------------------------------------------------ @@ -399,20 +438,19 @@ namespace ptn::core::common { template struct type_count> - : std::integral_constant ? 1 : 0))> { - }; + : std::integral_constant< + std::size_t, + (0 + ... + (std::is_same_v ? 1 : 0))> {}; } // namespace detail // Ensures Subject is a std::variant specialization. template constexpr void static_assert_variant_subject() { // (A) Subject must be a std::variant specialization. - constexpr bool is_variant_subject = - meta::is_spec_of_v>; - static_assert( - is_variant_subject, - "[Patternia.is]: Subject must be a std::variant."); + constexpr bool is_variant_subject = meta:: + is_spec_of_v>; + static_assert(is_variant_subject, + "[Patternia.is]: Subject must be a std::variant."); } // Ensures Alt appears exactly once in the variant's alternatives. @@ -424,15 +462,17 @@ namespace ptn::core::common { using args_t = typename meta::template_info::args; constexpr std::size_t - count = detail::type_count, args_t>::value; + count = detail::type_count, + args_t>::value; // (A) Alternative type must appear exactly once. constexpr bool alt_appears_once = (count == 1); - static_assert( - alt_appears_once, - "[Patternia.is]: Alternative type must appear exactly once in " - "std::variant. Tip: use alt() for duplicate types, or wrap " - "types in distinct structs."); + static_assert(alt_appears_once, + "[Patternia.is]: Alternative type must appear " + "exactly once in " + "std::variant. Tip: use alt() for duplicate " + "types, or wrap " + "types in distinct structs."); } // Ensures index I is within the variant's alternative range. @@ -442,10 +482,12 @@ namespace ptn::core::common { using subject_t = meta::remove_cvref_t; // (A) Alternative index must be in range. - constexpr bool alt_index_in_range = I < std::variant_size_v; - static_assert(alt_index_in_range, - "[Patternia.alt]: Alternative index is out of range. " - "Tip: valid indices are [0, variant_size-1]."); + constexpr bool + alt_index_in_range = I < std::variant_size_v; + static_assert( + alt_index_in_range, + "[Patternia.alt]: Alternative index is out of range. " + "Tip: valid indices are [0, variant_size-1]."); } } // namespace ptn::core::common diff --git a/include/ptn/core/engine/detail/pipeline_match_context.hpp b/include/ptn/core/engine/detail/pipeline_match_context.hpp index 9a9297fe..baa544c7 100644 --- a/include/ptn/core/engine/detail/pipeline_match_context.hpp +++ b/include/ptn/core/engine/detail/pipeline_match_context.hpp @@ -2,9 +2,9 @@ // Implementation of the pipeline-only match context. // -// This header contains the internal object returned by `match(subject)`. -// It exists solely to support immediate evaluation through -// `match(subject) | on(...)`. +// This header contains the internal object returned by +// `match(subject)`. It exists solely to support immediate evaluation +// through `match(subject) | on(...)`. #include #include @@ -23,15 +23,15 @@ namespace ptn::core::dsl::detail { namespace ptn::core::engine::detail { template - class [[nodiscard("[Patternia.match]: incomplete match expression. " - "Use `match(subject) | on(...)`.")]] - pipeline_match_context { + class [[nodiscard( + "[Patternia.match]: incomplete match expression. " + "Use `match(subject) | on(...)`.")]] pipeline_match_context { public: using subject_type = TV; - using subject_type_check = - ptn::core::common::subject_type_validator; + using subject_type_check = ptn::core::common:: + subject_type_validator; private: subject_type subject_; @@ -39,38 +39,40 @@ namespace ptn::core::engine::detail { template constexpr decltype(auto) eval_on_cases(CasesStorage &&pipeline_cases) && { - (ptn::core::common::static_assert_valid_case(), ...); + (ptn::core::common::static_assert_valid_case(), + ...); - ptn::core::common::static_assert_no_unreachable_alt_after_plain_alt< - Cases...>(); + ptn::core::common:: + static_assert_no_unreachable_alt_after_plain_alt< + Cases...>(); constexpr bool no_unreachable_cases = !ptn::core::common::has_unreachable_case_v; - static_assert( - no_unreachable_cases, - "[Patternia.on]: case sequence contains unreachable cases. " - "Tip: ensure wildcard '__' is last and remove shadowed cases."); + static_assert(no_unreachable_cases, + "[Patternia.on]: case sequence contains " + "unreachable cases. " + "Tip: ensure wildcard '_' is last and remove " + "shadowed cases."); constexpr bool has_pattern_fallback_in_on = - (traits::is_pattern_fallback_v> || ...); - static_assert( - has_pattern_fallback_in_on, - "[Patternia.on]: missing wildcard '__' fallback. " - "Tip: add '__ >> handler' as the last case."); - - auto dummy_fallback = - [](auto &&...) -> ptn::core::traits::detail::unreachable_t { - return {}; - }; + (traits::is_pattern_fallback_v< + traits::case_pattern_t> + || ...); + static_assert(has_pattern_fallback_in_on, + "[Patternia.on]: missing wildcard '_' fallback. " + "Tip: add '_ >> handler' as the last case."); + + auto dummy_fallback = [](auto &&...) + -> ptn::core::traits::detail::unreachable_t { return {}; }; using dummy_handler_t = decltype(dummy_fallback); - ptn::core::common::static_assert_valid_match< - subject_type, - dummy_handler_t, - Cases...>(); + ptn::core::common::static_assert_valid_match(); - using result_type = - core::traits::match_result_t; + using result_type = core::traits:: + match_result_t; if constexpr (traits::is_void_like_v) { match_impl::eval( @@ -88,7 +90,8 @@ namespace ptn::core::engine::detail { public: static constexpr auto create(subject_type subject) { - return pipeline_match_context{std::forward(subject)}; + return pipeline_match_context{ + std::forward(subject)}; } explicit constexpr pipeline_match_context(subject_type subject) @@ -98,7 +101,8 @@ namespace ptn::core::engine::detail { template constexpr decltype(auto) operator|(core::dsl::detail::on &on_cases) && { - return std::move(*this).template eval_on_cases(on_cases.cases); + return std::move(*this).template eval_on_cases( + on_cases.cases); } template diff --git a/include/ptn/core/engine/match.hpp b/include/ptn/core/engine/match.hpp index 3a9ceaa7..69a2f636 100644 --- a/include/ptn/core/engine/match.hpp +++ b/include/ptn/core/engine/match.hpp @@ -17,11 +17,12 @@ namespace ptn { // // Subject type is automatically deduced as `T&`. // Usage: - // - match(value) | on(case1, case2, ..., __ >> fallback) + // - match(value) | on(case1, case2, ..., _ >> fallback) template constexpr auto match(T &value) { using V = T &; - return core::engine::detail::pipeline_match_context::create(V(value)); + return core::engine::detail::pipeline_match_context::create( + V(value)); } // Caches an `on(...)` matcher behind a stateless factory. @@ -30,7 +31,7 @@ namespace ptn { // pattern: `static auto cases = on(...);`. // // Usage: - // - match(value) | static_on([] { return on(..., __ >> + // - match(value) | static_on([] { return on(..., _ >> // fallback); }) // // The factory must be stateless so caching does not silently diff --git a/include/ptn/pattern/bind.hpp b/include/ptn/pattern/bind.hpp index 84b64ae6..97025766 100644 --- a/include/ptn/pattern/bind.hpp +++ b/include/ptn/pattern/bind.hpp @@ -1,6 +1,7 @@ #pragma once -// Public API and implementation for binding patterns (`$` and `$()`). +// Public API and implementation for binding patterns (`$` and +// `$()`). // // This file provides factory functions to create patterns that // capture and bind subject values for later use in pattern matching. @@ -144,7 +145,7 @@ namespace ptn::pat { } // Extract member fields for all member-ptr Ms...; ignore - // wildcard (__). + // wildcard (_). template static constexpr auto bind_one(const Subject &subject) { if constexpr (std::is_member_object_pointer_v) { @@ -177,7 +178,7 @@ namespace ptn::pat { // // This allows $ to work in multiple ways: // $ >> handler // direct binding - // $[_0 > 0] >> handler // binding with guard + // $[_ > 0] >> handler // binding with guard // $(has<&T::x>) >> handler // binding with subpattern // $(is()) >> handler // binding variant type struct bind_factory : base::pattern_base, @@ -219,7 +220,7 @@ namespace ptn::pat { // // Usage: // $ >> handler // bind whole subject - // $[_0 > 0] >> handler // bind with guard + // $[_ > 0] >> handler // bind with guard // $(has<&T::x, &T::y>) >> handler // bind structural members // $(is()) >> handler // bind variant alternative inline constexpr bind_factory $; diff --git a/include/ptn/pattern/modifiers/guard.hpp b/include/ptn/pattern/modifiers/guard.hpp index eadd0f7b..01be9671 100644 --- a/include/ptn/pattern/modifiers/guard.hpp +++ b/include/ptn/pattern/modifiers/guard.hpp @@ -16,6 +16,7 @@ #include "ptn/pattern/base/fwd.h" #include "ptn/pattern/base/pattern_base.hpp" #include "ptn/pattern/base/pattern_traits.hpp" +#include "ptn/pattern/modifiers/placeholder.hpp" #include "ptn/core/common/diagnostics.hpp" namespace ptn::pat::mod { @@ -34,19 +35,6 @@ namespace ptn::pat::mod { }; // Multi-value guard expressions - // - // arg: placeholder for the Nth bound element. - template - struct arg_t { - static constexpr std::size_t index = I; - }; - - // Argument placeholder for multi-value expressions. - template - inline constexpr arg_t arg{}; - - // Shorthand aliases for common argument placeholders. - inline constexpr arg_t<0> _0{}; // Value wrapper for literals in expression templates. template @@ -131,10 +119,6 @@ namespace ptn::pat::mod { as_expr(std::forward(e))}; } - // Computes maximum argument index in expression. - template - struct max_arg_index; - template struct max_arg_index> : std::integral_constant {}; @@ -325,15 +309,13 @@ namespace ptn::pat::mod { template constexpr decltype(auto) invoke_from_tuple_impl( - Pred &&pred, - Tuple &&tuple, - std::index_sequence) { + Pred &&pred, Tuple &&tuple, std::index_sequence) { return std::forward(pred)( std::get(std::forward(tuple))...); } template - constexpr decltype(auto) invoke_from_tuple(Pred &&pred, + constexpr decltype(auto) invoke_from_tuple(Pred &&pred, Tuple &&tuple) { using tuple_t = std::remove_reference_t; return invoke_from_tuple_impl( @@ -359,12 +341,8 @@ namespace ptn::pat::mod { } // namespace detail - // Guard wrapper for macro-generated named predicates. - // - // PTN_WHERE(...) and PTN_LET(...) expand to plain lambdas. Wrapping them - // in guard_predicate_tag keeps them compatible with the existing guard - // composition operators while still using invoke_guard for the single-tuple - // case produced by guarded_pattern. + // Guard wrapper that makes a callable participate in guard + // composition. template struct callable_guard : traits::guard_predicate_tag { Fn fn; @@ -376,11 +354,11 @@ namespace ptn::pat::mod { constexpr bool operator()(Args &&...args) const { if constexpr (sizeof...(Args) == 1) { // guarded_pattern passes one tuple-like binding object here. - return detail::invoke_guard(fn, - std::forward(args)...); + return detail::invoke_guard(fn, std::forward(args)...); } else { - // Direct multi-argument calls come from composed guard predicates. + // Direct multi-argument calls come from composed guard + // predicates. return static_cast(fn(std::forward(args)...)); } } @@ -388,8 +366,7 @@ namespace ptn::pat::mod { template constexpr auto make_callable_guard(Fn &&fn) { - return callable_guard>( - std::forward(fn)); + return callable_guard>(std::forward(fn)); } // Logical AND for guard predicates. @@ -601,10 +578,11 @@ namespace ptn::pat::mod { pred(bound)); // tuple-level predicate } else { - // Callable guard: lambda / where / custom functor. + // Callable guard: lambda or custom functor. // Also handles binary_predicate and range_predicate via // tuple expansion for single-element bindings. - return static_cast(detail::invoke_from_tuple(pred, bound)); + return static_cast( + detail::invoke_from_tuple(pred, bound)); } } diff --git a/include/ptn/pattern/modifiers/placeholder.hpp b/include/ptn/pattern/modifiers/placeholder.hpp new file mode 100644 index 00000000..ae0e9662 --- /dev/null +++ b/include/ptn/pattern/modifiers/placeholder.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace ptn::pat::mod { + + // Internal placeholder node for the Nth bound value. + template + struct arg_t { + static constexpr std::size_t index = I; + }; + + // Computes the largest binding position referenced by an + // expression. + template + struct max_arg_index; + +} // namespace ptn::pat::mod diff --git a/include/ptn/pattern/structural.hpp b/include/ptn/pattern/structural.hpp index e77156f9..4edb2f6d 100644 --- a/include/ptn/pattern/structural.hpp +++ b/include/ptn/pattern/structural.hpp @@ -83,7 +83,8 @@ namespace ptn::pat { // not bind any values to the handler. // // Usage: - // has<&Pkt::type, &Pkt::len>[_0 == 0x01 && arg<1> == 0] + // PTN_BIND(Pkt, type, len); + // has<&Pkt::type, &Pkt::len>[type == 0x01 && len == 0] // >> [] { handle_ping(); } template constexpr auto operator[](Pred &&pred) const; diff --git a/include/ptn/pattern/type.hpp b/include/ptn/pattern/type.hpp index 88f020b3..bd84d2cc 100644 --- a/include/ptn/pattern/type.hpp +++ b/include/ptn/pattern/type.hpp @@ -18,144 +18,144 @@ namespace ptn::pat::detail { - // Sentinel type representing "no subpattern". - struct no_subpattern {}; - - // Empty base used when a pattern is not a binding pattern. - struct non_binding_base {}; - - // Matches a specific variant alternative by type, with optional - // subpattern. - // - When SubPattern is no_subpattern: only checks the - // alternative. - // - Otherwise: delegates match/bind to the subpattern on the - // selected alt. - // - // Important: when SubPattern is a binding pattern (e.g. $(), - // $(has<...>)), this type pattern itself becomes a binding - // pattern, enabling guard syntax: - // $(is())[predicate] // equivalent to - // is($[predicate]) - template - struct type_is_pattern - : base::pattern_base>, - std::conditional_t< - ptn::pat::traits::is_binding_pattern_v, - base::binding_pattern_base< - type_is_pattern>, - non_binding_base> { - using alt_t = meta::remove_cvref_t; - - SubPattern subpattern; - - constexpr type_is_pattern() = default; - constexpr explicit type_is_pattern(SubPattern sub) - : subpattern(std::move(sub)) { + // Sentinel type representing "no subpattern". + struct no_subpattern {}; + + // Empty base used when a pattern is not a binding pattern. + struct non_binding_base {}; + + // Matches a specific variant alternative by type, with optional + // subpattern. + // - When SubPattern is no_subpattern: only checks the + // alternative. + // - Otherwise: delegates match/bind to the subpattern on the + // selected alt. + // + // Important: when SubPattern is a binding pattern (e.g. $(), + // $(has<...>)), this type pattern itself becomes a binding + // pattern, enabling guard syntax: + // $(is())[predicate] // equivalent to + // is($[predicate]) + template + struct type_is_pattern + : base::pattern_base>, + std::conditional_t< + ptn::pat::traits::is_binding_pattern_v, + base::binding_pattern_base< + type_is_pattern>, + non_binding_base> { + using alt_t = meta::remove_cvref_t; + + SubPattern subpattern; + + constexpr type_is_pattern() = default; + constexpr explicit type_is_pattern(SubPattern sub) + : subpattern(std::move(sub)) { + } + + // Computes the alternative index of alt_t within Subject's + // variant. + template + static constexpr std::size_t alt_index() { + using subject_t = meta::remove_cvref_t; + using args_t = typename meta::template_info::args; + // Map alternative type to its variant index using meta + // type_list. + constexpr int idx = meta::index_of_v; + constexpr bool alt_type_found = (idx >= 0); + static_assert( + alt_type_found, + "[Patternia.is]: Alternative type not found in " + "std::variant. Tip: ensure the variant lists T, or use " + "alt() to match by index."); + return static_cast(idx); + } + + template + constexpr bool match(const Subject &subject) const noexcept { + core::common::static_assert_variant_alt_unique(); + // Fast alternative check via index; subpattern evaluated + // only on hit. + if (subject.index() != alt_index()) + return false; + if constexpr (std::is_same_v) { + return true; } - - // Computes the alternative index of alt_t within Subject's - // variant. - template - static constexpr std::size_t alt_index() { - using subject_t = meta::remove_cvref_t; - using args_t = typename meta::template_info::args; - // Map alternative type to its variant index using meta - // type_list. - constexpr int idx = meta::index_of_v; - constexpr bool alt_type_found = (idx >= 0); - static_assert( - alt_type_found, - "[Patternia.is]: Alternative type not found in " - "std::variant. Tip: ensure the variant lists T, or use " - "alt() to match by index."); - return static_cast(idx); + else { + return subpattern.match( + std::get()>(subject)); } - - template - constexpr bool match(const Subject &subject) const noexcept { - core::common::static_assert_variant_alt_unique(); - // Fast alternative check via index; subpattern evaluated - // only on hit. - if (subject.index() != alt_index()) - return false; - if constexpr (std::is_same_v) { - return true; - } - else { - return subpattern.match( - std::get()>(subject)); - } + } + + template + constexpr decltype(auto) bind(const Subject &subject) const { + core::common::static_assert_variant_alt_unique(); + // No bindings when there is no subpattern. + if constexpr (std::is_same_v) { + return std::tuple<>{}; } - - template - constexpr decltype(auto) bind(const Subject &subject) const { - core::common::static_assert_variant_alt_unique(); - // No bindings when there is no subpattern. - if constexpr (std::is_same_v) { - return std::tuple<>{}; - } - else { - return subpattern.bind( - std::get()>(subject)); - } + else { + return subpattern.bind( + std::get()>(subject)); } - }; + } + }; - // Matches a specific alternative by index, with optional - // subpattern. - // - When SubPattern is no_subpattern: only checks the index. - // - Otherwise: delegates match/bind to the subpattern on the - // selected alt. - // - // Like type_is_pattern, this becomes a binding pattern when - // SubPattern binds, so guards can be attached directly to the - // type pattern. - template - struct type_alt_pattern - : base::pattern_base>, - std::conditional_t< - ptn::pat::traits::is_binding_pattern_v, - base::binding_pattern_base< - type_alt_pattern>, - non_binding_base> { - SubPattern subpattern; - - constexpr type_alt_pattern() = default; - constexpr explicit type_alt_pattern(SubPattern sub) - : subpattern(std::move(sub)) { + // Matches a specific alternative by index, with optional + // subpattern. + // - When SubPattern is no_subpattern: only checks the index. + // - Otherwise: delegates match/bind to the subpattern on the + // selected alt. + // + // Like type_is_pattern, this becomes a binding pattern when + // SubPattern binds, so guards can be attached directly to the + // type pattern. + template + struct type_alt_pattern + : base::pattern_base>, + std::conditional_t< + ptn::pat::traits::is_binding_pattern_v, + base::binding_pattern_base< + type_alt_pattern>, + non_binding_base> { + SubPattern subpattern; + + constexpr type_alt_pattern() = default; + constexpr explicit type_alt_pattern(SubPattern sub) + : subpattern(std::move(sub)) { + } + + template + constexpr bool match(const Subject &subject) const noexcept { + core::common::static_assert_variant_alt_index(); + // Fast alternative check via index; subpattern evaluated + // only on hit. + if (subject.index() != I) + return false; + if constexpr (std::is_same_v) { + return true; } - - template - constexpr bool match(const Subject &subject) const noexcept { - core::common::static_assert_variant_alt_index(); - // Fast alternative check via index; subpattern evaluated - // only on hit. - if (subject.index() != I) - return false; - if constexpr (std::is_same_v) { - return true; - } - else { - return subpattern.match(std::get(subject)); - } + else { + return subpattern.match(std::get(subject)); } - - template - constexpr decltype(auto) bind(const Subject &subject) const { - core::common::static_assert_variant_alt_index(); - // No bindings when there is no subpattern. - if constexpr (std::is_same_v) { - return std::tuple<>{}; - } - else { - return subpattern.bind(std::get(subject)); - } + } + + template + constexpr decltype(auto) bind(const Subject &subject) const { + core::common::static_assert_variant_alt_index(); + // No bindings when there is no subpattern. + if constexpr (std::is_same_v) { + return std::tuple<>{}; } - }; + else { + return subpattern.bind(std::get(subject)); + } + } + }; - } // namespace ptn::pat::detail +} // namespace ptn::pat::detail // ---------------------------------------------------------------- // Short-form variable template aliases. @@ -168,7 +168,7 @@ namespace ptn::pat::detail { // is(sub_pattern) // match + delegate to sub_pattern // $(is()) // match + bind value (use $ // callable) -// $(is())[_0 > 0] // match + bind + guard +// $(is())[_ > 0] // match + bind + guard // alt_idx<0> // match by index, no bind // ---------------------------------------------------------------- @@ -194,7 +194,7 @@ namespace ptn::pat { template constexpr auto operator()(SubPattern &&sub) const { return type_is_pattern>( - std::forward(sub)); + std::forward(sub)); } }; @@ -205,15 +205,14 @@ namespace ptn::pat { constexpr alt_factory() = default; // No-arg call form: `alt()`. - constexpr type_alt_pattern - operator()() const { + constexpr type_alt_pattern operator()() const { return {}; } template constexpr auto operator()(SubPattern &&sub) const { return type_alt_pattern>( - std::forward(sub)); + std::forward(sub)); } }; diff --git a/include/ptn/pattern/wildcard.hpp b/include/ptn/pattern/wildcard.hpp index 9da8a9ed..71145d28 100644 --- a/include/ptn/pattern/wildcard.hpp +++ b/include/ptn/pattern/wildcard.hpp @@ -1,20 +1,25 @@ #pragma once -// Public API and implementation for wildcard patterns (`__`). +// Public API and implementation for wildcard patterns (`_`). // // This file provides the wildcard pattern that matches any value -// without binding. It includes both public API and internal -// implementation details, keeping the module self-contained. +// without binding, plus the expression hooks used by guard +// operators. Include modifiers/guard.hpp (or patternia.hpp) to use +// `_` in a guard expression such as `_ > 0`. #include "ptn/pattern/base/fwd.h" #include "ptn/pattern/base/pattern_base.hpp" +#include "ptn/pattern/base/pattern_traits.hpp" +#include "ptn/pattern/modifiers/placeholder.hpp" #include +#include namespace ptn::pat { namespace detail { // Wildcard pattern that matches any value and binds nothing. - struct wildcard_t : base::pattern_base { + struct wildcard_t : base::pattern_base, + mod::arg_t<0> { // Always matches successfully. template @@ -30,14 +35,34 @@ namespace ptn::pat { }; } // namespace detail - // Global wildcard instance. - inline constexpr ptn::pat::detail::wildcard_t __{}; - - // Shorthand wildcard alias. + // Global wildcard and, when guard operators are included, + // single-value guard placeholder. inline constexpr ptn::pat::detail::wildcard_t _{}; } // namespace ptn::pat +namespace ptn::pat::traits { + + template <> + struct is_arg_expr : std::true_type { + }; + +} // namespace ptn::pat::traits + +namespace ptn::pat::mod { + + template <> + struct max_arg_index + : std::integral_constant {}; + + template + constexpr decltype(auto) eval(const ptn::pat::detail::wildcard_t &, + Tuple &&tuple) { + return std::get<0>(std::forward(tuple)); + } + +} // namespace ptn::pat::mod + namespace ptn::pat::base { // Wildcard pattern binds no values. diff --git a/include/ptn/patternia.hpp b/include/ptn/patternia.hpp index dd59e1e1..259c1dea 100644 --- a/include/ptn/patternia.hpp +++ b/include/ptn/patternia.hpp @@ -49,13 +49,10 @@ namespace ptn { using ptn::pat::$; using ptn::pat::_; - using ptn::pat::__; // Guard utilities. - using ptn::pat::mod::_0; using ptn::pat::mod::operator&&; using ptn::pat::mod::operator||; - using ptn::pat::mod::arg; using ptn::pat::mod::rng; // Structural matching utilities. @@ -90,95 +87,13 @@ namespace ptn { }()) #endif -#define PTN_DETAIL_WHERE_CAT_IMPL(a, b) a##b -#define PTN_DETAIL_WHERE_CAT(a, b) PTN_DETAIL_WHERE_CAT_IMPL(a, b) -// Extra indirection so MSVC's traditional preprocessor splits -// __VA_ARGS__ into separate arguments before forwarding. -#define PTN_DETAIL_WHERE_EXPAND(...) __VA_ARGS__ -// Counts the number of names supplied in PTN_WHERE((a, b, ...), -// expr). -#define PTN_DETAIL_WHERE_COUNT_IMPL(a1, a2, a3, a4, a5, n, ...) n -#define PTN_DETAIL_WHERE_COUNT(...) \ - PTN_DETAIL_WHERE_EXPAND( \ - PTN_DETAIL_WHERE_COUNT_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1)) -#define PTN_DETAIL_WHERE_COUNT_TUPLE(args) \ - PTN_DETAIL_WHERE_COUNT args -// Picks the Nth identifier from the `(a, b, ...)` parameter list. -#define PTN_DETAIL_WHERE_NAME_1(a, ...) a -#define PTN_DETAIL_WHERE_NAME_2(a, b, ...) b -#define PTN_DETAIL_WHERE_NAME_3(a, b, c, ...) c -#define PTN_DETAIL_WHERE_NAME_4(a, b, c, d, ...) d -#define PTN_DETAIL_WHERE_NAME_5(a, b, c, d, e, ...) e -#define PTN_DETAIL_WHERE_SELECT(n) \ - PTN_DETAIL_WHERE_CAT(PTN_DETAIL_WHERE_, n) -#define PTN_DETAIL_WHERE_INVOKE(n, args, ...) \ - PTN_DETAIL_WHERE_EXPAND( \ - PTN_DETAIL_WHERE_SELECT(n)(args, __VA_ARGS__)) - -// Expands a named guard expression into a stateless predicate -// object. The tuple of bound values is mapped to lambda parameters -// by position. This macro currently supports 1 to 5 names. -#define PTN_DETAIL_WHERE_1(args, ...) \ - (::ptn::pat::mod::make_callable_guard( \ - [](auto &&PTN_DETAIL_WHERE_NAME_1 args) -> bool { \ - return static_cast(__VA_ARGS__); \ - })) - -#define PTN_DETAIL_WHERE_2(args, ...) \ - (::ptn::pat::mod::make_callable_guard( \ - [](auto &&PTN_DETAIL_WHERE_NAME_1 args, \ - auto &&PTN_DETAIL_WHERE_NAME_2 args) -> bool { \ - return static_cast(__VA_ARGS__); \ - })) - -#define PTN_DETAIL_WHERE_3(args, ...) \ - (::ptn::pat::mod::make_callable_guard( \ - [](auto &&PTN_DETAIL_WHERE_NAME_1 args, \ - auto &&PTN_DETAIL_WHERE_NAME_2 args, \ - auto &&PTN_DETAIL_WHERE_NAME_3 args) -> bool { \ - return static_cast(__VA_ARGS__); \ - })) - -#define PTN_DETAIL_WHERE_4(args, ...) \ - (::ptn::pat::mod::make_callable_guard( \ - [](auto &&PTN_DETAIL_WHERE_NAME_1 args, \ - auto &&PTN_DETAIL_WHERE_NAME_2 args, \ - auto &&PTN_DETAIL_WHERE_NAME_3 args, \ - auto &&PTN_DETAIL_WHERE_NAME_4 args) -> bool { \ - return static_cast(__VA_ARGS__); \ - })) - -#define PTN_DETAIL_WHERE_5(args, ...) \ - (::ptn::pat::mod::make_callable_guard( \ - [](auto &&PTN_DETAIL_WHERE_NAME_1 args, \ - auto &&PTN_DETAIL_WHERE_NAME_2 args, \ - auto &&PTN_DETAIL_WHERE_NAME_3 args, \ - auto &&PTN_DETAIL_WHERE_NAME_4 args, \ - auto &&PTN_DETAIL_WHERE_NAME_5 args) -> bool { \ - return static_cast(__VA_ARGS__); \ - })) - -#ifndef PTN_WHERE -// Creates a named guard predicate from 1 to 5 parameter names. -#define PTN_WHERE(args, ...) \ - PTN_DETAIL_WHERE_INVOKE( \ - PTN_DETAIL_WHERE_COUNT_TUPLE(args), args, __VA_ARGS__) -#endif - -#ifndef PTN_LET -// Single-value shorthand for PTN_WHERE((name), expr). -#define PTN_LET(name, ...) PTN_WHERE((name), __VA_ARGS__) -#endif - // PTN_BIND(Type, member0, member1, ...) // // Declares named placeholder objects for use in guard expressions. -// Each name is an inline constexpr arg_t where N is the -// zero-based position of the member in the argument list. These -// placeholders integrate directly with the existing -// expression-template machinery (operators, eval, max_arg_index) and -// replace positional placeholders -// (_0, arg<1>, etc.) with readable identifiers. +// Each name is a constexpr arg_t where N is the zero-based +// position of the member in the argument list. Declarations are +// valid at namespace or block scope, so names can stay close to a +// match. // // Example: // struct Point { int x; int y; }; @@ -197,33 +112,33 @@ namespace ptn { // // Supports 1 to 5 member names. #define PTN_BIND_1(Type, m0) \ - inline constexpr ::ptn::pat::mod::arg_t<0> m0 { \ + constexpr ::ptn::pat::mod::arg_t<0> m0 { \ } #define PTN_BIND_2(Type, m0, m1) \ - inline constexpr ::ptn::pat::mod::arg_t<0> m0{}; \ - inline constexpr ::ptn::pat::mod::arg_t<1> m1 { \ + constexpr ::ptn::pat::mod::arg_t<0> m0{}; \ + constexpr ::ptn::pat::mod::arg_t<1> m1 { \ } #define PTN_BIND_3(Type, m0, m1, m2) \ - inline constexpr ::ptn::pat::mod::arg_t<0> m0{}; \ - inline constexpr ::ptn::pat::mod::arg_t<1> m1{}; \ - inline constexpr ::ptn::pat::mod::arg_t<2> 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 { \ } #define PTN_BIND_4(Type, m0, m1, m2, m3) \ - inline constexpr ::ptn::pat::mod::arg_t<0> m0{}; \ - inline constexpr ::ptn::pat::mod::arg_t<1> m1{}; \ - inline constexpr ::ptn::pat::mod::arg_t<2> m2{}; \ - inline constexpr ::ptn::pat::mod::arg_t<3> 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 { \ } #define PTN_BIND_5(Type, m0, m1, m2, m3, m4) \ - inline constexpr ::ptn::pat::mod::arg_t<0> m0{}; \ - inline constexpr ::ptn::pat::mod::arg_t<1> m1{}; \ - inline constexpr ::ptn::pat::mod::arg_t<2> m2{}; \ - inline constexpr ::ptn::pat::mod::arg_t<3> m3{}; \ - inline constexpr ::ptn::pat::mod::arg_t<4> 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 { \ } #define PTN_BIND_PICK(_1, _2, _3, _4, _5, NAME, ...) NAME diff --git a/samples/fibo.cpp b/samples/fibo.cpp index 038608b4..6c06c30e 100644 --- a/samples/fibo.cpp +++ b/samples/fibo.cpp @@ -18,13 +18,17 @@ std::uint64_t fib(std::uint64_t n) { lit(1u) >> 1u, // Handles even n > 1. - $[PTN_LET(value, value > 1u) && is_even] >> - [&](std::uint64_t v) { return fib(v - 1) + fib(v - 2); }, + $[_ > 1u && is_even] >> + [&](std::uint64_t v) { + return fib(v - 1) + fib(v - 2); + }, // Handles odd n > 1. - $[PTN_LET(value, value > 1u) && is_odd] >> - [&](std::uint64_t v) { return fib(v - 1) + fib(v - 2); }, - __ >> 0u); + $[_ > 1u && is_odd] >> + [&](std::uint64_t v) { + return fib(v - 1) + fib(v - 2); + }, + _ >> 0u); } int main() { diff --git a/samples/handle_packet.cpp b/samples/handle_packet.cpp index 309b0dd1..e57df4ee 100644 --- a/samples/handle_packet.cpp +++ b/samples/handle_packet.cpp @@ -32,26 +32,28 @@ void reject_packet() { // Protocol parser. void parse_packet(const Packet &pkt) { + PTN_BIND(Packet, type, length); // Predicate for valid data payload packets. - auto is_valid_payload = [&pkt](const std::vector &payload) { - return pkt.type == 0x02 && pkt.length == payload.size() && - (pkt.flags & FLAG_VALID); - }; + auto is_valid_payload = + [&pkt](const std::vector &payload) { + return pkt.type == 0x02 && pkt.length == payload.size() + && (pkt.flags & FLAG_VALID); + }; // Predicate for error packets carrying payload bytes. - auto is_error_packet = [](std::uint8_t type, - const std::vector &payload) { - return type == 0xFF && !payload.empty(); - }; + auto is_error_packet = + [](std::uint8_t type, + const std::vector &payload) { + return type == 0xFF && !payload.empty(); + }; match(pkt) | on( // Handles ping packets. - $( - has<&Packet::type, - &Packet::length>)[arg<0> == 0x01 && arg<1> == 0] >> - [](auto &&...) { handle_ping(); }, + $(has<&Packet::type, &Packet::length>)[type == 0x01 + && length == 0] + >> [](auto &&...) { handle_ping(); }, // Handles data packets. $(has<&Packet::payload>)[is_valid_payload] >> @@ -61,12 +63,13 @@ void parse_packet(const Packet &pkt) { // Handles error packets. $(has<&Packet::type, &Packet::payload>)[is_error_packet] >> - [](std::uint8_t, const std::vector &payload) { + [](std::uint8_t, + const std::vector &payload) { handle_error(payload[0]); }, // Handles unmatched packets. - __ >> [] { reject_packet(); }); + _ >> [] { reject_packet(); }); } int main() { diff --git a/samples/json_dispatch/parse_json.cpp b/samples/json_dispatch/parse_json.cpp index 2b6f5c10..4ffd66b7 100644 --- a/samples/json_dispatch/parse_json.cpp +++ b/samples/json_dispatch/parse_json.cpp @@ -10,12 +10,12 @@ void parse_json(const json &j, int depth = 0) { | on( $[is_type(json::value_t::null)] >> print_null(depth), $[is_type(json::value_t::boolean)] >> print_bool(depth), - $[is_type(json::value_t::number_integer)] >> - print_number("int", depth), - $[is_type(json::value_t::number_unsigned)] >> - print_number("uint", depth), - $[is_type(json::value_t::number_float)] >> - print_number("float", depth), + $[is_type(json::value_t::number_integer)] + >> print_number("int", depth), + $[is_type(json::value_t::number_unsigned)] + >> print_number("uint", depth), + $[is_type(json::value_t::number_float)] + >> print_number("float", depth), $[is_type(json::value_t::string)] >> print_string(depth), $[is_empty_array] >> [=](const json &) { @@ -40,8 +40,9 @@ void parse_json(const json &j, int depth = 0) { parse_json(v, depth + 2); } }, - __ >> [=] { - indent(depth); - std::cout << "\n"; - }); + _ >> + [=] { + indent(depth); + std::cout << "\n"; + }); } diff --git a/samples/test.cpp b/samples/test.cpp index 911606ef..5b513c55 100644 --- a/samples/test.cpp +++ b/samples/test.cpp @@ -54,8 +54,9 @@ namespace { int x = 7; int r = match(x) | on( - forwarding_probe_pattern{} >> [](int v) { return v; }, - __ >> -1); + forwarding_probe_pattern{} >> + [](int v) { return v; }, + _ >> -1); const bool ok = r == 7 && forwarding_probe_pattern::lvalue_bind_calls @@ -81,7 +82,10 @@ int main() { V v{std::in_place_index<1>, 23}; - auto res = match(v) | on(alt<0>() >> "Point", alt<1>() >> "Height", __ >> "Other"); + auto res = match(v) + | on(alt<0>() >> "Point", + alt<1>() >> "Height", + _ >> "Other"); std::cout << res << '\n'; diff --git a/samples/variant_type_is.cpp b/samples/variant_type_is.cpp index fbdbb524..2157f809 100644 --- a/samples/variant_type_is.cpp +++ b/samples/variant_type_is.cpp @@ -19,12 +19,16 @@ describe(const std::variant &v) { $(is()) >> [](const std::string &s) { return "str:" + s; }, is($(has<&Point::x, &Point::y>)) >> - [](int x, int y) { return "pt:" + std::to_string(x + y); }, - __ >> [] { return std::string("other"); }); + [](int x, int y) { + return "pt:" + std::to_string(x + y); + }, + _ >> [] { return std::string("other"); }); } std::string describe_as(const std::variant &v) { + PTN_BIND(Point, x, y); + // Same as describe(...), but uses the binding sugar // `$(is())` and `$(has<...>)`. // @@ -35,13 +39,14 @@ describe_as(const std::variant &v) { return match(v) | on( is() >> "int", - $(is())[_0 != ""] >> + $(is())[_ != ""] >> [](const std::string &s) { return "str:" + s; }, - is($(has<&Point::x, &Point::y>))[arg<0> > 0 - && arg<1> > 0] + is($(has<&Point::x, &Point::y>))[x > 0 && y > 0] >> - [](int x, int y) { return "pt:" + std::to_string(x + y); }, - __ >> [] { return std::string("other"); }); + [](int x, int y) { + return "pt:" + std::to_string(x + y); + }, + _ >> [] { return std::string("other"); }); } int main() { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ccbdfe17..93f22754 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,6 +45,11 @@ set(PTN_COMPILE_FAIL_CASES compile_fail/direct_match_removed.cpp compile_fail/bind_function_removed.cpp compile_fail/guard_placeholder_alias_removed.cpp + compile_fail/zero_placeholder_removed.cpp + compile_fail/indexed_placeholder_removed.cpp + compile_fail/ptn_let_removed.cpp + compile_fail/ptn_where_removed.cpp + compile_fail/double_wildcard_alias_removed.cpp compile_fail/has_factory_removed.cpp compile_fail/lit_v_factory_removed.cpp compile_fail/lit_factory_removed.cpp diff --git a/tests/compile_fail/cases_factory_removed.cpp b/tests/compile_fail/cases_factory_removed.cpp index 3da9c0cc..198f606a 100644 --- a/tests/compile_fail/cases_factory_removed.cpp +++ b/tests/compile_fail/cases_factory_removed.cpp @@ -1,7 +1,7 @@ #include int main() { - auto pack = ptn::cases(ptn::lit(1) >> 1, ptn::__ >> 0); - (void)pack; + auto pack = ptn::cases(ptn::lit(1) >> 1, ptn::_ >> 0); + (void) pack; return 0; } diff --git a/tests/compile_fail/direct_match_removed.cpp b/tests/compile_fail/direct_match_removed.cpp index 5085e4bc..8d4b3618 100644 --- a/tests/compile_fail/direct_match_removed.cpp +++ b/tests/compile_fail/direct_match_removed.cpp @@ -1,8 +1,8 @@ #include int main() { - int x = 1; - auto r = ptn::match(x, ptn::lit(1) >> 1, ptn::__ >> 0); - (void)r; + int x = 1; + auto r = ptn::match(x, ptn::lit(1) >> 1, ptn::_ >> 0); + (void) r; return 0; } diff --git a/tests/compile_fail/double_wildcard_alias_removed.cpp b/tests/compile_fail/double_wildcard_alias_removed.cpp new file mode 100644 index 00000000..b3af9f05 --- /dev/null +++ b/tests/compile_fail/double_wildcard_alias_removed.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto wildcard = ptn::__; + (void) wildcard; +} diff --git a/tests/compile_fail/indexed_placeholder_removed.cpp b/tests/compile_fail/indexed_placeholder_removed.cpp new file mode 100644 index 00000000..f3c61933 --- /dev/null +++ b/tests/compile_fail/indexed_placeholder_removed.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto placeholder = ptn::arg<0>; + (void) placeholder; +} diff --git a/tests/compile_fail/ptn_let_removed.cpp b/tests/compile_fail/ptn_let_removed.cpp new file mode 100644 index 00000000..321bdcf5 --- /dev/null +++ b/tests/compile_fail/ptn_let_removed.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto guard = PTN_LET(v, v == 1); + (void) guard; +} diff --git a/tests/compile_fail/ptn_where_removed.cpp b/tests/compile_fail/ptn_where_removed.cpp new file mode 100644 index 00000000..b5b62181 --- /dev/null +++ b/tests/compile_fail/ptn_where_removed.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto guard = PTN_WHERE((x, y), x < y); + (void) guard; +} diff --git a/tests/compile_fail/static_on_with_capture.cpp b/tests/compile_fail/static_on_with_capture.cpp index 9006b353..971f6ca9 100644 --- a/tests/compile_fail/static_on_with_capture.cpp +++ b/tests/compile_fail/static_on_with_capture.cpp @@ -6,14 +6,9 @@ int main() { int x = 1; int base = 1; - auto r = match(x) - | static_on([base] { - return on( - val<1> >> base, - __ >> 0 - ); - }); - (void)r; + auto r = match(x) | static_on([base] { + return on(val<1> >> base, _ >> 0); + }); + (void) r; return 0; } - diff --git a/tests/compile_fail/zero_placeholder_removed.cpp b/tests/compile_fail/zero_placeholder_removed.cpp new file mode 100644 index 00000000..3502ebdf --- /dev/null +++ b/tests/compile_fail/zero_placeholder_removed.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto placeholder = ptn::_0; + (void) placeholder; +} diff --git a/tests/tests_combinator.cpp b/tests/tests_combinator.cpp index 3bab3b21..08a530c5 100644 --- a/tests/tests_combinator.cpp +++ b/tests/tests_combinator.cpp @@ -48,7 +48,7 @@ TEST(CombinatorPattern, AnyShortCircuitsAfterFirstHit) { ProbePattern{true, &c2}, ProbePattern{true, &c3}) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); EXPECT_EQ(c1, 1); @@ -65,7 +65,7 @@ TEST(CombinatorPattern, AnyFallsBackWhenAllMiss) { | on(any(ProbePattern{false, &c1}, ProbePattern{false, &c2}) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 0); EXPECT_EQ(c1, 1); @@ -83,7 +83,7 @@ TEST(CombinatorPattern, AllMatchesOnlyWhenAllHit) { ProbePattern{true, &c2}, ProbePattern{true, &c3}) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); EXPECT_EQ(c1, 1); @@ -102,7 +102,7 @@ TEST(CombinatorPattern, AllShortCircuitsOnFirstMiss) { ProbePattern{false, &c2}, ProbePattern{true, &c3}) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 0); EXPECT_EQ(c1, 1); @@ -116,13 +116,13 @@ TEST(CombinatorPattern, AnyAndAllWorkWithZeroBindHandlers) { int any_result = match(x) | on( any(lit(1), lit(2)) >> [] { return 11; }, - __ >> 0); + _ >> 0); int all_result = match(x) | on( all(any(lit(2), lit(3)), lit(2)) >> [] { return 22; }, - __ >> 0); + _ >> 0); EXPECT_EQ(any_result, 11); EXPECT_EQ(all_result, 22); @@ -131,7 +131,7 @@ TEST(CombinatorPattern, AnyAndAllWorkWithZeroBindHandlers) { TEST(CombinatorPattern, AnyWithValStaticLiterals) { int x = 2; int result = match(x) - | on(any(val<1>, val<2>, val<3>) >> 1, __ >> 0); + | on(any(val<1>, val<2>, val<3>) >> 1, _ >> 0); EXPECT_EQ(result, 1); } @@ -139,21 +139,21 @@ TEST(CombinatorPattern, AnyWithValStaticLiterals) { TEST(CombinatorPattern, AnyWithValStaticLiteralsMiss) { int x = 5; int result = match(x) - | on(any(val<1>, val<2>, val<3>) >> 1, __ >> 0); + | on(any(val<1>, val<2>, val<3>) >> 1, _ >> 0); EXPECT_EQ(result, 0); } TEST(CombinatorPattern, AllWithValStaticLiterals) { int x = 2; - int result = match(x) | on(all(val<2>, val<2>) >> 1, __ >> 0); + int result = match(x) | on(all(val<2>, val<2>) >> 1, _ >> 0); EXPECT_EQ(result, 1); } TEST(CombinatorPattern, AllWithValStaticLiteralsMiss) { int x = 2; - int result = match(x) | on(all(val<1>, val<2>) >> 1, __ >> 0); + int result = match(x) | on(all(val<1>, val<2>) >> 1, _ >> 0); EXPECT_EQ(result, 0); } @@ -164,7 +164,7 @@ TEST(CombinatorPattern, AnyWithIsTypePatterns) { int result = match(v) | on(any(is, is) >> 1, is >> 2, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 2); } @@ -175,21 +175,21 @@ TEST(CombinatorPattern, AnyMatchesVariantType) { int result = match(v) | on(any(is, is) >> 1, is >> 2, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } TEST(CombinatorPattern, SingleSubPatternAny) { int x = 7; - int result = match(x) | on(any(lit(7)) >> 1, __ >> 0); + int result = match(x) | on(any(lit(7)) >> 1, _ >> 0); EXPECT_EQ(result, 1); } TEST(CombinatorPattern, SingleSubPatternAll) { int x = 7; - int result = match(x) | on(all(lit(7)) >> 1, __ >> 0); + int result = match(x) | on(all(lit(7)) >> 1, _ >> 0); EXPECT_EQ(result, 1); } @@ -199,7 +199,7 @@ TEST(CombinatorPattern, NestedAnyInsideAll) { int result = match(x) | on(all(any(lit(1), lit(2), lit(3), lit(4)), val<4>) >> 42, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 42); } @@ -209,7 +209,7 @@ TEST(CombinatorPattern, NestedAllInsideAny) { int result = match(x) | on(any(all(val<5>, val<5>), all(val<6>, val<6>)) >> 99, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 99); } @@ -217,7 +217,7 @@ TEST(CombinatorPattern, NestedAllInsideAny) { TEST(CombinatorPattern, NestedAnyMissOuterAllHit) { int x = 10; int result = match(x) - | on(all(any(lit(1), lit(2)), val<10>) >> 1, __ >> 0); + | on(all(any(lit(1), lit(2)), val<10>) >> 1, _ >> 0); EXPECT_EQ(result, 0); } @@ -226,7 +226,7 @@ TEST(CombinatorPattern, AnyWithLitCi) { std::string s = "HeLLo"; int result = match(s) | on(any(lit_ci("hello"), lit_ci("world")) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } @@ -238,9 +238,9 @@ TEST(CombinatorPattern, AnyReusedAcrossMatches) { int b = 2; int c = 4; - EXPECT_EQ(match(a) | on(pattern >> 1, __ >> 0), 1); - EXPECT_EQ(match(b) | on(pattern >> 1, __ >> 0), 1); - EXPECT_EQ(match(c) | on(pattern >> 1, __ >> 0), 0); + EXPECT_EQ(match(a) | on(pattern >> 1, _ >> 0), 1); + EXPECT_EQ(match(b) | on(pattern >> 1, _ >> 0), 1); + EXPECT_EQ(match(c) | on(pattern >> 1, _ >> 0), 0); } TEST(CombinatorPattern, AllReusedAcrossMatches) { @@ -249,8 +249,8 @@ TEST(CombinatorPattern, AllReusedAcrossMatches) { int a = 2; int b = 3; - EXPECT_EQ(match(a) | on(pattern >> 1, __ >> 0), 1); - EXPECT_EQ(match(b) | on(pattern >> 1, __ >> 0), 0); + EXPECT_EQ(match(a) | on(pattern >> 1, _ >> 0), 1); + EXPECT_EQ(match(b) | on(pattern >> 1, _ >> 0), 0); } TEST(CombinatorPattern, AnyInsidePtnOnMacro) { @@ -260,7 +260,7 @@ TEST(CombinatorPattern, AnyInsidePtnOnMacro) { auto run = [](int x) { return match(x) - | PTN_ON(any(val<1>, val<2>, val<3>) >> 1, __ >> 0); + | PTN_ON(any(val<1>, val<2>, val<3>) >> 1, _ >> 0); }; EXPECT_EQ(run(a), 1); @@ -274,7 +274,7 @@ TEST(CombinatorPattern, AllInsidePtnOnMacro) { auto run = [](int x) { return match(x) - | PTN_ON(all(any(val<1>, val<2>), val<2>) >> 1, __ >> 0); + | PTN_ON(all(any(val<1>, val<2>), val<2>) >> 1, _ >> 0); }; EXPECT_EQ(run(a), 1); diff --git a/tests/tests_destructure.cpp b/tests/tests_destructure.cpp index 82f49cc5..af52b27e 100644 --- a/tests/tests_destructure.cpp +++ b/tests/tests_destructure.cpp @@ -18,15 +18,19 @@ struct Packet { std::string payload; }; +PTN_BIND(Point, point_x, point_y); +PTN_BIND(Packet, packet_type, packet_length); + // -- $(has<>) destructure binding -- TEST(Destructure, BasicMemberBinding) { Point p{3, 4}; int result = match(p) - | on($(has<&Point::x, &Point::y>) >> - [](int x, int y) { return x + y; }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); EXPECT_EQ(result, 7); } @@ -35,9 +39,11 @@ TEST(Destructure, WithGuard) { Point p{5, 10}; int result = match(p) - | on($(has<&Point::x, &Point::y>)[_0 > 0 && arg<1> > 0] - >> [](int x, int y) { return x * y; }, - _ >> -1); + | on( + $(has<&Point::x, &Point::y>)[point_x > 0 + && point_y > 0] + >> [](int x, int y) { return x * y; }, + _ >> -1); EXPECT_EQ(result, 50); } @@ -46,9 +52,11 @@ TEST(Destructure, GuardRejects) { Point p{-1, 10}; int result = match(p) - | on($(has<&Point::x, &Point::y>)[_0 > 0 && arg<1> > 0] - >> [](int x, int y) { return x * y; }, - _ >> -1); + | on( + $(has<&Point::x, &Point::y>)[point_x > 0 + && point_y > 0] + >> [](int x, int y) { return x * y; }, + _ >> -1); EXPECT_EQ(result, -1); } @@ -56,7 +64,10 @@ TEST(Destructure, GuardRejects) { TEST(Destructure, SingleMember) { Point p{42, 0}; - int result = match(p) | on($(has<&Point::x>) >> [](int x) { return x; }, _ >> 0); + int result = match(p) + | on( + $(has<&Point::x>) >> [](int x) { return x; }, + _ >> 0); EXPECT_EQ(result, 42); } @@ -65,14 +76,16 @@ TEST(Destructure, StructuralBindingProducesMemberValues) { Point p{3, 4}; int r1 = match(p) - | on($(has<&Point::x, &Point::y>) >> - [](int x, int y) { return x + y; }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); int r2 = match(p) - | on($(has<&Point::x, &Point::y>) >> - [](int x, int y) { return x + y; }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); EXPECT_EQ(r1, r2); } @@ -81,12 +94,15 @@ TEST(Destructure, ThreeMembers) { Packet pkt{1, 0, "hello"}; auto result = match(pkt) - | on($(has<&Packet::type, &Packet::length, &Packet::payload>) - >> - [](int t, int l, const std::string &p) { - return t + l + static_cast(p.size()); - }, - _ >> 0); + | on( + $(has<&Packet::type, + &Packet::length, + &Packet::payload>) + >> + [](int t, int l, const std::string &p) { + return t + l + static_cast(p.size()); + }, + _ >> 0); EXPECT_EQ(result, 6); // 1 + 0 + 5 } @@ -97,8 +113,9 @@ TEST(HasGuard, BasicGuardAccepts) { Packet pkt{0x01, 0, ""}; int result = match(pkt) - | on(has<&Packet::type>[_0 == 0x01] >> [] { return 1; }, - _ >> 0); + | on( + has<&Packet::type>[_ == 0x01] >> [] { return 1; }, + _ >> 0); EXPECT_EQ(result, 1); } @@ -107,8 +124,9 @@ TEST(HasGuard, BasicGuardRejects) { Packet pkt{0x02, 0, ""}; int result = match(pkt) - | on(has<&Packet::type>[_0 == 0x01] >> [] { return 1; }, - _ >> 0); + | on( + has<&Packet::type>[_ == 0x01] >> [] { return 1; }, + _ >> 0); EXPECT_EQ(result, 0); } @@ -116,11 +134,13 @@ TEST(HasGuard, BasicGuardRejects) { TEST(HasGuard, MultiMemberGuard) { Packet pkt{0x01, 0, ""}; - int result = - match(pkt) - | on(has<&Packet::type, &Packet::length>[_0 == 0x01 && arg<1> == 0] - >> [] { return 1; }, - _ >> 0); + int result = match(pkt) + | on( + has<&Packet::type, + &Packet::length>[packet_type == 0x01 + && packet_length == 0] + >> [] { return 1; }, + _ >> 0); EXPECT_EQ(result, 1); } @@ -129,8 +149,10 @@ TEST(HasGuard, DoesNotBindToHandler) { Packet pkt{0x01, 0, ""}; int result = match(pkt) - | on(has<&Packet::type>[_0 == 0x01] >> [] { return 42; }, - _ >> 0); + | on( + has<&Packet::type>[_ == 0x01] >> + [] { return 42; }, + _ >> 0); // Handler takes no arguments — has<> does not bind. EXPECT_EQ(result, 42); @@ -140,9 +162,10 @@ TEST(HasGuard, FallsThroughOnGuardFail) { Packet pkt{0x02, 10, "data"}; int result = match(pkt) - | on(has<&Packet::type>[_0 == 0x01] >> [] { return 1; }, - has<&Packet::type>[_0 == 0x02] >> [] { return 2; }, - _ >> 0); + | on( + has<&Packet::type>[_ == 0x01] >> [] { return 1; }, + has<&Packet::type>[_ == 0x02] >> [] { return 2; }, + _ >> 0); EXPECT_EQ(result, 2); } diff --git a/tests/tests_guard.cpp b/tests/tests_guard.cpp index 270076f2..985c7ff9 100644 --- a/tests/tests_guard.cpp +++ b/tests/tests_guard.cpp @@ -11,158 +11,118 @@ namespace { int y; }; + PTN_BIND(Point, x, y); + } // namespace -TEST(Guard, UnaryPlaceholderPredicate) { +TEST(Guard, WildcardActsAsSingleValuePlaceholder) { int inside = 6; int outside = 20; - int inside_result = ptn::match(inside) - | ptn::on(ptn::$[PTN_LET(value, - value > 0 && value < 10)] - >> 1, - ptn::__ >> 0); - - int outside_result = ptn::match(outside) - | ptn::on( - ptn::$[PTN_LET(value, - value > 0 && value < 10)] >> 1, - ptn::__ >> 0); + int inside_result = match(inside) + | on($[_ > 0 && _ < 10] >> 1, _ >> 0); + int outside_result = match(outside) + | on($[_ > 0 && _ < 10] >> 1, _ >> 0); EXPECT_EQ(inside_result, 1); EXPECT_EQ(outside_result, 0); } -TEST(Guard, RangeHelperModes) { - int boundary = 10; - - int closed_result = ptn::match(boundary) - | ptn::on(ptn::$[ptn::rng(0, 10)] >> 1, - ptn::__ >> 0); - - int open_result = ptn::match(boundary) - | ptn::on( - ptn::$[ptn::rng(0, 10, ptn::pat::mod::open)] >> 1, - ptn::__ >> 0); +TEST(Guard, WildcardPlaceholderSupportsArithmetic) { + int value = 5; + int result = match(value) | on($[_ * _ == 25] >> 1, _ >> 0); - EXPECT_EQ(closed_result, 1); - EXPECT_EQ(open_result, 0); + EXPECT_EQ(result, 1); } -TEST(Guard, MultiArgExpressionPredicate) { - Point p{3, 4}; - - int result = ptn::match(p) - | ptn::on(ptn::$(ptn::has<&Point::x, &Point::y>) - [ptn::arg<0> * ptn::arg<0> - + ptn::arg<1> * ptn::arg<1> - == 25] - >> 1, - ptn::__ >> 0); +TEST(Guard, WildcardPlaceholderSupportsAllBinaryOperators) { + int value = 5; + int result = match(value) + | on($[(_ + 1 == 6) && (_ - 1 == 4) && (_ * 2 == 10) + && (_ / 5 == 1) && (_ % 2 == 1) && (_ != 4) + && (_ < 6) && (_ <= 5) && (_ > 4) && (_ >= 5) + && (4 < _)] + >> 1, + $[_ == 0 || _ == 5] >> 2, + _ >> 0); EXPECT_EQ(result, 1); } -TEST(Guard, MultiArgCallablePredicate) { - Point p{2, 5}; - - int result = ptn::match(p) - | ptn::on(ptn::$(ptn::has<&Point::x, &Point::y>)[( - [](int x, int y) { return x < y; })] - >> 1, - ptn::__ >> 0); +TEST(Guard, WildcardPatternKeepsItsValueTypeProperties) { + using wildcard_t = std::decay_t; - EXPECT_EQ(result, 1); + static_assert(std::is_empty_v); + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(wildcard_t) == 1); } -// -- _N placeholder aliases -- - -TEST(Guard, ZeroPlaceholderSingleValue) { - int x = 7; - int result = match(x) | on($[_0 > 5] >> [](int v) { return v; }, _ >> 0); +TEST(Guard, RangeHelperModes) { + int boundary = 10; - EXPECT_EQ(result, 7); -} + int closed_result = ptn::match(boundary) + | ptn::on(ptn::$[ptn::rng(0, 10)] >> 1, + ptn::_ >> 0); -TEST(Guard, ZeroPlaceholderCompoundGuard) { - int x = 6; - int result = - match(x) | on($[_0 > 0 && _0 < 10] >> [](int v) { return v; }, _ >> -1); + int open_result = ptn::match(boundary) + | ptn::on( + ptn::$[ptn::rng(0, 10, ptn::pat::mod::open)] + >> 1, + ptn::_ >> 0); - EXPECT_EQ(result, 6); + EXPECT_EQ(closed_result, 1); + EXPECT_EQ(open_result, 0); } -TEST(Guard, MultiArgWithPlaceholderAliases) { - Point p{3, 4}; +TEST(Guard, NamedMultiValueExpressionPredicate) { + Point point{3, 4}; - int result = match(p) - | on($(has<&Point::x, &Point::y>)[_0 * _0 - + arg<1> * arg<1> - == 25] >> 1, + int result = match(point) + | on($(has<&Point::x, &Point::y>)[x * x + y * y == 25] + >> 1, _ >> 0); EXPECT_EQ(result, 1); } -TEST(Guard, PlaceholderAliasEquivalentToArg) { - Point p{2, 5}; +TEST(Guard, MultiValueCallablePredicate) { + Point point{2, 5}; - int r1 = match(p) - | on($(has<&Point::x, &Point::y>)[_0 + arg<1> == 7] >> 1, _ >> 0); - - int r2 = match(p) - | on($(has<&Point::x, &Point::y>)[arg<0> + arg<1> == 7] >> 1, - _ >> 0); + int result = match(point) + | on($(has<&Point::x, &Point::y>)[([](int left, + int right) { + return left < right; + })] >> 1, + _ >> 0); - EXPECT_EQ(r1, r2); + EXPECT_EQ(result, 1); } -TEST(Guard, MixedGuardExprAndCallable) { - int x = 8; - auto even = [](auto v) { return v % 2 == 0; }; - int result = match(x) | on($[_0 > 5 && even] >> [](int v) { return v; }, _ >> -1); +TEST(Guard, WildcardPlaceholderComposesWithCallablePredicate) { + int value = 8; + auto even = [](auto current) { return current % 2 == 0; }; + int result = match(value) + | on( + $[_ > 5 && even] >> + [](int current) { return current; }, + _ >> -1); EXPECT_EQ(result, 8); } -TEST(Guard, MixedGuardCallableRejects) { - int x = 7; - auto even = [](auto v) { return v % 2 == 0; }; - int result = match(x) | on($[_0 > 5 && even] >> [](int v) { return v; }, _ >> -1); +TEST(Guard, WildcardPlaceholderCallableCanReject) { + int value = 7; + auto even = [](auto current) { return current % 2 == 0; }; + int result = match(value) + | on( + $[_ > 5 && even] >> + [](int current) { return current; }, + _ >> -1); EXPECT_EQ(result, -1); } -TEST(Guard, NamedSingleArgGuardMacro) { - int x = 7; - int result = match(x) - | on($[PTN_WHERE((value), value > 5)] >> [](int v) { return v; }, - _ >> 0); - - EXPECT_EQ(result, 7); -} - -TEST(Guard, NamedSingleArgLetMacro) { - int x = 7; - int result = match(x) - | on($[PTN_LET(value, value > 5)] >> [](int v) { return v; }, - _ >> 0); - - EXPECT_EQ(result, 7); -} - -TEST(Guard, NamedMultiArgGuardMacro) { - Point p{2, 5}; - - int result = - match(p) | on($(has<&Point::x, &Point::y>) - [PTN_WHERE((x, y), x < y)] >> 1, - _ >> 0); - - EXPECT_EQ(result, 1); -} - -TEST(Guard, NamedFiveArgGuardMacro) { +TEST(Guard, BlockScopeNamesSupportFiveValues) { struct Record { int a; int b; @@ -171,40 +131,17 @@ TEST(Guard, NamedFiveArgGuardMacro) { int e; }; - Record r{1, 2, 3, 4, 10}; + PTN_BIND(Record, a, b, c, d, e); - int result = - match(r) | on($(has<&Record::a, + Record record{1, 2, 3, 4, 10}; + int result = match(record) + | on($(has<&Record::a, &Record::b, &Record::c, &Record::d, - &Record::e>) - [PTN_WHERE((a, b, c, d, e), a + b + c + d == e)] >> 1, + &Record::e>)[a + b + c + d == e] + >> 1, _ >> 0); EXPECT_EQ(result, 1); } - -TEST(Guard, NamedGuardMacroComposesWithCallablePredicate) { - int x = 8; - auto even = [](int v) { return v % 2 == 0; }; - - int result = - match(x) | on($[PTN_WHERE((value), value > 5) && even] - >> [](int v) { return v; }, - _ >> -1); - - EXPECT_EQ(result, 8); -} - -TEST(Guard, LetMacroComposesWithCallablePredicate) { - int x = 8; - auto even = [](int v) { return v % 2 == 0; }; - - int result = - match(x) | on($[PTN_LET(value, value > 5) && even] - >> [](int v) { return v; }, - _ >> -1); - - EXPECT_EQ(result, 8); -} diff --git a/tests/tests_literal_pattern.cpp b/tests/tests_literal_pattern.cpp index e8e2414c..feb3c3f5 100644 --- a/tests/tests_literal_pattern.cpp +++ b/tests/tests_literal_pattern.cpp @@ -8,53 +8,57 @@ using namespace ptn; TEST(LiteralPattern, LitMatchesExpectedValue) { int x = 5; - int result = ptn::match(x) | ptn::on(ptn::lit(5) >> 42, ptn::__ >> -1); + int result = ptn::match(x) + | ptn::on(ptn::lit(5) >> 42, ptn::_ >> -1); EXPECT_EQ(result, 42); } TEST(LiteralPattern, LitOtherwiseFallback) { int x = 3; - int result = ptn::match(x) | ptn::on(ptn::lit(5) >> 42, ptn::__ >> -1); + int result = ptn::match(x) + | ptn::on(ptn::lit(5) >> 42, ptn::_ >> -1); EXPECT_EQ(result, -1); } TEST(LiteralPattern, LitMatchesStringLiteral) { - std::string s = "hello"; - int result = ptn::match(s) | ptn::on(ptn::lit("hello") >> 1, ptn::__ >> 0); + std::string s = "hello"; + int result = ptn::match(s) + | ptn::on(ptn::lit("hello") >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } TEST(LiteralPattern, LitStringOtherwiseFallback) { - std::string s = "world"; - int result = ptn::match(s) | ptn::on(ptn::lit("hello") >> 1, ptn::__ >> 0); + std::string s = "world"; + int result = ptn::match(s) + | ptn::on(ptn::lit("hello") >> 1, ptn::_ >> 0); EXPECT_EQ(result, 0); } TEST(LiteralPattern, LitMatchesCompileTimeValue) { int x = 5; - int result = ptn::match(x) | ptn::on(ptn::val<5> >> 42, ptn::__ >> -1); + int result = ptn::match(x) + | ptn::on(ptn::val<5> >> 42, ptn::_ >> -1); EXPECT_EQ(result, 42); } TEST(LiteralPattern, LitCompileTimeOtherwiseFallback) { int x = 3; - int result = ptn::match(x) | ptn::on(ptn::val<5> >> 42, ptn::__ >> -1); + int result = ptn::match(x) + | ptn::on(ptn::val<5> >> 42, ptn::_ >> -1); EXPECT_EQ(result, -1); } TEST(LiteralPattern, LitSelectsSparseDenseCases) { - auto cases = on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); + auto cases = on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); int one = 1; int two = 2; @@ -73,26 +77,20 @@ TEST(LiteralPattern, LitInlineOnSelectsDenseCases) { int other = 7; int one_result = ptn::match(one) - | on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); + | on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); int ten_result = ptn::match(ten) - | on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); + | on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); int other_result = ptn::match(other) - | on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); + | on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); EXPECT_EQ(one_result, 1); EXPECT_EQ(ten_result, 10); @@ -104,33 +102,24 @@ TEST(LiteralPattern, LitStaticOnFactorySelectsDenseCases) { int ten = 10; int other = 7; - int one_result = ptn::match(one) - | ptn::static_on([] { - return on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); - }); - int ten_result = ptn::match(ten) - | ptn::static_on([] { - return on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); + int one_result = ptn::match(one) | ptn::static_on([] { + return on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); + }); + int ten_result = ptn::match(ten) | ptn::static_on([] { + return on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); + }); + int other_result = ptn::match(other) | ptn::static_on([] { + return on(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); }); - int other_result = ptn::match(other) - | ptn::static_on([] { - return on( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0 - ); - }); EXPECT_EQ(one_result, 1); EXPECT_EQ(ten_result, 10); @@ -143,23 +132,20 @@ TEST(LiteralPattern, LitPtnOnMacroSelectsDenseCases) { int other = 7; int one_result = ptn::match(one) - | PTN_ON( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0); + | PTN_ON(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); int ten_result = ptn::match(ten) - | PTN_ON( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0); + | PTN_ON(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); int other_result = ptn::match(other) - | PTN_ON( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::val<10> >> 10, - __ >> 0); + | PTN_ON(ptn::val<1> >> 1, + ptn::val<2> >> 2, + ptn::val<10> >> 10, + _ >> 0); EXPECT_EQ(one_result, 1); EXPECT_EQ(ten_result, 10); @@ -167,18 +153,20 @@ TEST(LiteralPattern, LitPtnOnMacroSelectsDenseCases) { } TEST(LiteralPattern, LitCiMatchesAsciiCaseInsensitive) { - std::string s = "HeLLo"; - int result = ptn::match(s) | ptn::on(ptn::lit_ci("hello") >> 1, ptn::__ >> 0); + std::string s = "HeLLo"; + int result = ptn::match(s) + | ptn::on(ptn::lit_ci("hello") >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } -#if (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) || __cplusplus >= 202002L +#if (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) \ + || __cplusplus >= 202002L TEST(LiteralPattern, ValSupportsFloatingPointInCpp20) { double x = 3.25; - int hit = ptn::match(x) | ptn::on(ptn::val<3.25> >> 7, ptn::__ >> -1); + int hit = ptn::match(x) + | ptn::on(ptn::val<3.25> >> 7, ptn::_ >> -1); EXPECT_EQ(hit, 7); } #endif - diff --git a/tests/tests_match_flow.cpp b/tests/tests_match_flow.cpp index b0ee7de9..7ae8d59c 100644 --- a/tests/tests_match_flow.cpp +++ b/tests/tests_match_flow.cpp @@ -95,9 +95,10 @@ namespace ptn::pat::base { TEST(MatchFlow, FallbackCaseCanBindSubject) { int x = 7; int result = ptn::match(x) - | ptn::on(ptn::lit(1) >> 10, - ptn::$ >> [](int v) { return v * 2; }, - ptn::__ >> -1); + | ptn::on( + ptn::lit(1) >> 10, + ptn::$ >> [](int v) { return v * 2; }, + ptn::_ >> -1); EXPECT_EQ(result, 14); } @@ -106,8 +107,10 @@ TEST(MatchFlow, WildcardCaseRunsFallbackHandler) { int x = 9; int hit = 0; - ptn::match(x) | ptn::on(ptn::lit(1) >> [&] { hit = 1; }, - ptn::__ >> [&] { hit = 2; }); + ptn::match(x) + | ptn::on( + ptn::lit(1) >> [&] { hit = 1; }, + ptn::_ >> [&] { hit = 2; }); EXPECT_EQ(hit, 2); } @@ -118,8 +121,10 @@ TEST(MatchFlow, SubjectBindsAsLvalue) { int x = 11; int result = ptn::match(x) - | ptn::on(ForwardingProbePattern{} >> [](int v) { return v; }, - ptn::__ >> -1); + | ptn::on( + ForwardingProbePattern{} >> + [](int v) { return v; }, + ptn::_ >> -1); EXPECT_EQ(result, 11); EXPECT_EQ(ForwardingProbePattern::lvalue_bind_calls, 1); @@ -132,9 +137,10 @@ TEST(MatchFlow, GuardedCaseBindsOnlyOnceOnMatch) { int x = 11; int result = ptn::match(x) - | ptn::on(ForwardingProbePattern{}[ptn::_0 > 0] - >> [](int v) { return v; }, - ptn::__ >> -1); + | ptn::on( + ForwardingProbePattern{}[ptn::_ > 0] >> + [](int v) { return v; }, + ptn::_ >> -1); EXPECT_EQ(result, 11); EXPECT_EQ(ForwardingProbePattern::lvalue_bind_calls, 1); @@ -144,7 +150,7 @@ TEST(MatchFlow, GuardedCaseBindsOnlyOnceOnMatch) { TEST(MatchFlow, GuardedPathAvoidsManualDoubleBindSequence) { int x = 11; - auto guarded = ForwardingProbePattern{}[ptn::_0 > 0]; + auto guarded = ForwardingProbePattern{}[ptn::_ > 0]; // Simulate a naive two-step flow: // 1) guarded.match(x) -> inner.bind(x) inside guard evaluation @@ -166,9 +172,10 @@ TEST(MatchFlow, GuardedPathAvoidsManualDoubleBindSequence) { ForwardingProbePattern::rvalue_bind_calls = 0; int result = ptn::match(x) - | ptn::on(ForwardingProbePattern{}[ptn::_0 > 0] - >> [](int v) { return v; }, - ptn::__ >> -1); + | ptn::on( + ForwardingProbePattern{}[ptn::_ > 0] >> + [](int v) { return v; }, + ptn::_ >> -1); EXPECT_EQ(result, 11); EXPECT_EQ(ForwardingProbePattern::lvalue_bind_calls, 1); @@ -181,8 +188,10 @@ TEST(MatchFlow, BindCountMatrixNoBindWhenPatternMisses) { int x = 11; int result = ptn::match(x) - | ptn::on(ConditionalProbePattern{} >> [](int) { return 1; }, - ptn::__ >> 0); + | ptn::on( + ConditionalProbePattern{} >> + [](int) { return 1; }, + ptn::_ >> 0); EXPECT_EQ(result, 0); EXPECT_EQ(ConditionalProbePattern::bind_calls, 0); @@ -194,8 +203,10 @@ TEST(MatchFlow, BindCountMatrixOneBindWhenPatternMatches) { int x = 11; int result = ptn::match(x) - | ptn::on(ConditionalProbePattern{} >> [](int v) { return v; }, - ptn::__ >> -1); + | ptn::on( + ConditionalProbePattern{} >> + [](int v) { return v; }, + ptn::_ >> -1); EXPECT_EQ(result, 11); EXPECT_EQ(ConditionalProbePattern::bind_calls, 1); @@ -207,9 +218,10 @@ TEST(MatchFlow, BindCountMatrixGuardMissBindsOnceThenFallback) { int x = 11; int result = ptn::match(x) - | ptn::on(ForwardingProbePattern{}[ptn::_0 > 100] - >> [](int) { return 1; }, - ptn::__ >> 0); + | ptn::on( + ForwardingProbePattern{}[ptn::_ > 100] >> + [](int) { return 1; }, + ptn::_ >> 0); EXPECT_EQ(result, 0); EXPECT_EQ(ForwardingProbePattern::lvalue_bind_calls, 1); @@ -222,10 +234,12 @@ TEST(MatchFlow, BindCountMatrixGuardMissThenNextCaseAddsSecondBind) { int x = 11; int result = ptn::match(x) - | ptn::on(ForwardingProbePattern{}[ptn::_0 > 100] - >> [](int) { return -1; }, - ForwardingProbePattern{} >> [](int v) { return v; }, - ptn::__ >> 0); + | ptn::on( + ForwardingProbePattern{}[ptn::_ > 100] >> + [](int) { return -1; }, + ForwardingProbePattern{} >> + [](int v) { return v; }, + ptn::_ >> 0); EXPECT_EQ(result, 11); EXPECT_EQ(ForwardingProbePattern::lvalue_bind_calls, 2); @@ -238,9 +252,10 @@ TEST(MatchFlow, ZeroBindVariantStyleCaseSkipsBindInTypedEval) { ZeroBindProbePattern::bind_calls = 0; int x = 7; - int result = - ptn::match(x) | ptn::on(ZeroBindProbePattern{} >> [] { return 42; }, - ptn::__ >> -1); + int result = ptn::match(x) + | ptn::on( + ZeroBindProbePattern{} >> [] { return 42; }, + ptn::_ >> -1); EXPECT_EQ(result, 42); // Fast path contract: zero-bind case should not call bind(). @@ -253,7 +268,7 @@ TEST(MatchFlow, PipeOnUsesWildcardFallback) { int result = ptn::match(x) | ptn::on(ptn::lit(1) >> 10, ptn::lit(2) >> 20, - ptn::__ >> -1); + ptn::_ >> -1); EXPECT_EQ(result, -1); } @@ -262,7 +277,7 @@ TEST(MatchFlow, PipeOnWildcardFallbackReturnsValue) { int x = 2; int result = ptn::match(x) - | ptn::on(ptn::lit(1) >> 10, ptn::__ >> 99); + | ptn::on(ptn::lit(1) >> 10, ptn::_ >> 99); EXPECT_EQ(result, 99); } @@ -272,10 +287,10 @@ TEST(MatchFlow, PipeOnVariantGuardedThenSameAltPlainGuardHit) { V v = 123; int result = ptn::match(v) - | ptn::on(ptn::$(ptn::is())[ptn::_0 > 100] >> 10, + | ptn::on(ptn::$(ptn::is())[ptn::_ > 100] >> 10, ptn::is() >> 1, ptn::is() >> 2, - ptn::__ >> 0); + ptn::_ >> 0); EXPECT_EQ(result, 10); } @@ -286,10 +301,10 @@ TEST(MatchFlow, V v = 7; int result = ptn::match(v) - | ptn::on(ptn::$(ptn::is())[ptn::_0 > 100] >> 10, + | ptn::on(ptn::$(ptn::is())[ptn::_ > 100] >> 10, ptn::is() >> 1, ptn::is() >> 2, - ptn::__ >> 0); + ptn::_ >> 0); EXPECT_EQ(result, 1); } @@ -300,10 +315,10 @@ TEST(MatchFlow, V v = std::string("ok"); int result = ptn::match(v) - | ptn::on(ptn::$(ptn::is())[ptn::_0 > 100] >> 10, + | ptn::on(ptn::$(ptn::is())[ptn::_ > 100] >> 10, ptn::is() >> 1, ptn::is() >> 2, - ptn::__ >> 0); + ptn::_ >> 0); EXPECT_EQ(result, 2); } diff --git a/tests/tests_named_placeholder.cpp b/tests/tests_named_placeholder.cpp index e2043d75..b66717a8 100644 --- a/tests/tests_named_placeholder.cpp +++ b/tests/tests_named_placeholder.cpp @@ -1,10 +1,8 @@ // Tests for PTN_BIND named placeholder macro. // -// Verifies that named placeholders declared via PTN_BIND produce the -// same types and runtime results as the positional placeholders (_0, -// arg) they replace. All tests exercise the full match pipeline -// (match | on($[guard] >> handler)) to ensure end-to-end -// correctness. +// Verifies that PTN_BIND declares readable names for bound tuple +// positions. All tests exercise the full match pipeline +// (match | on($[guard] >> handler)) end to end. #include @@ -49,7 +47,7 @@ namespace { }; // Declare named placeholders for each struct. - // These are namespace-scoped inline constexpr arg_t objects. + // These are constexpr arg_t objects. PTN_BIND(Point, x, y); PTN_BIND(Packet, type, len); PTN_BIND(Triple, a, b, c); @@ -59,6 +57,25 @@ namespace { } // namespace +TEST(NamedPlaceholder, SupportsBlockScopeDeclarations) { + struct Pair { + int left; + int right; + }; + + PTN_BIND(Pair, left, right); + + Pair pair{2, 5}; + auto result = ptn::match(pair) + | ptn::on( + ptn::$(ptn::has<&Pair::left, + &Pair::right>)[left < right] + >> 1, + ptn::_ >> 0); + + EXPECT_EQ(result, 1); +} + // ========================================================================= // Type correctness: PTN_BIND names must be arg_t of the right // index. @@ -89,31 +106,27 @@ TEST(NamedPlaceholder, ThreeArgTypesMatchPositions) { // Runtime correctness: named placeholders in guard expressions. // ========================================================================= -TEST(NamedPlaceholder, SingleValueGuard) { - // Same as: $[_0 > 5] - // Now: $[x > 5] (where x maps to arg<0>) +TEST(NamedPlaceholder, SingleValueUsesWildcardPlaceholder) { int val = 10; auto result = ptn::match(val) | PTN_ON( - ptn::$[x > 5] >> [](int v) { return v * 2; }, + ptn::$[ptn::_ > 5] >> + [](int v) { return v * 2; }, ptn::_ >> 0); EXPECT_EQ(result, 20); } -TEST(NamedPlaceholder, SingleValueGuardFails) { +TEST(NamedPlaceholder, SingleValueWildcardGuardFails) { int val = 3; auto result = ptn::match(val) | PTN_ON( - ptn::$[x > 5] >> [](int v) { return v * 2; }, + ptn::$[ptn::_ > 5] >> + [](int v) { return v * 2; }, ptn::_ >> 0); EXPECT_EQ(result, 0); } TEST(NamedPlaceholder, StructuralGuardTwoMembers) { - // Same as: - // $(has<&Point::x, &Point::y>)[_0*_0 + arg<1>*arg<1> == 25] - // Now: - // $(has<&Point::x, &Point::y>)[x*x + y*y == 25] Point p{3, 4}; auto result = ptn::match(p) | PTN_ON( @@ -192,35 +205,6 @@ TEST(NamedPlaceholder, ThreeMemberGuardFails) { EXPECT_EQ(result, 0); } -// ========================================================================= -// Equivalence: named and positional placeholders produce identical -// results for the same expression. -// ========================================================================= - -TEST(NamedPlaceholder, NamedEquivalentToPositional) { - using ptn::_0; - using ptn::arg; - - Point p{3, 4}; - - auto named = ptn::match(p) - | PTN_ON( - ptn::$(ptn::has<&Point::x, - &Point::y>)[x * x + y * y == 25] - >> 1, - ptn::_ >> 0); - - auto positional = ptn::match(p) - | PTN_ON( - ptn::$(ptn::has<&Point::x, &Point::y>) - [_0 * _0 + arg<1> * arg<1> == 25] - >> 1, - ptn::_ >> 0); - - EXPECT_EQ(named, positional); - EXPECT_EQ(named, 1); -} - // ========================================================================= // Arithmetic expressions in guards (not just comparisons). // ========================================================================= @@ -347,59 +331,52 @@ TEST(NamedPlaceholder, FiveMemberGuardFails) { } // ========================================================================= -// PTN_WHERE arity coverage: 3 and 4 parameter variants. -// (1, 2, and 5 are already tested in tests_guard.cpp.) +// PTN_BIND arity coverage: 3 and 4 parameter variants. // ========================================================================= -TEST(NamedPlaceholder, WhereThreeArgGuard) { - // PTN_WHERE((a, b, c), a + b == c) +TEST(NamedPlaceholder, BindThreeArgGuard) { Triple t{2, 3, 5}; auto result = ptn::match(t) - | PTN_ON( - ptn::$( - ptn::has<&Triple::a, &Triple::b, &Triple::c>) - [PTN_WHERE((x, y, z), x + y == z)] - >> 1, - ptn::_ >> 0); + | PTN_ON(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 1); } -TEST(NamedPlaceholder, WhereThreeArgGuardFails) { +TEST(NamedPlaceholder, BindThreeArgGuardFails) { Triple t{2, 3, 6}; auto result = ptn::match(t) - | PTN_ON( - ptn::$( - ptn::has<&Triple::a, &Triple::b, &Triple::c>) - [PTN_WHERE((x, y, z), x + y == z)] - >> 1, - ptn::_ >> 0); + | PTN_ON(ptn::$(ptn::has<&Triple::a, + &Triple::b, + &Triple::c>)[a + b == c] + >> 1, + ptn::_ >> 0); EXPECT_EQ(result, 0); } -TEST(NamedPlaceholder, WhereFourArgGuard) { - // PTN_WHERE((a, b, c, d), a + b + c == d) +TEST(NamedPlaceholder, BindFourArgGuard) { Quad q{1, 2, 3, 6}; auto result = ptn::match(q) | PTN_ON( ptn::$(ptn::has<&Quad::a, &Quad::b, &Quad::c, - &Quad::d>) - [PTN_WHERE((w, x, y, z), w + x + y == z)] + &Quad::d>)[qa + qb + qc == qd] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 1); } -TEST(NamedPlaceholder, WhereFourArgGuardFails) { +TEST(NamedPlaceholder, BindFourArgGuardFails) { Quad q{1, 2, 3, 7}; auto result = ptn::match(q) | PTN_ON( ptn::$(ptn::has<&Quad::a, &Quad::b, &Quad::c, - &Quad::d>) - [PTN_WHERE((w, x, y, z), w + x + y == z)] + &Quad::d>)[qa + qb + qc == qd] >> 1, ptn::_ >> 0); EXPECT_EQ(result, 0); diff --git a/tests/tests_pipeline_match.cpp b/tests/tests_pipeline_match.cpp index 8595691c..47fb57e5 100644 --- a/tests/tests_pipeline_match.cpp +++ b/tests/tests_pipeline_match.cpp @@ -9,28 +9,30 @@ using namespace ptn; TEST(PipelineMatch, LitMatchesFirstCase) { int x = 1; - int result = match(x) | on(lit(1) >> 42, __ >> 0); + int result = match(x) | on(lit(1) >> 42, _ >> 0); EXPECT_EQ(result, 42); } TEST(PipelineMatch, LitFallsToWildcard) { int x = 99; - int result = match(x) | on(lit(1) >> 42, lit(2) >> 84, __ >> -1); + int result = match(x) | on(lit(1) >> 42, lit(2) >> 84, _ >> -1); EXPECT_EQ(result, -1); } TEST(PipelineMatch, LitMultipleCasesSelectsCorrect) { int x = 3; - int result = match(x) | on(lit(1) >> 10, lit(2) >> 20, lit(3) >> 30, __ >> 0); + int result = match(x) + | on( + lit(1) >> 10, lit(2) >> 20, lit(3) >> 30, _ >> 0); EXPECT_EQ(result, 30); } TEST(PipelineMatch, CompileTimeLitMatchesValue) { int x = 2; - int result = match(x) | on(val<1> >> 10, val<2> >> 20, __ >> 0); + int result = match(x) | on(val<1> >> 10, val<2> >> 20, _ >> 0); EXPECT_EQ(result, 20); } @@ -39,14 +41,14 @@ TEST(PipelineMatch, VoidHandlerSideEffect) { int x = 1; int hit = 0; - match(x) | on(lit(1) >> [&] { hit = 1; }, __ >> [&] { hit = -1; }); + match(x) | on(lit(1) >> [&] { hit = 1; }, _ >> [&] { hit = -1; }); EXPECT_EQ(hit, 1); } TEST(PipelineMatch, LambdaHandlerReturnsValue) { int x = 5; - int result = match(x) | on(lit(5) >> [] { return 500; }, __ >> 0); + int result = match(x) | on(lit(5) >> [] { return 500; }, _ >> 0); EXPECT_EQ(result, 500); } @@ -55,7 +57,8 @@ TEST(PipelineMatch, VariantTypeIsDispatch) { using V = std::variant; V v = std::string("hello"); - int result = match(v) | on(is() >> 1, is() >> 2, __ >> 0); + int result = match(v) + | on(is() >> 1, is() >> 2, _ >> 0); EXPECT_EQ(result, 2); } @@ -64,39 +67,41 @@ TEST(PipelineMatch, VariantTypeAsBindsValue) { using V = std::variant; V v = 42; - int result = match(v) | on($(is()) >> [](int i) { return i * 2; }, __ >> 0); + int result = match(v) + | on( + $(is()) >> [](int i) { return i * 2; }, + _ >> 0); EXPECT_EQ(result, 84); } TEST(PipelineMatch, GuardedBindMatchesWhenTrue) { int x = 10; - int result = - match(x) | on($[PTN_LET(value, value > 5)] >> [](int v) { return v; }, - __ >> 0); + int result = match(x) + | on($[_ > 5] >> [](int v) { return v; }, _ >> 0); EXPECT_EQ(result, 10); } TEST(PipelineMatch, GuardedBindFallsWhenFalse) { int x = 3; - int result = - match(x) | on($[PTN_LET(value, value > 5)] >> [](int v) { return v; }, - __ >> -1); + int result = match(x) + | on($[_ > 5] >> [](int v) { return v; }, _ >> -1); EXPECT_EQ(result, -1); } TEST(PipelineMatch, FirstMatchWins) { int x = 1; - int result = match(x) | on(lit(1) >> 100, lit(1) >> 200, __ >> 0); + int result = match(x) | on(lit(1) >> 100, lit(1) >> 200, _ >> 0); EXPECT_EQ(result, 100); } TEST(PipelineMatch, FullyQualifiedWorks) { int x = 1; - int result = ptn::match(x) | ptn::on(ptn::lit(1) >> 42, ptn::__ >> 0); + int result = ptn::match(x) + | ptn::on(ptn::lit(1) >> 42, ptn::_ >> 0); EXPECT_EQ(result, 42); } @@ -124,50 +129,36 @@ TEST(PipelineMatch, UnderscoreWildcardWithVoidHandler) { EXPECT_EQ(hit, 1); } -TEST(PipelineMatch, UnderscoreAndDoubleUnderscoreAreEquivalent) { - int x = 5; - int r1 = match(x) | on(lit(5) >> 50, _ >> 0); - int r2 = match(x) | on(lit(5) >> 50, __ >> 0); - - EXPECT_EQ(r1, r2); -} - TEST(PipelineMatch, DollarBindCapturesSubject) { int x = 42; - int result = match(x) | on($ >> [](int v) { return v * 2; }, _ >> 0); + int result = match(x) + | on($ >> [](int v) { return v * 2; }, _ >> 0); EXPECT_EQ(result, 84); } TEST(PipelineMatch, DollarBindWithGuard) { int x = 10; - int result = match(x) | on($[_0 > 5] >> [](int v) { return v; }, _ >> -1); + int result = match(x) + | on($[_ > 5] >> [](int v) { return v; }, _ >> -1); EXPECT_EQ(result, 10); } TEST(PipelineMatch, DollarBindGuardRejects) { int x = 3; - int result = match(x) | on($[_0 > 5] >> [](int v) { return v; }, _ >> -1); + int result = match(x) + | on($[_ > 5] >> [](int v) { return v; }, _ >> -1); EXPECT_EQ(result, -1); } -TEST(PipelineMatch, DollarBindSupportsNamedGuard) { - int x = 7; - int r1 = match(x) | on($[_0 > 0] >> [](int v) { return v; }, _ >> 0); - int r2 = - match(x) | on($[PTN_LET(value, value > 0)] >> [](int v) { return v; }, - _ >> 0); - - EXPECT_EQ(r1, r2); -} - TEST(PipelineMatch, IsVariableTemplateMatchesType) { using V = std::variant; V v = 42; - int result = match(v) | on(is >> 1, is >> 2, _ >> 0); + int result = match(v) + | on(is >> 1, is >> 2, _ >> 0); EXPECT_EQ(result, 1); } @@ -176,7 +167,10 @@ TEST(PipelineMatch, AsVariableTemplateBindsValue) { using V = std::variant; V v = 42; - int result = match(v) | on($(is) >> [](int i) { return i * 2; }, _ >> 0); + int result = match(v) + | on( + $(is) >> [](int i) { return i * 2; }, + _ >> 0); EXPECT_EQ(result, 84); } @@ -185,10 +179,10 @@ TEST(PipelineMatch, IsWithSubPattern) { using V = std::variant; V v = std::string("hello"); - auto result = - match(v) | on($(is()) >> [](const std::string &s) { - return s; - }, + auto result = match(v) + | on( + $(is()) >> + [](const std::string &s) { return s; }, _ >> std::string("other")); EXPECT_EQ(result, "hello"); @@ -199,9 +193,10 @@ TEST(PipelineMatch, AsWithGuard) { V v = 42; int result = match(v) - | on($(is())[_0 > 100] >> [](int i) { return i; }, - $(is()) >> [](int i) { return -i; }, - _ >> 0); + | on( + $(is())[_ > 100] >> [](int i) { return i; }, + $(is()) >> [](int i) { return -i; }, + _ >> 0); EXPECT_EQ(result, -42); } @@ -210,21 +205,30 @@ TEST(PipelineMatch, AltVariableTemplateMatchesByIndex) { using V = std::variant; V v = 3.14; - int result = match(v) | on(alt<0> >> 1, alt<1> >> 2, alt<2> >> 3, _ >> 0); + int result = match(v) + | on(alt<0> >> 1, alt<1> >> 2, alt<2> >> 3, _ >> 0); EXPECT_EQ(result, 3); } TEST(PipelineMatch, ImplicitLitIntWithLambdaHandler) { int x = 2; - int result = match(x) | on(1 >> [] { return 10; }, 2 >> [] { return 20; }, _ >> 0); + int result = match(x) + | on( + 1 >> [] { return 10; }, + 2 >> [] { return 20; }, + _ >> 0); EXPECT_EQ(result, 20); } TEST(PipelineMatch, ImplicitLitFallsToWildcard) { int x = 99; - int result = match(x) | on(1 >> [] { return 10; }, 2 >> [] { return 20; }, _ >> -1); + int result = match(x) + | on( + 1 >> [] { return 10; }, + 2 >> [] { return 20; }, + _ >> -1); EXPECT_EQ(result, -1); } @@ -245,10 +249,10 @@ TEST(PipelineMatch, DollarHasBindsStructuralMembers) { Point p{3, 4}; int result = match(p) - | on($(has<&Point::x, &Point::y>) >> [](int x, int y) { - return x + y; - }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); EXPECT_EQ(result, 7); } @@ -258,15 +262,16 @@ TEST(PipelineMatch, DollarHasWithGuard) { int x; int y; }; + PTN_BIND(Point, x, y); Point p{3, 4}; int result = match(p) - | on($(has<&Point::x, &Point::y>)[arg<0> + arg<1> > 10] - >> [](int x, int y) { return x + y; }, - $(has<&Point::x, &Point::y>) >> [](int x, int y) { - return x * y; - }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>)[x + y > 10] >> + [](int x, int y) { return x + y; }, + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x * y; }, + _ >> 0); EXPECT_EQ(result, 12); } @@ -279,16 +284,16 @@ TEST(PipelineMatch, DollarHasMatchesStructuralBinding) { Point p{5, 6}; int r1 = match(p) - | on($(has<&Point::x, &Point::y>) >> [](int x, int y) { - return x + y; - }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); int r2 = match(p) - | on($(has<&Point::x, &Point::y>) >> [](int x, int y) { - return x + y; - }, - _ >> 0); + | on( + $(has<&Point::x, &Point::y>) >> + [](int x, int y) { return x + y; }, + _ >> 0); EXPECT_EQ(r1, r2); } @@ -297,7 +302,10 @@ TEST(PipelineMatch, DollarIsBindsVariantAlternative) { using V = std::variant; V v = 42; - int result = match(v) | on($(is()) >> [](int i) { return i * 2; }, _ >> 0); + int result = match(v) + | on( + $(is()) >> [](int i) { return i * 2; }, + _ >> 0); EXPECT_EQ(result, 84); } @@ -307,9 +315,10 @@ TEST(PipelineMatch, DollarIsWithGuard) { V v = 42; int result = match(v) - | on($(is())[_0 > 100] >> [](int i) { return i; }, - $(is()) >> [](int i) { return -i; }, - _ >> 0); + | on( + $(is())[_ > 100] >> [](int i) { return i; }, + $(is()) >> [](int i) { return -i; }, + _ >> 0); EXPECT_EQ(result, -42); } @@ -318,8 +327,10 @@ TEST(PipelineMatch, DollarIsMatchesTypedBinding) { using V = std::variant; V v = 42; - int r1 = match(v) | on($(is()) >> [](int i) { return i * 2; }, _ >> 0); - int r2 = match(v) | on($(is()) >> [](int i) { return i * 2; }, _ >> 0); + int r1 = match(v) + | on($(is()) >> [](int i) { return i * 2; }, _ >> 0); + int r2 = match(v) + | on($(is()) >> [](int i) { return i * 2; }, _ >> 0); EXPECT_EQ(r1, r2); } @@ -329,10 +340,12 @@ TEST(PipelineMatch, DollarIsWithStringVariant) { V v = std::string("hello"); auto result = match(v) - | on($(is()) >> [](const std::string &s) { - return s + " world"; - }, - _ >> std::string("other")); + | on( + $(is()) >> + [](const std::string &s) { + return s + " world"; + }, + _ >> std::string("other")); EXPECT_EQ(result, "hello world"); } diff --git a/tests/tests_pred_pattern.cpp b/tests/tests_pred_pattern.cpp index 5c79ee20..20b42efe 100644 --- a/tests/tests_pred_pattern.cpp +++ b/tests/tests_pred_pattern.cpp @@ -10,7 +10,7 @@ TEST(PredPattern, MatchesWhenPredicateReturnsTrue) { int x = 4; int result = match(x) | on(pred([](int v) { return v % 2 == 0; }) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } @@ -19,7 +19,7 @@ TEST(PredPattern, FallsBackWhenPredicateReturnsFalse) { int x = 3; int result = match(x) | on(pred([](int v) { return v % 2 == 0; }) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 0); } @@ -30,7 +30,7 @@ TEST(PredPattern, WorksWithStringSubject) { | on(pred([](const std::string &v) { return v.size() > 3; }) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } @@ -41,7 +41,7 @@ TEST(PredPattern, FallsBackWithShortString) { | on(pred([](const std::string &v) { return v.size() > 3; }) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 0); } @@ -52,7 +52,7 @@ TEST(PredPattern, ComposesWithAny) { | on(any(pred([](int v) { return v < 0; }), pred([](int v) { return v > 5; })) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } @@ -63,7 +63,7 @@ TEST(PredPattern, ComposesWithAll) { | on(all(pred([](int v) { return v > 0; }), pred([](int v) { return v % 2 == 0; })) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 1); } @@ -74,7 +74,7 @@ TEST(PredPattern, AllMissWhenOnePredicateFails) { | on(all(pred([](int v) { return v > 0; }), pred([](int v) { return v % 2 == 0; })) >> 1, - __ >> 0); + _ >> 0); EXPECT_EQ(result, 0); } @@ -86,16 +86,16 @@ TEST(PredPattern, ReusableAcrossMatches) { int zero = 0; int neg = -1; - EXPECT_EQ(match(pos) | on(is_positive >> 1, __ >> 0), 1); - EXPECT_EQ(match(zero) | on(is_positive >> 1, __ >> 0), 0); - EXPECT_EQ(match(neg) | on(is_positive >> 1, __ >> 0), 0); + EXPECT_EQ(match(pos) | on(is_positive >> 1, _ >> 0), 1); + EXPECT_EQ(match(zero) | on(is_positive >> 1, _ >> 0), 0); + EXPECT_EQ(match(neg) | on(is_positive >> 1, _ >> 0), 0); } TEST(PredPattern, WorksInsidePtnOnMacro) { auto run = [](int x) { return match(x) | PTN_ON(pred([](int v) { return v % 2 == 0; }) >> 1, - __ >> 0); + _ >> 0); }; EXPECT_EQ(run(2), 1); @@ -108,7 +108,7 @@ TEST(PredPattern, FullyQualifiedWithoutUsingDirective) { int result = ptn::match(x) | ptn::on( ptn::pred([](int v) { return v == 10; }) >> 42, - ptn::__ >> 0); + ptn::_ >> 0); EXPECT_EQ(result, 42); } diff --git a/tests/tests_public_api.cpp b/tests/tests_public_api.cpp index 675ee7b2..78fb75cf 100644 --- a/tests/tests_public_api.cpp +++ b/tests/tests_public_api.cpp @@ -6,23 +6,18 @@ TEST(PublicApi, FullyQualifiedPipelineOnWorksWithoutUsingDirective) { int x = 1; int result = ptn::match(x) - | ptn::on( - ptn::lit(1) >> 42, - ptn::__ >> 0 - ); + | ptn::on(ptn::lit(1) >> 42, ptn::_ >> 0); EXPECT_EQ(result, 42); } -TEST(PublicApi, FullyQualifiedPipelinePtnOnWorksWithoutUsingDirective) { +TEST(PublicApi, + FullyQualifiedPipelinePtnOnWorksWithoutUsingDirective) { int x = 2; int result = ptn::match(x) | PTN_ON( - ptn::val<1> >> 1, - ptn::val<2> >> 2, - ptn::__ >> 0 - ); + ptn::val<1> >> 1, ptn::val<2> >> 2, ptn::_ >> 0); EXPECT_EQ(result, 2); } diff --git a/tests/tests_terminal_semantics.cpp b/tests/tests_terminal_semantics.cpp index ce269649..dda90f18 100644 --- a/tests/tests_terminal_semantics.cpp +++ b/tests/tests_terminal_semantics.cpp @@ -7,7 +7,8 @@ using namespace ptn; TEST(TerminalSemantics, WildcardUsedWhenNoCaseMatches) { int x = 3; - int result = ptn::match(x) | ptn::on(ptn::lit(1) >> 10, ptn::__ >> 7); + int result = ptn::match(x) + | ptn::on(ptn::lit(1) >> 10, ptn::_ >> 7); EXPECT_EQ(result, 7); } @@ -17,11 +18,11 @@ TEST(TerminalSemantics, WildcardSkippedWhenCaseMatches) { int fallback_calls = 0; int result = ptn::match(x) - | ptn::on(ptn::lit(1) >> 5, - ptn::__ >> [&] { - ++fallback_calls; - return -1; - }); + | ptn::on( + ptn::lit(1) >> 5, ptn::_ >> [&] { + ++fallback_calls; + return -1; + }); EXPECT_EQ(result, 5); EXPECT_EQ(fallback_calls, 0); @@ -31,7 +32,8 @@ TEST(TerminalSemantics, WildcardReturnsFallbackCase) { int x = 2; const char *result = ptn::match(x) - | ptn::on(ptn::lit(1) >> "one", ptn::__ >> "other"); + | ptn::on(ptn::lit(1) >> "one", + ptn::_ >> "other"); EXPECT_STREQ(result, "other"); } @@ -39,9 +41,8 @@ TEST(TerminalSemantics, WildcardReturnsFallbackCase) { TEST(TerminalSemantics, FirstMatchingCaseWins) { int x = 9; - int result = - ptn::match(x) | ptn::on(ptn::$ >> 1, ptn::$ >> 2, ptn::__ >> 0); + int result = ptn::match(x) + | ptn::on(ptn::$ >> 1, ptn::$ >> 2, ptn::_ >> 0); EXPECT_EQ(result, 1); } - diff --git a/tests/tests_type_pattern.cpp b/tests/tests_type_pattern.cpp index b387748b..cbaa4cb1 100644 --- a/tests/tests_type_pattern.cpp +++ b/tests/tests_type_pattern.cpp @@ -11,12 +11,13 @@ TEST(TypePattern, TypeIsAndTypeAs) { std::variant v = std::string("patternia"); int result = ptn::match(v) - | ptn::on(ptn::is() >> [] { return -1; }, - ptn::$(ptn::is()) >> - [](const std::string &s) { - return static_cast(s.size()); - }, - ptn::__ >> 0); + | ptn::on( + ptn::is() >> [] { return -1; }, + ptn::$(ptn::is()) >> + [](const std::string &s) { + return static_cast(s.size()); + }, + ptn::_ >> 0); EXPECT_EQ(result, 9); } @@ -27,7 +28,7 @@ TEST(TypePattern, AltByIndex) { const char *result = ptn::match(v) | ptn::on(ptn::alt<0>() >> "int", ptn::alt<1>() >> "string", - ptn::__ >> "other"); + ptn::_ >> "other"); EXPECT_STREQ(result, "int"); } @@ -36,22 +37,23 @@ TEST(TypePattern, SimpleVariantDispatchPreservesFirstMatchWins) { std::variant v = 42; int hit_count = 0; - int result = ptn::match(v) - | ptn::on(ptn::is() >> - [&] { - ++hit_count; - return 1; - }, - ptn::is() >> - [&] { - ++hit_count; - return 2; - }, - ptn::__ >> - [&] { - ++hit_count; - return 3; - }); + int result = ptn::match(v) + | ptn::on( + ptn::is() >> + [&] { + ++hit_count; + return 1; + }, + ptn::is() >> + [&] { + ++hit_count; + return 2; + }, + ptn::_ >> + [&] { + ++hit_count; + return 3; + }); EXPECT_EQ(result, 1); EXPECT_EQ(hit_count, 1); @@ -60,7 +62,8 @@ TEST(TypePattern, SimpleVariantDispatchPreservesFirstMatchWins) { TEST(TypePattern, SimpleVariantDispatchFallsBackToWildcard) { std::variant v = std::string("patternia"); - int result = ptn::match(v) | ptn::on(ptn::is() >> 1, ptn::__ >> 99); + int result = ptn::match(v) + | ptn::on(ptn::is() >> 1, ptn::_ >> 99); EXPECT_EQ(result, 99); } @@ -73,21 +76,22 @@ TEST(TypePattern, SimpleVariantDispatchUnlistedAltFallsToWildcard) { int str_hits = 0; int result = ptn::match(v) - | ptn::on(ptn::is() >> - [&] { - ++int_hits; - return 1; - }, - ptn::is() >> - [&] { - ++str_hits; - return 2; - }, - ptn::__ >> - [&] { - ++wildcard_hits; - return 7; - }); + | ptn::on( + ptn::is() >> + [&] { + ++int_hits; + return 1; + }, + ptn::is() >> + [&] { + ++str_hits; + return 2; + }, + ptn::_ >> + [&] { + ++wildcard_hits; + return 7; + }); EXPECT_EQ(result, 7); EXPECT_EQ(wildcard_hits, 1); @@ -102,17 +106,18 @@ TEST(TypePattern, MixedVariantGuardedFallsThroughToSimpleCase) { int simple_hits = 0; int result = ptn::match(v) - | ptn::on(ptn::$(ptn::is())[ptn::_0 > 100] >> - [&](int) { - ++guarded_hits; - return 10; - }, - ptn::is() >> - [&] { - ++simple_hits; - return 1; - }, - ptn::__ >> [] { return 0; }); + | ptn::on( + ptn::$(ptn::is())[ptn::_ > 100] >> + [&](int) { + ++guarded_hits; + return 10; + }, + ptn::is() >> + [&] { + ++simple_hits; + return 1; + }, + ptn::_ >> [] { return 0; }); EXPECT_EQ(result, 1); EXPECT_EQ(guarded_hits, 0); @@ -129,18 +134,18 @@ TEST(TypePattern, MixedVariantGuardedCaseWinsWhenPredicateTrue) { int simple_hits = 0; int result = ptn::match(v) - | ptn::on(ptn::$(ptn::is())[long_string] - >> - [&](const std::string &) { - ++guarded_hits; - return 20; - }, - ptn::is() >> - [&] { - ++simple_hits; - return 2; - }, - ptn::__ >> [] { return 0; }); + | ptn::on( + ptn::$(ptn::is())[long_string] >> + [&](const std::string &) { + ++guarded_hits; + return 20; + }, + ptn::is() >> + [&] { + ++simple_hits; + return 2; + }, + ptn::_ >> [] { return 0; }); EXPECT_EQ(result, 20); EXPECT_EQ(guarded_hits, 1);