diff --git a/tests/tests_combinator.cpp b/tests/tests_combinator.cpp index 4c472a5..1560019 100644 --- a/tests/tests_combinator.cpp +++ b/tests/tests_combinator.cpp @@ -421,3 +421,30 @@ TEST(OperatorSugar, MixedWithTypePatterns) { _ >> 0), 0); } + +// --- Operator sugar truth table: exercise every !/||/&& combination +// over a small predicate set so each operator branch is hit. --- + +TEST(OperatorSugar, FullTruthTable) { + auto pos = [](int n) { return n > 0; }; + auto even = [](int n) { return n % 2 == 0; }; + + auto run = [&](int v, auto pat) { + return ptn::match(v) | ptn::on(pat >> 1, ptn::_ >> 0); + }; + + // (a || b) : both true, one true, none true + EXPECT_EQ(run(4, ptn::pred(pos) || ptn::pred(even)), 1); + EXPECT_EQ(run(-2, ptn::pred(pos) || ptn::pred(even)), 1); + EXPECT_EQ(run(-3, ptn::pred(pos) || ptn::pred(even)), 0); + + // (a && b) : both true, one false, none true + EXPECT_EQ(run(4, ptn::pred(pos) && ptn::pred(even)), 1); + EXPECT_EQ(run(3, ptn::pred(pos) && ptn::pred(even)), 0); + EXPECT_EQ(run(-2, ptn::pred(pos) && ptn::pred(even)), 0); + + // !p combined with && / || + EXPECT_EQ(run(-1, !ptn::pred(pos) && ptn::pred(even)), 0); + EXPECT_EQ(run(-4, !ptn::pred(pos) && ptn::pred(even)), 1); + EXPECT_EQ(run(0, !ptn::pred(pos) || ptn::pred(pos)), 1); +} diff --git a/tests/tests_guard.cpp b/tests/tests_guard.cpp index 985c7ff..d6c0b8b 100644 --- a/tests/tests_guard.cpp +++ b/tests/tests_guard.cpp @@ -145,3 +145,49 @@ TEST(Guard, BlockScopeNamesSupportFiveValues) { EXPECT_EQ(result, 1); } + +// --- Guard operators not previously exercised: %, /, <=, >=. --- + +namespace { + struct Pair { + int a; + int b; + }; + PTN_BIND(Pair, a, b); +} // namespace + +TEST(GuardExpression, ModuloAndDivisionOperators) { + Pair p{9, 4}; + // a % b == 1 and a / b == 2 + auto r = ptn::match(p) + | ptn::on( + ptn::$(ptn::has<&Pair::a, &Pair::b>)[a % b == 1 + && a / b == 2] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(r, 1); +} + +TEST(GuardExpression, LessEqualGreaterEqual) { + int v = 5; + auto r = ptn::match(v) + | ptn::on(ptn::$[_ <= 5 && _ >= 5] >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 1); + + int w = 6; + auto r2 = ptn::match(w) + | ptn::on(ptn::$[_ <= 5] >> 1, ptn::_ >> 0); + EXPECT_EQ(r2, 0); +} + +TEST(GuardExpression, AllComparisonOperatorsOnMembers) { + Pair p{3, 3}; + auto r = ptn::match(p) + | ptn::on( + ptn::$( + ptn::has<&Pair::a, &Pair::b>)[a != b || a == b + || a < b || a > b] + >> 1, + ptn::_ >> 0); + EXPECT_EQ(r, 1); +} diff --git a/tests/tests_literal_pattern.cpp b/tests/tests_literal_pattern.cpp index feb3c3f..5f8edea 100644 --- a/tests/tests_literal_pattern.cpp +++ b/tests/tests_literal_pattern.cpp @@ -170,3 +170,40 @@ TEST(LiteralPattern, ValSupportsFloatingPointInCpp20) { EXPECT_EQ(hit, 7); } #endif + +// --- lit_ci branch coverage: non-letter chars, size mismatch, +// and the generic convertible-to-string_view comparison path. --- + +TEST(LiteralPattern, LitCiNonLetterCharacters) { + // Exercises tolower_ascii's non-letter branch (digits/symbols). + std::string s = "ABC-123_XYZ"; + auto r = ptn::match(s) + | ptn::on(ptn::lit_ci("abc-123_xyz") >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 1); +} + +TEST(LiteralPattern, LitCiSizeMismatch) { + // Different lengths hit the early-return false in iequal_ascii. + std::string s = "ab"; + auto r = ptn::match(s) + | ptn::on(ptn::lit_ci("abc") >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 0); +} + +TEST(LiteralPattern, LitCiAgainstStringViewSubject) { + // Subject as std::string_view exercises the comparison through + // the string_view overload. + std::string_view s = "HeLLo"; + auto r = ptn::match(s) + | ptn::on(ptn::lit_ci("hello") >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 1); +} + +TEST(LiteralPattern, LitCiCharLiteralSubject) { + // const char* subject forces the templated convertible overload + // of iequal_ascii::operator(). + const char *s = "World"; + auto r = ptn::match(s) + | ptn::on(ptn::lit_ci("WORLD") >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 1); +} diff --git a/tests/tests_terminal_semantics.cpp b/tests/tests_terminal_semantics.cpp index dda90f1..fee38ea 100644 --- a/tests/tests_terminal_semantics.cpp +++ b/tests/tests_terminal_semantics.cpp @@ -45,4 +45,4 @@ TEST(TerminalSemantics, FirstMatchingCaseWins) { | ptn::on(ptn::$ >> 1, ptn::$ >> 2, ptn::_ >> 0); EXPECT_EQ(result, 1); -} +} \ No newline at end of file diff --git a/tests/tests_type_pattern.cpp b/tests/tests_type_pattern.cpp index cbaa4cb..09898e9 100644 --- a/tests/tests_type_pattern.cpp +++ b/tests/tests_type_pattern.cpp @@ -151,3 +151,47 @@ TEST(TypePattern, MixedVariantGuardedCaseWinsWhenPredicateTrue) { EXPECT_EQ(guarded_hits, 1); EXPECT_EQ(simple_hits, 0); } + +// --- alt and is: cover both the no-subpattern fast path and +// the subpattern path for match()/bind() in type.hpp. --- + +TEST(TypePattern, AltByIndexNoSubpatternMatchFastPath) { + std::variant v = std::string("x"); + // alt() with no subpattern: match() fast-path returns true; + // handler is nullary (no_subpattern binds nothing). + auto r = ptn::match(v) + | ptn::on(ptn::alt<1>() >> [] { return 1; }, ptn::_ >> 0); + EXPECT_EQ(r, 1); +} + +TEST(TypePattern, AltByIndexWithSubpattern) { + std::variant v = std::string("hi"); + // alt(sub): the subpattern match()/bind() branches. + auto r = ptn::match(v) + | ptn::on(ptn::alt<1>(ptn::pred([](const std::string &s) { + return s.size() == 2; + })) >> 1, + ptn::_ >> 0); + EXPECT_EQ(r, 1); +} + +TEST(TypePattern, AltRejectsWrongIndex) { + std::variant v = 7; + auto r = ptn::match(v) | ptn::on(ptn::alt<1>() >> 1, ptn::_ >> 0); + EXPECT_EQ(r, 0); +} + +TEST(TypePattern, IsWithSubpatternMatchAndBind) { + struct P { + int a; + int b; + }; + std::variant v = P{2, 3}; + // is(sub): exercises the subpattern branch of type_is_pattern. + auto r = ptn::match(v) + | ptn::on( + ptn::is

(ptn::$(ptn::has<&P::a, &P::b>)) >> + [](int a, int b) { return a * b; }, + ptn::_ >> 0); + EXPECT_EQ(r, 6); +}