diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b83cff2..095a9357 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: patternia CI on: push: - branches: [main, dev] + branches: [main, dev-main] paths-ignore: - "README.md" - "CONTRIBUTING.md" @@ -10,7 +10,7 @@ on: - "LICENSE" - ".github/ISSUE_TEMPLATE/**" pull_request: - branches: [main, dev] + branches: [main, dev-main] paths-ignore: - "README.md" - "CONTRIBUTING.md" diff --git a/CMakeLists.txt b/CMakeLists.txt index f01a2c47..5891db1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,11 +78,13 @@ PTN_JOIN_HEADERS(PTN_HEADERS include/ptn/config.hpp include/ptn/patternia.hpp include/ptn/core/match_builder.hpp - include/ptn/dsl/case_expr.hpp + include/ptn/core/dsl/case_expr.hpp include/ptn/patterns/relational.hpp include/ptn/patterns/value.hpp include/ptn/patterns/predicate.hpp - include/ptn/patterns/pattern_tag.hpp + include/ptn/patterns/type.hpp + include/ptn/patterns/pattern_base.hpp + include/ptn/detail/pattern_tag.hpp ) # ========== Public interface target ========== diff --git a/include/ptn/dsl/case_expr.hpp b/include/ptn/core/dsl/case_expr.hpp similarity index 100% rename from include/ptn/dsl/case_expr.hpp rename to include/ptn/core/dsl/case_expr.hpp diff --git a/include/ptn/core/match_builder.hpp b/include/ptn/core/match_builder.hpp index 898572f5..a2ad92ca 100644 --- a/include/ptn/core/match_builder.hpp +++ b/include/ptn/core/match_builder.hpp @@ -11,47 +11,77 @@ #include #endif -#include "ptn/dsl/case_expr.hpp" -#if PTN_ENABLE_VALUE_PATTERN -#include "ptn/patterns/value.hpp" -#endif +#include "ptn/core/dsl/case_expr.hpp" + +/** + * @file match_builder.hpp + * @brief Core matching engine that evaluates pattern-handler pairs. + * + * This header implements Patternia’s *Core Layer*. + * `match_builder` owns: + * - a subject value + * - a compile-time tuple of (Pattern, Handler) pairs + * + * The builder supports: + * - `.when(Pattern >> Handler)` to append a new case + * - `.otherwise(handler)` to perform final evaluation + * + * All patterns in Patternia must implement: + * - `bool match(subject) const` + * - `bind(subject)` (default provided by pattern_base) + * + * @ingroup core + */ namespace ptn { - /* free match function forward declaration */ + + /** + * @brief Forward declaration of free-function `match()`. + */ template constexpr auto match(T &&) noexcept(std::is_nothrow_constructible_v, T &&>); } // namespace ptn namespace ptn::detail { + #if PTN_USE_CONCEPTS + template concept Invocable1 = std::is_invocable_v; + template concept Invocable0 = std::is_invocable_v; - // handler callable with (x) + /** + * @brief Invoke handler `h(x)` if available. + */ template requires Invocable1 constexpr decltype(auto) run_handler(H &h, X &x) { return std::invoke(h, x); } - // not callable with (x) but callable with () + /** + * @brief Invoke handler `h()` if `(x)` overload is not available. + */ template requires(!Invocable1 && Invocable0) - constexpr decltype(auto) run_handler(H &h, X & /*x*/) { + constexpr decltype(auto) run_handler(H &h, X &) { return std::invoke(h); } - // neither callable -> treat handler as plain value and return it + /** + * @brief Fallback: return handler as value. + */ template requires(!Invocable1 && !Invocable0) - constexpr decltype(auto) run_handler(H &h, X & /*x*/) { + constexpr decltype(auto) run_handler(H &h, X &) { return (h); } -#else - // C++17 fallback: use SFINAE overloads + +#else // C++17 fallback + template < typename H, typename X, @@ -66,7 +96,7 @@ namespace ptn::detail { typename = void, typename = std::enable_if_t< !std::is_invocable_v && std::is_invocable_v>> - constexpr decltype(auto) run_handler(H &h, X & /*x*/) { + constexpr decltype(auto) run_handler(H &h, X &) { return std::invoke(h); } @@ -77,28 +107,56 @@ namespace ptn::detail { typename = void, typename = std::enable_if_t< !std::is_invocable_v && !std::is_invocable_v>> - constexpr decltype(auto) run_handler(H &h, X & /*x*/) { + constexpr decltype(auto) run_handler(H &h, X &) { return (h); } + #endif } // namespace ptn::detail namespace ptn::core { + + /** + * @brief Tag used to disambiguate private constructors. + */ struct ctor_tag {}; + /** + * @class match_builder + * @brief Core engine storing subject and case list, evaluated via + * `.otherwise()`. + * + * @tparam TV Type of stored subject. + * @tparam Cases Variadic pack of `(Pattern, Handler)` pairs. + * + * ### Example + * @code + * match(x) + * .when(value(10) >> "ten") + * .when(pred(is_even) >> "even") + * .otherwise("other"); + * @endcode + * + * This class is immutable: each `.when()` returns a new type with the added + * case. + * + * @ingroup core + */ template class match_builder { - TV value_; - std::tuple cases_; + TV value_; ///< Subject value + std::tuple cases_; ///< Stored (Pattern, Handler) pairs using ctor_tag_t = ptn::core::ctor_tag; template friend struct ptn::dsl::case_expr; - /* make all specializations of match_builder mutual friends */ template friend class match_builder; + /** + * @brief Private constructor used by `create()`. + */ template #if PTN_USE_CONCEPTS requires std::constructible_from && @@ -108,36 +166,50 @@ namespace ptn::core { : value_(std::forward(v)), cases_(std::forward(cs)) { } - // with (lvalue) + // Append a case (lvalue) template constexpr auto with(Pattern p, Handler h) & { using pair_t = std::pair; auto new_cases = std::tuple_cat( cases_, std::make_tuple(pair_t{std::move(p), std::move(h)})); - // use brace-init to construct the returned match_builder + return match_builder( value_, std::move(new_cases), ctor_tag_t{}); } - // with (rvalue) + // Append a case (rvalue) template constexpr auto with(Pattern p, Handler h) && { using pair_t = std::pair; auto new_cases = std::tuple_cat( std::move(cases_), std::make_tuple(pair_t{std::move(p), std::move(h)})); + return match_builder( std::move(value_), std::move(new_cases), ctor_tag_t{}); } - // try_cases + // Recursive case matching + /** + * @brief Try each case in order. + * + * Stops when: + * - a pattern matches (via `pattern.match(value_)`), or + * - all cases exhausted. + * + * @tparam I Current tuple index. + * @tparam OutT Output type inferred from handlers. + */ template constexpr void try_cases(OutT &out, bool &done) { if constexpr (I < sizeof...(Cases)) { auto &c = std::get(cases_); auto &[p, handler] = c; - if (!done && p(value_)) { + // *** IMPORTANT *** + // Unified Pattern Layer API: + // Pattern must implement match(value) + if (!done && p.match(value_)) { out = static_cast(ptn::detail::run_handler(handler, value_)); done = true; } @@ -148,7 +220,7 @@ namespace ptn::core { } public: - // Correctly place template & requires for create + // Builder creation (called from ptn::match()) template #if PTN_USE_CONCEPTS requires std::constructible_from, Tuple> @@ -160,60 +232,37 @@ namespace ptn::core { std::forward(v), std::forward(cs), ctor_tag{}); } - // otherwise + // otherwise() + /** + * @brief Finalize matching. + * + * Computes a common return type from all handlers in all cases, + * applies the first matching handler, or the fallback if none match. + */ template - constexpr auto otherwise(H &&h) && { + constexpr auto otherwise(H &&fallback) && { using R = std::common_type_t< decltype(ptn::detail::run_handler( std::declval(), std::declval()))..., decltype(ptn::detail::run_handler( std::declval &>(), std::declval()))>; + R out{}; bool done = false; try_cases(out, done); if (!done) - out = static_cast(ptn::detail::run_handler(h, value_)); + out = static_cast(ptn::detail::run_handler(fallback, value_)); return out; } -#if PTN_ENABLE_VALUE_PATTERN - // with_value - template - constexpr auto with_value(V &&v, H &&h) & { - using ptn::patterns::value; - return with(value(std::forward(v)), std::forward(h)); - } - - template - constexpr auto with_value(V &&v, H &&h) && { - using ptn::patterns::value; - return std::move(*this).with( - value(std::forward(v)), std::forward(h)); - } - - // with_value_cmp - template - constexpr auto with_value_cmp(V &&v, Cmp &&cmp, H &&h) & { - using store_t = ptn::patterns::value_store_t; - auto p = ptn::patterns::value_pattern>{ - store_t(std::forward(v)), std::forward(cmp)}; - return with(std::move(p), std::forward(h)); - } - - template - constexpr auto with_value_cmp(V &&v, Cmp &&cmp, H &&h) && { - using store_t = ptn::patterns::value_store_t; - auto p = ptn::patterns::value_pattern>{ - store_t(std::forward(v)), std::forward(cmp)}; - return std::move(*this).with(std::move(p), std::forward(h)); - } -#endif - - // when + // when() + /** + * @brief Add a pattern-handler case from a `case_expr`. + */ template constexpr auto when(dsl::case_expr &&e) & { return this->with(std::move(e.pattern), std::move(e.handler)); @@ -224,4 +273,5 @@ namespace ptn::core { return std::move(*this).with(std::move(e.pattern), std::move(e.handler)); } }; + } // namespace ptn::core diff --git a/include/ptn/detail/pattern_tag.hpp b/include/ptn/detail/pattern_tag.hpp new file mode 100644 index 00000000..b1315a16 --- /dev/null +++ b/include/ptn/detail/pattern_tag.hpp @@ -0,0 +1,107 @@ +#pragma once +#include +#include +#include "ptn/config.hpp" + +/** + * @file pattern_tag.hpp + * @brief Marker base type and detection utilities for Patternia patterns. + * + * This header defines the @ref ptn::patterns::pattern_tag base struct and the + * compile-time facilities for identifying "pattern-like" types. + * + * A type is considered *pattern-like* if: + * - It derives from `pattern_tag`, **or** + * - It provides a valid `match(subject)` member returning something + * convertible to `bool`. + * + * This enables Patternia's DSL (`match().when(Pattern >> Handler)`) to accept + * both built-in and user-defined patterns with consistent compile-time rules. + * + * @ingroup patterns + */ + +namespace ptn::detail { + /** + * @brief C++17 fallback: checks whether `P::match(x)` is valid. + * + * This trait detects whether `const P&` can be invoked with + * `p.match(subj)` and the result is convertible to `bool`. + * + * It is used when `PTN_USE_CONCEPTS` is disabled. + * + * @tparam P Type being inspected. + */ + template + struct has_match_method : std::false_type {}; + + template + struct has_match_method< + P, + std::void_t(std::declval().match( + std::declval())))>> : std::true_type {}; + + /** + * @brief Marker base type for all built-in Patternia patterns. + * + * Any pattern in Patternia should inherit from `pattern_tag` to signal + * that it participates in the pattern-matching DSL. This enables + * uniform compile-time detection and improves diagnostic quality. + * + * Example: + * @code{.cpp} + * struct value_pattern : pattern_tag { + * bool match(int x) const noexcept { return x == 42; } + * }; + * @endcode + */ + struct pattern_tag { + static constexpr bool is_pattern = true; + }; + +// Concepts-based detection (C++20) +#if defined(PTN_USE_CONCEPTS) && PTN_USE_CONCEPTS + + /** + * @brief C++20 Concept: determines whether a type behaves like a Pattern. + * + * A type P is considered pattern-like if: + * - it derives from `pattern_tag`, OR + * - it defines a valid `match(x)` operation returning something + * convertible to `bool`. + * + * This makes Patternia extensible: user-defined types with + * `match(subject)` automatically qualify as patterns without needing + * inheritance from `pattern_tag`. + * + * @tparam P The type being tested. + */ + template + concept pattern_like = + std::derived_from || requires(const P &p, auto &&subj) { + { p.match(subj) } -> std::convertible_to; + }; + +#else // C++17 fallback + + /** + * @brief C++17-compatible trait checking whether a type behaves like a + * Pattern. + * + * A type `P` is pattern-like if: + * - `P` derives from pattern_tag, OR + * - `P` has a member `match(subj)` returning something `bool`-convertible. + * + * Patternia uses this trait to constrain `.when(...)` overloads on C++17 + * mode. + * + * @tparam P The type being tested. + */ + template + struct is_pattern_like : std::integral_constant< + bool, + std::is_base_of_v || + ptn::detail::has_match_method

