diff --git a/libs/strnatcmp/strnatcmp.cpp b/libs/strnatcmp/strnatcmp.cpp index 74cbb61a541..612555bd339 100644 --- a/libs/strnatcmp/strnatcmp.cpp +++ b/libs/strnatcmp/strnatcmp.cpp @@ -20,7 +20,6 @@ 3. This notice may not be removed or altered from any source distribution. */ - /* partial change history: * * 2004-10-10 mbp: Lift out character type dependencies into macros. @@ -37,142 +36,150 @@ #include "strnatcmp.h" - /* These are defined as macros to make it easier to adapt this code to * different characters types or comparison functions. */ -static inline int -nat_isdigit(nat_char a) -{ - return isdigit((unsigned char) a); -} - +static inline int nat_isdigit(nat_char a) { return isdigit((unsigned char)a); } -static inline int -nat_isspace(nat_char a) -{ - return isspace((unsigned char) a); -} +static inline int nat_isspace(nat_char a) { return isspace((unsigned char)a); } +static inline nat_char nat_toupper(nat_char a) { return toupper((unsigned char)a); } -static inline nat_char -nat_toupper(nat_char a) +static int compare_right(nat_char const *a, nat_char const *b) { - return toupper((unsigned char) a); + int bias = 0; + + /* The longest run of digits wins. That aside, the greatest + value wins, but we can't know that it will until we've scanned + both numbers to know that they have the same magnitude, so we + remember it in BIAS. */ + for (;; a++, b++) + { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return bias; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) + { + if (!bias) + bias = -1; + } + else if (*a > *b) + { + if (!bias) + bias = +1; + } + else if (!*a && !*b) + return bias; + } + + return 0; } - - -static int -compare_right(nat_char const *a, nat_char const *b) +static int compare_left(nat_char const *a, nat_char const *b) { - int bias = 0; - - /* The longest run of digits wins. That aside, the greatest - value wins, but we can't know that it will until we've scanned - both numbers to know that they have the same magnitude, so we - remember it in BIAS. */ - for (;; a++, b++) { - if (!nat_isdigit(*a) && !nat_isdigit(*b)) - return bias; - else if (!nat_isdigit(*a)) - return -1; - else if (!nat_isdigit(*b)) - return +1; - else if (*a < *b) { - if (!bias) - bias = -1; - } else if (*a > *b) { - if (!bias) - bias = +1; - } else if (!*a && !*b) - return bias; - } - - return 0; + /* Compare two left-aligned numbers: the first to have a + different value wins. */ + for (;; a++, b++) + { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return 0; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) + return -1; + else if (*a > *b) + return +1; + } + + return 0; } - -static int -compare_left(nat_char const *a, nat_char const *b) -{ - /* Compare two left-aligned numbers: the first to have a - different value wins. */ - for (;; a++, b++) { - if (!nat_isdigit(*a) && !nat_isdigit(*b)) - return 0; - else if (!nat_isdigit(*a)) - return -1; - else if (!nat_isdigit(*b)) - return +1; - else if (*a < *b) - return -1; - else if (*a > *b) - return +1; - } - - return 0; -} - - static int strnatcmp0(nat_char const *a, nat_char const *b, int fold_case) { - int ai, bi; - nat_char ca, cb; - int fractional, result; - - assert(a && b); - ai = bi = 0; - while (1) { - ca = a[ai]; cb = b[bi]; - - /* skip over leading spaces or zeros */ - while (nat_isspace(ca)) - ca = a[++ai]; - - while (nat_isspace(cb)) - cb = b[++bi]; - - /* process run of digits */ - if (nat_isdigit(ca) && nat_isdigit(cb)) { - fractional = (ca == '0' || cb == '0'); - - if (fractional) { - if ((result = compare_left(a+ai, b+bi)) != 0) - return result; - } else { - if ((result = compare_right(a+ai, b+bi)) != 0) - return result; - } - } - - if (!ca && !cb) { - /* The strings compare the same. Perhaps the caller - will want to call strcmp to break the tie. */ - return 0; - } - - if (fold_case) { - ca = nat_toupper(ca); - cb = nat_toupper(cb); - } - - if (ca < cb) - return -1; - else if (ca > cb) - return +1; - - ++ai; ++bi; - } -} - - - -int strnatcmp(nat_char const *a, nat_char const *b) { - return strnatcmp0(a, b, 0); + int ai, bi; + nat_char ca, cb; + int fractional, result; + + assert(a && b); + ai = bi = 0; + + // Modification: Strip all leading spaces and any consecutive internal spaces + bool fa{true}, fb{true}; + while (1) + { + ca = a[ai]; + cb = b[bi]; + + /* skip over leading spaces or zeros */ + if (fa) + { + while (nat_isspace(ca)) + ca = a[++ai]; + } + else + { + while (ca && nat_isspace(ca) && nat_isspace(a[ai + 1])) + ca = a[++ai]; + } + fa = false; + + if (fb) + { + while (nat_isspace(cb)) + cb = b[++bi]; + } + else + { + while (cb && nat_isspace(cb) && nat_isspace(b[bi + 1])) + cb = b[++bi]; + } + fb = false; + + /* process run of digits */ + if (nat_isdigit(ca) && nat_isdigit(cb)) + { + fractional = (ca == '0' || cb == '0'); + + if (fractional) + { + if ((result = compare_left(a + ai, b + bi)) != 0) + return result; + } + else + { + if ((result = compare_right(a + ai, b + bi)) != 0) + return result; + } + } + + if (!ca && !cb) + { + /* The strings compare the same. Perhaps the caller + will want to call strcmp to break the tie. */ + return 0; + } + + if (fold_case) + { + ca = nat_toupper(ca); + cb = nat_toupper(cb); + } + + if (ca < cb) + return -1; + else if (ca > cb) + return +1; + + ++ai; + ++bi; + } } +int strnatcmp(nat_char const *a, nat_char const *b) { return strnatcmp0(a, b, 0); } /* Compare, recognizing numeric string and ignoring case. */ -int strnatcasecmp(nat_char const *a, nat_char const *b) { - return strnatcmp0(a, b, 1); -} +int strnatcasecmp(nat_char const *a, nat_char const *b) { return strnatcmp0(a, b, 1); } diff --git a/src/surge-testrunner/UnitTestsINFRA.cpp b/src/surge-testrunner/UnitTestsINFRA.cpp index 6ba06b8132a..e923d93122e 100644 --- a/src/surge-testrunner/UnitTestsINFRA.cpp +++ b/src/surge-testrunner/UnitTestsINFRA.cpp @@ -6,6 +6,8 @@ #include "QuadFilterUnit.h" #include "MemoryPool.h" +#include "strnatcmp.h" + #include "catch2/catch2.hpp" inline size_t align_diff(const void *ptr, std::uintptr_t alignment) noexcept @@ -140,4 +142,56 @@ TEST_CASE("Memory Pool Works", "[infra]") REQUIRE(CountAlloc<3>::alloc == 160); REQUIRE(CountAlloc<3>::ct == 0); } +} + +TEST_CASE("strnatcmp with spaces", "[infra]") +{ + SECTION("Basic Compare") + { + REQUIRE(strnatcmp("foo", "bar") == 1); + REQUIRE(strnatcmp("bar", "foo") == -1); + REQUIRE(strnatcmp("bar", "bar") == 0); + } + + SECTION("Number Compare") + { + REQUIRE(strnatcmp("1 foo", "2 foo") == -1); + REQUIRE(strnatcmp("1 foo", "11 foo") == -1); + REQUIRE(strnatcmp("1 foo", "91 foo") == -1); + REQUIRE(strnatcmp("01 foo", "91 foo") == -1); + REQUIRE(strnatcmp("91 foo", "1 foo") == 1); + REQUIRE(strnatcmp("91 foo", "01 foo") == 1); + REQUIRE(strnatcmp("91 foo", "001 foo") == 1); + REQUIRE(strnatcmp("1 foo", "112 foo") == -1); + REQUIRE(strnatcmp("91 foo", "112 foo") == -1); + REQUIRE(strnatcmp("112 foo", "91 foo") == 1); + REQUIRE(strnatcmp("01 foo", "2 foo") == -1); + REQUIRE(strnatcmp("001 foo", "2 foo") == -1); + REQUIRE(strnatcmp("001 foo", "002 foo") == -1); + // Fix these one day + // REQUIRE(strnatcmp("01 foo", "002 foo") == -1); + // REQUIRE(strnatcmp("1 foo", "002 foo") == -1); + // REQUIRE(strnatcmp("1 foo", "02 foo") == -1); + } + + SECTION("Spaces vs Letters") + { + REQUIRE(strnatcmp("hello", "zebra") == -1); + REQUIRE(strnatcmp("hello ", "zebra") == -1); + REQUIRE(strnatcmp(" hello", "zebra") == -1); + REQUIRE(strnatcmp("hello", " zebra") == -1); + REQUIRE(strnatcmp("hello", "zebra ") == -1); + REQUIRE(strnatcmp("hello", "z") == -1); + REQUIRE(strnatcmp("hello", "z ") == -1); + REQUIRE(strnatcmp("hello", " z") == -1); + REQUIRE(strnatcmp(" hello", "z") == -1); + REQUIRE(strnatcmp("hello ", "z") == -1); + } + + SECTION("Central Spaces") + { + REQUIRE(strnatcmp("Spa Day", "SpaDay") == -1); + REQUIRE(strnatcmp("SpaDay", "Spa Day") == 1); + } + SECTION("Doubled Spaces") { REQUIRE(strnatcmp("Spa Day", "Spa Day") == 0); } } \ No newline at end of file