Skip to content

Commit

Permalink
Handle StrNat Internal Spaces Differently
Browse files Browse the repository at this point in the history
StrNat would strip all spaces. We want it to strip leading
spaces and *consecutive* internal spaces but not *semantic*
internal spaces. So modify accordingly.

Closes surge-synthesizer#5634
  • Loading branch information
baconpaul committed Jan 18, 2022
1 parent 1edd725 commit 496c54c
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 123 deletions.
253 changes: 130 additions & 123 deletions libs/strnatcmp/strnatcmp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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); }
54 changes: 54 additions & 0 deletions src/surge-testrunner/UnitTestsINFRA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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); }
}

0 comments on commit 496c54c

Please sign in to comment.