diff --git a/README.md b/README.md index 88015ae..ea1288a 100644 --- a/README.md +++ b/README.md @@ -26,62 +26,95 @@
-**Patternia** is a header-only pattern matching library for modern C++. -It keeps matching expression-oriented, explicit, and zero-overhead. - -> Compile-time literal matching uses `val`. -> Runtime literal matching remains `lit(value)` and `lit_ci(value)`. - -## Syntax +**Patternia** is a header-only pattern matching library for C++17 and later. +Match literals, `std::variant` alternatives, and struct members, bind the +values you need, and guard them, all in one expression: ```cpp #include +#include +#include +using namespace ptn; -int classify(int x) { - using namespace ptn; +using Value = std::variant; - return match(x) | on( - lit(0) >> 0, - lit(1) >> 1, - _ >> -1 +std::string describe(const Value &v) { + return match(v) | on( + is(lit(0)) >> "zero", + $(is)[_ < 0] >> [](int n) { return "negative " + std::to_string(n); }, + $(is) >> [](int n) { return "positive " + std::to_string(n); }, + $(is)[_ != ""] >> [](const std::string &s) { return "text: " + s; }, + _ >> "empty" ); } ``` -`match(subject)` creates the evaluation context. -`on(...)` provides the ordered case list. -`pattern >> handler` defines one case. -`_` is the required fallback case. +`$(...)` binds the matched value and passes it to the handler, `[...]` is a +guard that must also hold, and the first matching case wins. ## Highlights -- Literal, structural, and `std::variant` matching in one DSL. -- Explicit binding through `$` and `$(...)`. -- Declarative guards via `_`, structured-binding member names, `rng(...)`, and callables. -- No RTTI, no virtual dispatch, no heap allocation. -- Static literal and variant dispatch lowering for hot paths. - -## Quick Examples - -### Guarded value match +- **One DSL for many shapes**: runtime and compile-time literals, ASCII + case-insensitive strings, struct members, and `std::variant` + alternatives (by type or by index). +- **Expression-oriented**: `match(subject) | on(...)` returns a value; + cases are ordered and first-match-wins, with a required `_` fallback. +- **Explicit binding**: nothing binds implicitly. `$` binds the subject, + `$(pattern)` binds what a subpattern exposes, and handler parameters + follow binding order. +- **Declarative guards**: `$[_ > 0 && _ < 10]`, `rng(lo, hi)`, named + struct-member placeholders via C++17 structured bindings, or any + callable. +- **Pattern algebra**: `any`, `all`, `neg`, with operator sugar + `(a || b)`, `(a && b)`, and `!p`. +- **Compile-time checks**: a missing `_` fallback, unreachable cases, + out-of-range `has<...>` indices, and invalid member names are + diagnosed at compile time. +- **Zero-overhead goals**: no RTTI, no virtual dispatch, no heap + allocation; static dispatch lowering for compile-time literals and + variant cases, and `PTN_ON(...)` to cache case packs on hot paths. +- **No dependencies**: just headers. Packaged for vcpkg and exported as + a CMake target. + +## Quick Tour + +All examples assume `#include `, the standard headers +they use, and `using namespace ptn;`. + +### Literals and value guards ```cpp -using namespace ptn; - const char *bucket(int x) { return match(x) | on( - $[_ < 0] >> "negative", + lit(0) >> "zero", + $[_ < 0] >> "negative", // `_` is the bound value inside a guard $[_ < 10] >> "small", - _ >> "large" + _ >> "large" + ); +} + +int classify(int x) { + return match(x) | on( + lit(0) >> 0, // runtime literal + val<1> >> 1, // compile-time literal (eligible for static dispatch) + _ >> -1 // required fallback + ); +} + +int greet(const std::string &s) { + return match(s) | on( + lit_ci("hello") >> 1, // ASCII case-insensitive + _ >> 0 ); } ``` -### Structural match +### Structs -```cpp -using namespace ptn; +`has<&T::member...>` describes structure; wrap it in `$(...)` to pass the +listed members to the handler, in the order you list them. +```cpp struct Point { int x; int y; }; int magnitude2(const Point &p) { @@ -94,70 +127,152 @@ int magnitude2(const Point &p) { } ``` -### Structural match with named placeholders +### Named placeholders in guards -Name the members of a structural pattern with a C++17 structured binding, -then use those placeholders directly in guard expressions: +A `has<...>` pattern is tuple-like, so a C++17 structured binding gives its +members names you can use directly in guard expressions: ```cpp -using namespace ptn; - struct Point { int x; int y; }; + constexpr auto pt = has<&Point::x, &Point::y>; -auto [x, y] = pt; +auto [x, y] = pt; // placeholders, not values bool on_circle_radius5(const Point &p) { return match(p) | on( $(pt)[x * x + y * y == 25] >> true, - _ >> false + _ >> false ); } ``` -`PTN_BIND` is deprecated but still supported. Function-local `auto [x, y] = pt;` -works with `on(...)`, not with `PTN_ON`'s captureless lambda. For `PTN_ON`, -use namespace-scope names as above. In C++20, function-local declarations can -use `static constexpr auto pt = has<&Point::x, &Point::y>;` and -`static auto [x, y] = pt;`. In C++17, keep `PTN_BIND` for function-local names -(and give any local pattern used by `PTN_ON` static storage too). +### `std::variant` -### Variant match +Match an alternative by type with `is` or by index with `alt`; wrap it +in `$(...)` to pass the held value to the handler (see the example at the top). ```cpp -using namespace ptn; - using Value = std::variant; -std::string describe(const Value &v) { +const char *kind(const Value &v) { return match(v) | on( - is >> "int", - $(is) >> [](const std::string &s) { - return "str:" + s; + is >> "int", // by type + alt<1> >> "string", // by index + _ >> "other" + ); +} +``` + +### Combinators + +```cpp +const char *char_kind(char c) { + return match(c) | on( + any(lit('a'), lit('e'), lit('i'), lit('o'), lit('u')) >> "vowel", + (lit(' ') || lit('\n')) >> "whitespace", // same as any(...) + !lit('\0') >> "other", // same as neg(...) + _ >> "end" + ); +} +``` + +`>>` binds tighter than `||` and `&&`, so combined patterns need +parentheses; unary `!` does not. + +### Putting it together: event dispatch + +Variant dispatch, structural binding, named-field guards, and a combinator in +one match: + +```cpp +struct Click { int x, y; }; +struct Key { char code; bool ctrl; }; +struct Resize { int w, h; }; +struct Quit {}; +using Event = std::variant; + +constexpr auto click = has<&Click::x, &Click::y>; +constexpr auto key = has<&Key::code, &Key::ctrl>; +auto [x, y] = click; // name the fields once, +auto [code, ctrl] = key; // then use them in guards + +std::string handle(const Event &e) { + return match(e) | on( + is($(click))[x < 0 || y < 0] >> "click outside", + is($(click)) >> [](int cx, int cy) { + return "click at " + std::to_string(cx) + "," + std::to_string(cy); }, - _ >> [] { return std::string("other"); } + is($(key))[ctrl == true && code == 'q'] >> "quit", + (is || is) >> "window event", + _ >> "ignored" ); } ``` -### Negation match +### Cached case packs for hot paths + +`PTN_ON(...)` wraps `static_on(...)` so the matcher is built once instead +of on every call: ```cpp -// Negation: match values NOT equal to specific literals -int status = 404; -auto msg = match(status) | on( - !val<200> >> []{ return std::string("error"); }, - _ >> []{ return std::string("ok"); } -); -// msg == "error" — status isn't 200 -// `!p` is sugar for neg(p); likewise (a || b) for any and -// (a && b) for all. +int fast_classify(int x) { + return match(x) | PTN_ON( + val<1> >> 1, + val<2> >> 2, + _ >> 0 + ); +} ``` +`PTN_ON` uses a captureless lambda, so function-local structured-binding +names are not visible inside it. Declare the pattern and its names at +namespace scope (as above), use `static` declarations in C++20, or keep the +deprecated `PTN_BIND` in C++17. See +[Scope and cached matchers](https://patternia.tech/api/#scope-and-cached-matchers). + +## Core Concepts + +| Construct | Meaning | +|---|---| +| `match(subject)` | Starts a match on an lvalue subject. | +| `on(cases...)` | Ordered case list; the last case must be the `_` fallback. | +| `pattern >> handler` | One case. The handler is a value or a callable. | +| `_` | Wildcard / fallback, and the single-value guard placeholder. | +| `lit(v)`, `val`, `lit_ci(s)` | Runtime, compile-time, and ASCII case-insensitive literals. | +| `$`, `$(p)` | Bind the whole subject, or the values exposed by subpattern `p`. | +| `p[guard]` | Run `guard` after `p` matches and binds; failure falls through to the next case. | +| `rng(lo, hi[, mode])` | Range guard helper. | +| `has<&T::m...>` | Structural pattern over the listed data members. | +| `is`, `alt` | `std::variant` alternative by type or by index. | +| `any`, `all`, `neg` | Pattern combinators (`\|\|`, `&&`, `!`). | +| `static_on`, `PTN_ON` | Cached case packs. | + +The full reference is in the [API documentation](https://patternia.tech/api/). + +## Requirements + +- C++17 or later (the CMake target requires `cxx_std_17`). Floating-point + `val` literals need C++20. +- A compiler at or above the minimums enforced by the CMake configure check + (`cmake/CheckCompilerVersion.cmake`): + + | Compiler | Minimum | + |---|---| + | GCC | 11 | + | Clang | 12 | + | AppleClang | 14 | + | MSVC | 19.30 (Visual Studio 2022) | + | clang-cl | 18, with an MSVC 19.30+ toolset | + +CI builds and tests GCC 11/13 and Clang 14/16/18 on Linux (C++17 through +C++26), and MSVC and clang-cl on Windows (C++20 through C++26). + ## Installation -Patternia is header-only with no external dependencies. +Patternia is header-only and has no external dependencies. Every method +below provides the `patternia::patternia` CMake target. -**vcpkg** (recommended): +### vcpkg ```bash vcpkg install patternia @@ -168,7 +283,7 @@ find_package(patternia CONFIG REQUIRED) target_link_libraries(your_target PRIVATE patternia::patternia) ``` -**FetchContent**: +### CMake FetchContent ```cmake include(FetchContent) @@ -181,18 +296,30 @@ FetchContent_MakeAvailable(patternia) target_link_libraries(your_target PRIVATE patternia::patternia) ``` -**Direct clone**: +### Install and `find_package` ```bash -git clone https://github.com/SentoMK/patternia.git -cd patternia -cmake -S . -B build -cmake --build build +git clone https://github.com/sentomk/patternia.git +cmake -S patternia -B patternia/build -DPTN_BUILD_TESTS=OFF \ + -DPTN_BUILD_BENCHMARKS=OFF -DPTN_BUILD_SAMPLES=OFF +cmake --install patternia/build --prefix /path/to/install ``` -See [Installation Guide](https://patternia.tech/guide/installation/) for `find_package`, submodule, and header-copy options. +```cmake +find_package(patternia CONFIG REQUIRED) +target_link_libraries(your_target PRIVATE patternia::patternia) +``` + +### Without a package manager + +Add the repository as a subdirectory (for example a Git submodule) with +`add_subdirectory(extern/patternia)`, or simply put `include/` on your +include path and `#include `. -## Tests +See the [Installation Guide](https://patternia.tech/guide/installation/) +for CPM.cmake, vcpkg manifest mode, and more detail. + +## Building the Tests ```bash cmake -S . -B build -DPTN_BUILD_TESTS=ON @@ -200,7 +327,13 @@ cmake --build build --target ptn_tests ctest --test-dir build --output-on-failure ``` -## Benchmarks +Other CMake options: `PTN_BUILD_SAMPLES`, `PTN_BUILD_BENCHMARKS`, +`PTN_BUILD_FUZZ` (Clang only), `PTN_INSTALL`, `PTN_DEV_INDEX` (local IDE +indexing target for top-level builds, not installed; on by default), and +`PTN_SKIP_COMPILER_CHECK`. Tests, samples, and benchmarks are on by +default only when Patternia is the top-level project. + +## Performance

