diff --git a/include/bx/string.h b/include/bx/string.h index 7956819..4715062 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -89,26 +89,56 @@ namespace bx }; /// - bool toBool(const char* _str); - - /// Case insensitive string compare. - int32_t stricmp(const char* _a, const char* _b); + bool isSpace(char _ch); /// - size_t strnlen(const char* _str, size_t _max); + bool isUpper(char _ch); + + /// + bool isLower(char _ch); + + /// + bool isAlpha(char _ch); + + /// + bool isNumeric(char _ch); + + /// + bool isAlphaNum(char _ch); + + /// + char toLower(char _ch); + + /// + char toUpper(char _ch); + + /// + bool toBool(const char* _str); + + /// String compare. + int32_t strncmp(const char* _lhs, const char* _rhs, size_t _max = -1); + + /// Case insensitive string compare. + int32_t strincmp(const char* _lhs, const char* _rhs, size_t _max = -1); + + /// + size_t strnlen(const char* _str, size_t _max = -1); /// Copy _num characters from string _src to _dst buffer of maximum _dstSize capacity /// including zero terminator. Copy will be terminated with '\0'. size_t strlncpy(char* _dst, size_t _dstSize, const char* _src, size_t _num = -1); - /// Find substring in string. Limit search to _size. - const char* strnstr(const char* _str, const char* _find, size_t _size); + /// + const char* strnchr(const char* _str, char _ch, size_t _max = -1); - /// Find substring in string. Case insensitive. - const char* stristr(const char* _str, const char* _find); + /// + const char* strnrchr(const char* _str, char _ch, size_t _max = -1); + + /// Find substring in string. Limit search to _size. + const char* strnstr(const char* _str, const char* _find, size_t _max = -1); /// Find substring in string. Case insensitive. Limit search to _max. - const char* stristr(const char* _str, const char* _find, size_t _max); + const char* stristr(const char* _str, const char* _find, size_t _max = -1); /// Find new line. Returns pointer after new line terminator. const char* strnl(const char* _str); diff --git a/src/commandline.cpp b/src/commandline.cpp index 3e609e9..3cbe098 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -246,11 +246,11 @@ namespace bx const char* arg = findOption(_short, _long, 1); if (NULL != arg) { - if ('0' == *arg || (0 == stricmp(arg, "false") ) ) + if ('0' == *arg || (0 == strincmp(arg, "false") ) ) { _value = false; } - else if ('0' != *arg || (0 == stricmp(arg, "true") ) ) + else if ('0' != *arg || (0 == strincmp(arg, "true") ) ) { _value = true; } @@ -294,7 +294,7 @@ namespace bx } else if (NULL != _long && '-' == *arg - && 0 == stricmp(arg+1, _long) ) + && 0 == strincmp(arg+1, _long) ) { if (0 == _skip) { diff --git a/src/string.cpp b/src/string.cpp index 8bca907..b730633 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -4,11 +4,8 @@ */ #include -#include // tolower #include // va_list #include // vsnprintf, vsnwprintf -#include -#include // wchar_t #include @@ -17,19 +14,90 @@ namespace bx { + bool isSpace(char _ch) + { + return ' ' == _ch + || '\t' == _ch + || '\n' == _ch + || '\v' == _ch + || '\f' == _ch + || '\r' == _ch + ; + } + + bool isUpper(char _ch) + { + return _ch >= 'A' && _ch <= 'Z'; + } + + bool isLower(char _ch) + { + return _ch >= 'a' && _ch <= 'z'; + } + + bool isAlpha(char _ch) + { + return isLower(_ch) || isUpper(_ch); + } + + bool isNumeric(char _ch) + { + return _ch >= '0' && _ch <= '9'; + } + + bool isAlphaNum(char _ch) + { + return isAlpha(_ch) || isNumeric(_ch); + } + + char toLower(char _ch) + { + return _ch - (isUpper(_ch) ? 0x20 : 0); + } + + char toUpper(char _ch) + { + return _ch + (isLower(_ch) ? 0x20 : 0); + } + bool toBool(const char* _str) { - char ch = (char)::tolower(_str[0]); + char ch = (char)toLower(_str[0]); return ch == 't' || ch == '1'; } - int32_t stricmp(const char* _a, const char* _b) + int32_t strncmp(const char* _lhs, const char* _rhs, size_t _max) { -#if BX_CRT_MSVC - return ::_stricmp(_a, _b); -#else - return ::strcasecmp(_a, _b); -#endif // BX_COMPILER_ + for ( + ; 0 < _max && *_lhs == *_rhs + ; ++_lhs, ++_rhs, --_max + ) + { + if (*_lhs != '\0' + || *_rhs != '\0') + { + break; + } + } + + return *_lhs - *_rhs; + } + + int32_t strincmp(const char* _lhs, const char* _rhs, size_t _max) + { + for ( + ; 0 < _max && toLower(*_lhs) == toLower(*_rhs) + ; ++_lhs, ++_rhs, --_max + ) + { + if (*_lhs != '\0' + || *_rhs != '\0') + { + break; + } + } + + return *_lhs - *_rhs; } size_t strnlen(const char* _str, size_t _max) @@ -48,13 +116,39 @@ namespace bx const size_t len = strnlen(_src, _num); const size_t max = _dstSize-1; const size_t num = (len < max ? len : max); - strncpy(_dst, _src, num); + memcpy(_dst, _src, num); _dst[num] = '\0'; return num; } - const char* strnstr(const char* _str, const char* _find, size_t _size) + const char* strnchr(const char* _str, char _ch, size_t _max) + { + for (size_t ii = 0, len = strnlen(_str, _max); ii < len; ++ii) + { + if (_str[ii] == _ch) + { + return &_str[ii]; + } + } + + return NULL; + } + + const char* strnrchr(const char* _str, char _ch, size_t _max) + { + for (size_t ii = strnlen(_str, _max); 0 < ii; --ii) + { + if (_str[ii] == _ch) + { + return &_str[ii]; + } + } + + return NULL; + } + + const char* strnstr(const char* _str, const char* _find, size_t _max) { char first = *_find; if ('\0' == first) @@ -63,10 +157,10 @@ namespace bx } const char* cmp = _find + 1; - size_t len = strlen(cmp); + size_t len = strnlen(cmp); do { - for (char match = *_str++; match != first && 0 < _size; match = *_str++, --_size) + for (char match = *_str++; match != first && 0 < _max; match = *_str++, --_max) { if ('\0' == match) { @@ -74,7 +168,7 @@ namespace bx } } - if (0 == _size) + if (0 == _max) { return NULL; } @@ -84,56 +178,17 @@ namespace bx return --_str; } - const char* stristr(const char* _str, const char* _find) - { - const char* ptr = _str; - - for (size_t len = strlen(_str), searchLen = strlen(_find) - ; len >= searchLen - ; ++ptr, --len) - { - // Find start of the string. - while (tolower(*ptr) != tolower(*_find) ) - { - ++ptr; - --len; - - // Search pattern lenght can't be longer than the string. - if (searchLen > len) - { - return NULL; - } - } - - // Set pointers. - const char* string = ptr; - const char* search = _find; - - // Start comparing. - while (tolower(*string++) == tolower(*search++) ) - { - // If end of the 'search' string is reached, all characters match. - if ('\0' == *search) - { - return ptr; - } - } - } - - return NULL; - } - const char* stristr(const char* _str, const char* _find, size_t _max) { const char* ptr = _str; size_t stringLen = strnlen(_str, _max); - const size_t findLen = strlen(_find); + const size_t findLen = strnlen(_find); for (; stringLen >= findLen; ++ptr, --stringLen) { // Find start of the string. - while (tolower(*ptr) != tolower(*_find) ) + while (toLower(*ptr) != toLower(*_find) ) { ++ptr; --stringLen; @@ -150,7 +205,7 @@ namespace bx const char* search = _find; // Start comparing. - while (tolower(*string++) == tolower(*search++) ) + while (toLower(*string++) == toLower(*search++) ) { // If end of the 'search' string is reached, all characters match. if ('\0' == *search) @@ -205,19 +260,19 @@ namespace bx const char* strws(const char* _str) { - for (; isspace(*_str); ++_str) {}; + for (; isSpace(*_str); ++_str) {}; return _str; } const char* strnws(const char* _str) { - for (; !isspace(*_str); ++_str) {}; + for (; !isSpace(*_str); ++_str) {}; return _str; } const char* strword(const char* _str) { - for (char ch = *_str++; isalnum(ch) || '_' == ch; ch = *_str++) {}; + for (char ch = *_str++; isAlphaNum(ch) || '_' == ch; ch = *_str++) {}; return _str-1; } @@ -262,21 +317,21 @@ namespace bx const char* findIdentifierMatch(const char* _str, const char* _word) { - size_t len = strlen(_word); - const char* ptr = strstr(_str, _word); - for (; NULL != ptr; ptr = strstr(ptr + len, _word) ) + size_t len = strnlen(_word); + const char* ptr = strnstr(_str, _word); + for (; NULL != ptr; ptr = strnstr(ptr + len, _word) ) { if (ptr != _str) { char ch = *(ptr - 1); - if (isalnum(ch) || '_' == ch) + if (isAlphaNum(ch) || '_' == ch) { continue; } } char ch = ptr[len]; - if (isalnum(ch) || '_' == ch) + if (isAlphaNum(ch) || '_' == ch) { continue; } @@ -357,10 +412,10 @@ namespace bx const char* baseName(const char* _filePath) { - const char* bs = strrchr(_filePath, '\\'); - const char* fs = strrchr(_filePath, '/'); + const char* bs = strnrchr(_filePath, '\\'); + const char* fs = strnrchr(_filePath, '/'); const char* slash = (bs > fs ? bs : fs); - const char* colon = strrchr(_filePath, ':'); + const char* colon = strnrchr(_filePath, ':'); const char* basename = slash > colon ? slash : colon; if (NULL != basename) { @@ -452,7 +507,7 @@ namespace bx if (nn == 0) { - return(dlen + strlen(s)); + return(dlen + strnlen(s)); } while (*s != '\0') diff --git a/tests/handle_bench.cpp b/tests/handle_bench.cpp index a882cbe..6169bc4 100644 --- a/tests/handle_bench.cpp +++ b/tests/handle_bench.cpp @@ -28,13 +28,13 @@ int main() for (uint32_t jj = 0; jj < numElements; ++jj) { tinystl::pair ok = map.insert(tinystl::make_pair(uint64_t(jj), uint16_t(jj) ) ); - assert(ok.second); + assert(ok.second); BX_UNUSED(ok); } for (uint32_t jj = 0; jj < numElements; ++jj) { bool ok = bx::mapRemove(map, uint64_t(jj) ); - assert(ok); + assert(ok); BX_UNUSED(ok); } assert(map.size() == 0); @@ -56,13 +56,13 @@ int main() for (uint32_t jj = 0; jj < numElements; ++jj) { std::pair ok = map.insert(std::make_pair(uint64_t(jj), uint16_t(jj) ) ); - assert(ok.second); + assert(ok.second); BX_UNUSED(ok); } for (uint32_t jj = 0; jj < numElements; ++jj) { bool ok = bx::mapRemove(map, uint64_t(jj) ); - assert(ok); + assert(ok); BX_UNUSED(ok); } assert(map.size() == 0); @@ -83,13 +83,13 @@ int main() for (uint32_t jj = 0; jj < numElements; ++jj) { bool ok = map.insert(jj, uint16_t(jj) ); - assert(ok); + assert(ok); BX_UNUSED(ok); } for (uint32_t jj = 0; jj < numElements; ++jj) { bool ok = map.removeByKey(uint64_t(jj) ); - assert(ok); + assert(ok); BX_UNUSED(ok); } assert(map.getNumElements() == 0); diff --git a/tests/string_test.cpp b/tests/string_test.cpp index ed83e86..771cace 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -40,6 +40,29 @@ TEST_CASE("strlncpy", "") REQUIRE(num == 4); } +TEST_CASE("strincmp", "") +{ + REQUIRE(0 == bx::strincmp("test", "test") ); + REQUIRE(0 == bx::strincmp("test", "testestes", 4) ); + REQUIRE(0 == bx::strincmp("testestes", "test", 4) ); +} + +TEST_CASE("strnchr", "") +{ + const char* test = "test"; + REQUIRE(NULL == bx::strnchr(test, 's', 0) ); + REQUIRE(NULL == bx::strnchr(test, 's', 2) ); + REQUIRE(&test[2] == bx::strnchr(test, 's') ); +} + +TEST_CASE("strnrchr", "") +{ + const char* test = "test"; + REQUIRE(NULL == bx::strnrchr(test, 's', 0) ); + REQUIRE(NULL == bx::strnrchr(test, 's', 1) ); + REQUIRE(&test[2] == bx::strnrchr(test, 's') ); +} + TEST_CASE("StringView", "") { bx::StringView sv("test"); diff --git a/tests/vsnprintf_test.cpp b/tests/vsnprintf_test.cpp index 8113beb..04dcf16 100644 --- a/tests/vsnprintf_test.cpp +++ b/tests/vsnprintf_test.cpp @@ -20,3 +20,35 @@ TEST_CASE("vsnprintf truncated", "Truncated output buffer.") REQUIRE(10 == bx::snprintf(buffer, BX_COUNTOF(buffer), "Ten chars!") ); REQUIRE(0 == strcmp(buffer, "Ten ch") ); } + +static bool test(const char* _expected, const char* _format, ...) +{ + int32_t max = (int32_t)strlen(_expected) + 1; + char* temp = (char*)alloca(max); + + va_list argList; + va_start(argList, _format); + int32_t len = bx::vsnprintf(temp, max, _format, argList); + va_end(argList); + + bool result = true + && len == max-1 + && 0 == strcmp(_expected, temp) + ; + + if (!result) + { + printf("result (%d) %s, expected (%d) %s\n", len, temp, max-1, _expected); + } + + return result; +} + +TEST_CASE("vsnprintf f", "") +{ + REQUIRE(test("1.337", "%0.3f", 1.337) ); + REQUIRE(test(" 13.370", "%8.3f", 13.37) ); + REQUIRE(test(" 13.370", "%*.*f", 8, 3, 13.37) ); + REQUIRE(test("13.370 ", "%-8.3f", 13.37) ); + REQUIRE(test("13.370 ", "%*.*f", -8, 3, 13.37) ); +}