::value> {}; +#endif + +} // namespace ptn::detail diff --git a/include/ptn/patternia.hpp b/include/ptn/patternia.hpp index c3454022..77ae9b85 100644 --- a/include/ptn/patternia.hpp +++ b/include/ptn/patternia.hpp @@ -1,11 +1,17 @@ #pragma once +#include +#include +#include +#include "ptn/core/match_builder.hpp" // core API +#include "ptn/config.hpp" + /** * @defgroup main Main API * @brief Core public interface of Patternia. * * @file patternia.hpp - * @brief Public entry header for `patternia` + * @brief Public entry header for Patternia * @details * This header serves as the unified interface for end users. * It includes the core DSL (`match_builder`) and all enabled pattern modules. @@ -26,12 +32,6 @@ * */ -#include -#include -#include -#include "ptn/core/match_builder.hpp" // core API -#include "ptn/config.hpp" - #if PTN_ENABLE_VALUE_PATTERN // clang-format off # include @@ -52,7 +52,7 @@ /** * @namespace ptn - * @brief Root namespace for `patternia`. + * @brief Root namespace for Patternia. * @details * Contains all top-level APIs: * - `match()` — entry point to create a matching builder. diff --git a/include/ptn/patterns/pattern_base.hpp b/include/ptn/patterns/pattern_base.hpp new file mode 100644 index 00000000..714bac5e --- /dev/null +++ b/include/ptn/patterns/pattern_base.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "ptn/detail/pattern_tag.hpp" // marker +#include "ptn/config.hpp" +#include + +/** + * @file pattern_base.hpp + * @brief CRTP base class providing unified pattern interface for Patternia. + * + * All built-in patterns should derive from `pattern_base`. + * This base class delivers: + * - the “pattern identity” (via pattern_tag) + * - default forwarding for `match(subject)` + * - default forwarding for `bind(subject)` + * - default no-op bind for predicate-like patterns + * + * @ingroup patterns + */ + +namespace ptn::patterns { + + /* forward declare */ + template + struct pattern_base; + + /// Helper to obtain Derived& + template + constexpr Derived &derived_of(pattern_base &self) noexcept { + return static_cast(self); + } + /// Helper to obtain const Derived& + template + constexpr const Derived & + derived_of(const pattern_base &self) noexcept { + return static_cast(self); + } + + /** + * @brief CRTP base class for all Patternia patterns. + * + * Derived must implement: + * - `bool match(const Subject&) const` + * - optionally: `auto bind(const Subject&) const` + * + * If bind() is not implemented, pattern_base returns subject unchanged. + */ + template + struct pattern_base : ptn::detail::pattern_tag { + + /** + * @brief default match() forwarding to Derived::match() + */ + template + constexpr bool match(Subject &&subj) const noexcept( + noexcept(derived_of(*this).match(std::forward(subj)))) { + return derived_of(*this).match(std::forward(subj)); + } + + /** + * @brief Default bind(): return the subject unchanged. + * + * Predicate patterns or patterns that do not transform the value may + * simply rely on this implementation. + * + * Value-pattern, type-pattern etc should override bind(). + */ + template + constexpr bool bind(Subject &&subj) const noexcept { + return std::forward(subj); + } + }; + +} // namespace ptn::patterns \ No newline at end of file diff --git a/include/ptn/patterns/pattern_tag.hpp b/include/ptn/patterns/pattern_tag.hpp deleted file mode 100644 index bd13cc63..00000000 --- a/include/ptn/patterns/pattern_tag.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include - -namespace ptn::patterns { - - // pattern_tag - struct pattern_tag { - static constexpr bool is_pattern = true; - }; - -#if defined(__cpp_concepts) && __cpp_concepts >= 201907L - template - concept pattern_like = std::derived_from; -#else - template - struct is_pattern_like : std::is_base_of {}; -#endif - -} // namespace ptn::patterns diff --git a/include/ptn/patterns/predicate.hpp b/include/ptn/patterns/predicate.hpp index c2fc5342..6a0297b7 100644 --- a/include/ptn/patterns/predicate.hpp +++ b/include/ptn/patterns/predicate.hpp @@ -2,14 +2,44 @@ #include #include -#include "ptn/patterns/pattern_tag.hpp" + +#include "ptn/patterns/pattern_base.hpp" #include "ptn/config.hpp" +/** + * @file predicate.hpp + * @brief Predicate-based patterns and logical composition (`&&`, `||`, `!`). + * + * This module belongs to the *Pattern Layer* and provides: + * - `predicate_pattern`: wraps any boolean callable + * - `and_pattern`, `or_pattern`, `not_pattern`: Boolean combinators + * - DSL composition operators under `patterns::ops` + * + * All predicate-style patterns are *Filter Patterns*: they override `match()` + * but leave `bind()` to the default identity implementation in pattern_base. + * + * @ingroup patterns + */ + namespace ptn::patterns { - /* predicate_pattern: wraps any callable returning bool */ + // predicate_pattern: wraps any callable returning bool + + /** + * @brief A pattern wrapping any callable returning something convertible to + * bool. + * + * Example: + * @code {.cpp} + * match(x) + * .when(pred([](int v){ return v % 2 == 0; }) >> "even"); + * @endcode + * + * @tparam F A callable type that must support the expression `fn(x)`. + */ template - struct predicate_pattern : pattern_tag { + struct predicate_pattern : pattern_base> { + #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] F fn; #else @@ -19,24 +49,39 @@ namespace ptn::patterns { constexpr explicit predicate_pattern(F f) : fn(std::move(f)) { } + /** + * @brief Match if `fn(x)` evaluates to true. + */ + template + constexpr bool match(X const &x) const + noexcept(noexcept(std::declval(), x)) { + return fn(x); + } template constexpr bool operator()(X const &x) const - noexcept(noexcept(std::invoke(std::declval(), x))) { - return std::invoke(fn, x); + noexcept(noexcept(this->match(x))) { + return this->match(x); } }; - // Factory + /** + * @brief Factory for predicate_pattern. + */ template constexpr auto pred(F &&f) { return predicate_pattern>(std::forward(f)); } - /* logical composition */ + // logical and - // and + /** + * @brief Logical AND composition of two patterns. + * + * `match(x)` succeeds if both `l.match(x)` and `r.match(x)` succeed. + */ template - struct and_pattern : pattern_tag { + struct and_pattern : pattern_base> { + #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] L l; [[no_unique_address]] R r; @@ -48,15 +93,27 @@ namespace ptn::patterns { constexpr and_pattern(L lhs, R rhs) : l(std::move(lhs)), r(std::move(rhs)) { } template + constexpr bool match(X const &x) const + noexcept(noexcept(l.match(x)) && noexcept(r.match(x))) { + return l.match(x) && r.match(x); + } + template constexpr bool operator()(X const &x) const - noexcept(noexcept(l(x) && noexcept(r(x)))) { - return l(x) && r(x); + noexcept(noexcept(this->match(x))) { + return this->match(x); } }; - // or + // logical or + + /** + * @brief Logical OR composition of two patterns. + * + * `match(x)` succeeds iff either `l.match(x)` or `r.match(x)` succeeds. + */ template - struct or_pattern : pattern_tag { + struct or_pattern : pattern_base> { + #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] L l; [[no_unique_address]] R r; @@ -68,15 +125,21 @@ namespace ptn::patterns { constexpr or_pattern(L lhs, R rhs) : l(std::move(lhs)), r(std::move(rhs)) { } template + constexpr bool match(X const &x) const + noexcept(noexcept(l.match(x)) && noexcept(r.match(x))) { + return l.match(x) || r.match(x); + } + template constexpr bool operator()(X const &x) const - noexcept(noexcept(l(x) && noexcept(r(x)))) { - return l(x) || r(x); + noexcept(noexcept(this->match(x))) { + return this->match(x); } }; // not template - struct not_pattern : pattern_tag { + struct not_pattern : pattern_base> { + #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] P p; #else @@ -86,39 +149,56 @@ namespace ptn::patterns { constexpr explicit not_pattern(P inner) : p(std::move(inner)) { } template - constexpr bool operator()(X const &x) const noexcept(noexcept(!p(x))) { - return !p(x); + constexpr bool match(X const &x) const noexcept(noexcept(!p.match(x))) { + return !p.match(x); + } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); } }; - /* ops namespace */ + // DSL operators + namespace ops { -#if defined(__cpp_concepts) && __cpp_concepts >= 201907L +#if defined(PTN_USE_CONCEPTS) && PTN_USE_CONCEPTS + + /** + * @brief Pattern logical AND operator. + */ template constexpr auto operator&&(L &&l, R &&r) { return and_pattern, std::decay_t>( std::forward(l), std::forward(r)); } + /** + * @brief Pattern logical OR operator. + */ template constexpr auto operator||(L &&l, R &&r) { return or_pattern, std::decay_t>( std::forward(l), std::forward(r)); } + /** + * @brief Pattern logical NOT operator. + */ template constexpr auto operator!(P &&p) { return not_pattern>(std::forward

