diff --git a/string.hpp b/string.hpp index 19177ece..70eadf06 100644 --- a/string.hpp +++ b/string.hpp @@ -8,6 +8,7 @@ #pragma once +#include "ascii.hpp" #include "debug.hpp" #include "view.hpp" @@ -216,3 +217,57 @@ namespace cruft { } }; } + + +namespace cruft::string::compare { + /////////////////////////////////////////////////////////////////////////// + /// A case comparator that tests equality on a string after a + /// per-character transform is applied. + /// + /// Neither string will be modified. + /// + /// \tparam TransformV A character transform function + template + struct transform { + template < + typename CharT, + typename = std::void_t< + typename std::char_traits::char_type + > + > + bool operator() (cruft::view a, cruft::view b) + { + if (a.size () != b.size ()) + return false; + + for (auto i = a.begin (), j = b.begin (); i != a.end (); ++i, ++j) + if (TransformV (*i) != TransformV (*j)) + return false; + + return true; + } + + + template < + typename CharT, + typename = std::void_t< + typename std::char_traits::char_type + > + > + bool operator() (CharT const *a, CharT const *b) const noexcept + { + auto const *i = a; + auto const *j = b; + + for ( ; *i && *j; ++i, ++j) + if (TransformV (*i) != TransformV (*j)) + return false; + + // Ensure we've reached the ends of both strings + return !*i && !*j; + } + }; + + using lower = transform; + using upper = transform; +} diff --git a/test/string.cpp b/test/string.cpp index 529b2ded..ee510175 100644 --- a/test/string.cpp +++ b/test/string.cpp @@ -144,7 +144,7 @@ test_contains (cruft::TAP::logger &tap) /////////////////////////////////////////////////////////////////////////////// -void test_comparator (cruft::TAP::logger &tap) +void test_comparator_less (cruft::TAP::logger &tap) { cruft::string_less cmp; @@ -164,6 +164,39 @@ void test_comparator (cruft::TAP::logger &tap) } +/////////////////////////////////////////////////////////////////////////////// +void test_comparator_lower (cruft::TAP::logger &tap) +{ + struct { + char const *a; + char const *b; + bool equal; + char const *message; + } const TESTS[] = { + { "a", "a", true, "both single lower" }, + { "a", "A", true, "lower, upper singles" }, + { "A", "a", true, "upper, lower singles" }, + { "AbcDEf", "aBcdEF", true, "mixed upper lower" }, + { "A", "aa", false, "mixed case, different lengths" }, + }; + + cruft::string::compare::lower cmp; + + for (auto const &t: TESTS) { + tap.expect_eq (cmp (t.a, t.b), t.equal, "compare::lower, pointers, %!", t.message); + tap.expect_eq ( + cmp ( + cruft::view (t.a), + cruft::view (t.b) + ), + t.equal, + "compare::lower, views, %!", + t.message + ); + } +} + + /////////////////////////////////////////////////////////////////////////////// int main (int, char**) @@ -173,7 +206,8 @@ main (int, char**) test_tokeniser (tap); test_position (tap); test_contains (tap); - test_comparator (tap); + test_comparator_less (tap); + test_comparator_lower (tap); return tap.status (); } \ No newline at end of file