Patternia benchmark comparison @@ -208,36 +341,32 @@ ctest --test-dir build --output-on-failure

Patternia gap map across key scenarios. Each row is normalized to the fastest implementation in that scenario.

-See [Performance Notes](https://patternia.tech/performance/) for full reports and methodology. - -## Performance-Oriented Usage - -Cache the case pack for repeated hot paths: - -```cpp -using namespace ptn; - -int fast_classify(int x) { - return match(x) | PTN_ON( - val<1> >> 1, - val<2> >> 2, - _ >> 0 - ); -} -``` - -`PTN_ON(...)` is a convenience wrapper over `static_on(...)`. -It avoids rebuilding the matcher object on every call. +See the [Performance Notes](https://patternia.tech/performance/) for full +reports, methodology, and per-version results, and the +[Performance Tuning guide](https://patternia.tech/guide/performance-tuning/) +for hot-path advice. ## Documentation - [Getting Started](https://patternia.tech/guide/getting-started/) +- [Installation](https://patternia.tech/guide/installation/) - [API Reference](https://patternia.tech/api/) - [Design Overview](https://patternia.tech/design-overview/) - [Tutorials](https://patternia.tech/tutorials/from-control-flow/) - [Performance Notes](https://patternia.tech/performance/) +- [Release Notes](https://patternia.tech/changelog/releases/) +- [Samples](samples/) ## Contributing -Please read [CONTRIBUTING.md](CONTRIBUTING.md) before sending changes. -This project is governed by [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). +Bug reports, feature proposals, and pull requests are welcome. Please read +[CONTRIBUTING.md](CONTRIBUTING.md) first: `main` is protected, pull +requests carry a single Conventional Commits commit, and formatting is +checked with `clang-format`. Coding agents should also read +[AGENTS.md](AGENTS.md). This project is governed by the +[Code of Conduct](CODE_OF_CONDUCT.md); report security issues as described +in [SECURITY.md](SECURITY.md). + +## License + +Patternia is released under the [MIT License](LICENSE). diff --git a/docs/index.md b/docs/index.md index 5756331..311ab3f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,9 +34,19 @@ Patternia is a modern C++ pattern-matching DSL designed to make dispatch logic c ## System Requirements -- **C++ Standard**: C++17 or later -- **Compilers**: GCC >= 11, Clang >= 12, MSVC >= 2019 -- **Platforms**: Linux, Windows, macOS +- **C++ Standard**: C++17 or later (floating-point `val` literals need C++20) +- **Compilers** (minimums enforced by `cmake/CheckCompilerVersion.cmake`): + +| Compiler | Minimum | +|---|---| +| GCC | 11 | +| Clang | 12 | +| AppleClang | 14 | +| MSVC | 19.30 (Visual Studio 2022) | +| clang-cl | 18, with an MSVC 19.30+ toolset | + +- **Tested in CI**: GCC 11/13 and Clang 14/16/18 on Linux (C++17 through + C++26); MSVC and clang-cl on Windows (C++20 through C++26). ---