(p)); } -#else +#else // C++17 fallback + template < typename L, typename R, typename = std::enable_if_t< - std::is_base_of_v> && - std::is_base_of_v>>> + ptn::detail::is_pattern_like>::value && + ptn::detail::is_pattern_like>::value>> constexpr auto operator&&(L &&l, R &&r) { return and_pattern, std::decay_t>( std::forward(l), std::forward(r)); @@ -128,8 +208,8 @@ namespace ptn::patterns { typename L, typename R, typename = std::enable_if_t< - std::is_base_of_v> && - std::is_base_of_v>>> + ptn::detail::is_pattern_like>::value && + ptn::detail::is_pattern_like>::value>> constexpr auto operator||(L &&l, R &&r) { return or_pattern, std::decay_t>( std::forward(l), std::forward(r)); @@ -137,11 +217,12 @@ namespace ptn::patterns { template < typename P, - typename = - std::enable_if_t>>> + typename = std::enable_if_t< + ptn::detail::is_pattern_like>::value>> constexpr auto operator!(P &&p) { return not_pattern>(std::forward

(p)); } + #endif } // namespace ops diff --git a/include/ptn/patterns/relational.hpp b/include/ptn/patterns/relational.hpp index 083c5bec..a9b84ec7 100644 --- a/include/ptn/patterns/relational.hpp +++ b/include/ptn/patterns/relational.hpp @@ -3,16 +3,54 @@ #include // std::less<> #include // std::decay_t #include // std::forward -#include "ptn/patterns/pattern_tag.hpp" + +#include "ptn/patterns/pattern_base.hpp" +#include "ptn/config.hpp" + +/** + * @file relational.hpp + * @brief Relational comparison patterns: < <= > >= == != and between. + * + * All relational patterns compare a subject value against a stored value using + * a comparator (default: `std::less<>` or `std::equal_to<>`). + * + * These patterns inherit from pattern_base and therefore supply: + * - `match(subject)` for matching + * - default identity `bind(subject)` unless overridden + * + * Example: + * @code {.cpp} + * using namespace ptn::patterns; + * + * match(x) + * .when(lt(10) >> "small") + * .when(between(10, 20) >> "medium") + * .when(ge(20) >> "large"); + * @endcode + * + * @ingroup patterns + */ namespace ptn::patterns { - /* enhance this alias later to support string_view */ + + /** + * @brief Internal storage type for relational values. + * + * Currently uses std::decay_t, but may be extended for string_view + * specialization in future releases. + */ template using rel_store_t = std::decay_t; - // x < v + // less than + /** + * @brief Pattern matching `x < v`. + * + * @tparam V value type + * @tparam Cmp comparator type (default: `std::less<>`) + */ template > - struct lt_pattern : pattern_tag { + struct lt_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -24,15 +62,20 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(x, v))) { return cmp(x, v); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; // x <= v <=> !(v < x) template > - struct le_pattern : pattern_tag { + struct le_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -44,15 +87,20 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(v, x))) { return !cmp(v, x); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; // x > v <=> (v < x) template > - struct gt_pattern : pattern_tag { + struct gt_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -64,15 +112,20 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(v, x))) { return cmp(v, x); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; // x >= v <=> !(x < v) template > - struct ge_pattern : pattern_tag { + struct ge_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -83,15 +136,20 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(x, v))) { return !cmp(x, v); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; // x == v template > - struct eq_pattern : pattern_tag { + struct eq_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -103,15 +161,20 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(x, v))) { return cmp(x, v); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; // x != v template > - struct ne_pattern : pattern_tag { + struct ne_pattern : pattern_base> { rel_store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L [[no_unique_address]] Cmp cmp{}; @@ -123,13 +186,18 @@ namespace ptn::patterns { : v(std::move(val)), cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const + constexpr bool match(X const &x) const noexcept(noexcept(std::declval()(x, v))) { return cmp(x, v); } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; - /* Factories */ + // Factories template constexpr auto lt(V &&v) { return lt_pattern>(rel_store_t(std::forward(v))); @@ -160,13 +228,27 @@ namespace ptn::patterns { return ne_pattern>(rel_store_t(std::forward(v))); } - /* - between: - closed==true -> [lo, hi] : !(x < lo) && !(hi < x) - closed==false -> (lo, hi) : (lo < x) && (x < hi) - */ + // + // between: + // closed==true -> [lo, hi] : !(x < lo) && !(hi < x) + // closed==false -> (lo, hi) : (lo < x) && (x < hi) + // + + /** + * @brief Pattern checking that a subject value falls within an interval. + * + * @tparam L lower-bound type + * @tparam R upper-bound type + * @tparam Cmp comparator (`std::less<>` by default) + * + * Closed interval: + * `[lo, hi] → !(x < lo) && !(hi < x)` + * + * Open interval: + * `(lo, hi) → (lo < x) && (x < hi)` + */ template > - struct between_pattern : pattern_tag { + struct between_pattern : pattern_base> { rel_store_t lo; rel_store_t hi; bool closed{}; @@ -182,7 +264,7 @@ namespace ptn::patterns { cmp(std::move(c)) { } template - constexpr bool operator()(X const &x) const noexcept( + constexpr bool match(X const &x) const noexcept( noexcept(std::declval()(x, lo)) && noexcept(std::declval()(hi, x)) && noexcept(std::declval()(lo, x)) && @@ -194,6 +276,11 @@ namespace ptn::patterns { return cmp(lo, x) && cmp(x, hi); } } + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } }; template diff --git a/include/ptn/patterns/type.hpp b/include/ptn/patterns/type.hpp new file mode 100644 index 00000000..22ad057c --- /dev/null +++ b/include/ptn/patterns/type.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include "ptn/patterns/pattern_tag.hpp" + +namespace ptn::patterns { + +} \ No newline at end of file diff --git a/include/ptn/patterns/value.hpp b/include/ptn/patterns/value.hpp index 978d5030..d58a6537 100644 --- a/include/ptn/patterns/value.hpp +++ b/include/ptn/patterns/value.hpp @@ -4,10 +4,40 @@ #include #include #include -#include "pattern_tag.hpp" +#include "ptn/patterns/pattern_base.hpp" // use pattern_base #include "ptn/config.hpp" +/** + * @file value.hpp + * @brief Value-based patterns (`value()` and `ci_value()`). + * + * This module provides the canonical equality-comparison pattern for Patternia. + * A `value_pattern` stores a reference value and matches subjects via an + * equality comparator (default: `std::equal_to<>`). + * + * Example: + * @code {.cpp} + * using namespace ptn::patterns; + * + * match(cmd) + * .when(value("start") >> []{ return "starting"; }) + * .when(ci_value("STOP") >> []{ return "stopping"; }) + * .otherwise("unknown"); + * @endcode + * + * @ingroup patterns + */ + namespace ptn::patterns { + + /** + * @brief Selects the internal storage type for value-like patterns. + * + * C-style strings and string literals are stored as `std::string_view`. + * All other types use `std::decay_t`. + * + * @tparam V Input value type. + */ template /* if is c-style string/array or others */ using value_store_t = std::conditional_t< @@ -16,44 +46,116 @@ namespace ptn::patterns { std::string_view, std::decay_t>; + /** + * @brief A pattern that matches a subject by comparing it with a stored + * value. + * + * @tparam V The stored value type (already decayed/string-view adapted). + * @tparam Cmp Comparator type (`Cmp(a, b)` → bool). Default: + * `std::equal_to<>`. + * + * The comparison is performed as: + * @code + * cmp(subject, stored_value) + * @endcode + * + * This pattern inherits from pattern_base, therefore supporting: + * - `match(subject)` via CRTP forwarding + * - `bind(subject)` defaulting to returning the subject unchanged + */ template > - struct value_pattern : pattern_tag { + struct value_pattern : pattern_base> { using store_t = value_store_t; + /** @brief Stored value for matching. */ store_t v; #if defined(__cpp_no_unique_address) && __cpp_no_unique_address >= 201803L + /** @brief Comparator for equality-like matching (no_unique_address if + * available). */ [[no_unique_address]] Cmp cmp{}; #else Cmp cmp{}; #endif + /** + * @brief Constructs a value_pattern with a stored value and comparator. + * @param val Value to store. + * @param c Comparator instance (default-constructed by default). + */ constexpr value_pattern(store_t val, Cmp c = {}) : v(std::move(val)), cmp(std::move(c)) { } - /* allow matching end x to be compared heterogeneously with stored v */ + + /** + * @brief Performs the actual comparison. + * + * @tparam X Subject type. + * @param x The subject value. + * @return `true` if `cmp(x, v)` returns true. + */ template - constexpr bool operator()(X const &x) const - noexcept(noexcept(std::declval()(x, v))) { + constexpr bool match(X const &x) const noexcept(noexcept(cmp(x, v))) { return cmp(x, v); } + + template + constexpr bool operator()(X const &x) const + noexcept(noexcept(this->match(x))) { + return this->match(x); + } + + /** + * @brief Binding result for value-patterns. + * + * Returns the stored value `v`, enabling value extraction: + * @code + * when(value(42) >> [](auto n){ return n + 1; }) + * @endcode + */ + template + constexpr const store_t &bind(X const &) const noexcept { + return v; + } }; - /* factory: automatic selection of the storage type based on the entry - * parameter */ + /** + * @brief Factory function that constructs a value-pattern. + * + * Automatically adapts C-style strings into `std::string_view`, otherwise + * stores values as `std::decay_t`. + * + * @tparam V Input value type (deduced). + * @param v Value to store. + * @return A `value_pattern` storing the adapted value. + * + * @ingroup patterns + */ template constexpr auto value(V &&v) { using store_t = value_store_t; return value_pattern(store_t(std::forward(v))); } - /* case insensitive comparator */ - struct iequal_ascii : pattern_tag { + /** + * @brief Case-insensitive ASCII comparator for string-like values. + * + * Performs ASCII-only case folding (`A-Z` → `a-z`) and compares characters + * one-by-one. + * + * Supports heterogeneous comparisons via transparent operator() overloads + * accepting any type convertible to `std::string_view`. + * + * @ingroup patterns + */ + struct iequal_ascii : ptn::detail::pattern_tag { + + /** @brief ASCII-only lowercase conversion. */ static constexpr char tolower_ascii(char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; } - /* string_view vsc string_view */ + /** @brief Case-insensitive comparison of two string_view values. */ constexpr bool operator()(std::string_view a, std::string_view b) const noexcept { if (a.size() != b.size()) @@ -65,8 +167,13 @@ namespace ptn::patterns { return true; } - /* transparent comparison */ #if PTN_USE_CONCEPTS + + /** + * @brief Transparent heterogeneous case-insensitive comparison (C++20). + * + * Accepts any A/B convertible to `std::string_view`. + */ template requires( std::is_convertible_v && @@ -75,6 +182,10 @@ namespace ptn::patterns { return (*this)(std::string_view(a), std::string_view(b)); } #else + + /** + * @brief Transparent heterogeneous case-insensitive comparison (C++17). + */ template < typename A, typename B, @@ -87,7 +198,23 @@ namespace ptn::patterns { #endif }; - /* convenience factory: case-insensitive value model */ + /** + * @brief Case-insensitive value-pattern factory. + * + * Equivalent to: + * @code + * value_pattern(store_t(v), iequal_ascii{}); + * @endcode + * + * Used for matching command strings, tokens, or user input without + * case sensitivity. + * + * @tparam V Input value type (deduced). + * @param v Value to store. + * @return A `value_pattern` using `iequal_ascii` comparator. + * + * @ingroup patterns + */ template constexpr auto ci_value(V &&v) { using store_t = value_store_t;