eed33f255d
Merge commit '80d62f224900ab486a5bc5a6e80ce1e25a0e38e8' as 'externals/zycore'
1098 lines
34 KiB
C
1098 lines
34 KiB
C
/***************************************************************************************************
|
|
|
|
Zyan Core Library (Zycore-C)
|
|
|
|
Original Author : Florian Bernd
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
|
|
***************************************************************************************************/
|
|
|
|
#include <Zycore/String.h>
|
|
#include <Zycore/LibC.h>
|
|
|
|
/* ============================================================================================== */
|
|
/* Internal macros */
|
|
/* ============================================================================================== */
|
|
|
|
/**
|
|
* Writes a terminating '\0' character at the end of the string data.
|
|
*/
|
|
#define ZYCORE_STRING_NULLTERMINATE(string) \
|
|
*(char*)((ZyanU8*)(string)->vector.data + (string)->vector.size - 1) = '\0';
|
|
|
|
/**
|
|
* Checks for a terminating '\0' character at the end of the string data.
|
|
*/
|
|
#define ZYCORE_STRING_ASSERT_NULLTERMINATION(string) \
|
|
ZYAN_ASSERT(*(char*)((ZyanU8*)(string)->vector.data + (string)->vector.size - 1) == '\0');
|
|
|
|
/* ============================================================================================== */
|
|
/* Exported functions */
|
|
/* ============================================================================================== */
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Constructor and destructor */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
#ifndef ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringInit(ZyanString* string, ZyanUSize capacity)
|
|
{
|
|
return ZyanStringInitEx(string, capacity, ZyanAllocatorDefault(),
|
|
ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
|
|
}
|
|
|
|
#endif // ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringInitEx(ZyanString* string, ZyanUSize capacity, ZyanAllocator* allocator,
|
|
float growth_factor, float shrink_threshold)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
string->flags = 0;
|
|
capacity = ZYAN_MAX(ZYAN_STRING_MIN_CAPACITY, capacity) + 1;
|
|
ZYAN_CHECK(ZyanVectorInitEx(&string->vector, sizeof(char), capacity, ZYAN_NULL, allocator,
|
|
growth_factor, shrink_threshold));
|
|
ZYAN_ASSERT(string->vector.capacity >= capacity);
|
|
// Some of the string code relies on `sizeof(char) == 1`
|
|
ZYAN_ASSERT(string->vector.element_size == 1);
|
|
|
|
*(char*)string->vector.data = '\0';
|
|
++string->vector.size;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringInitCustomBuffer(ZyanString* string, char* buffer, ZyanUSize capacity)
|
|
{
|
|
if (!string || !capacity)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
string->flags = ZYAN_STRING_HAS_FIXED_CAPACITY;
|
|
ZYAN_CHECK(ZyanVectorInitCustomBuffer(&string->vector, sizeof(char), (void*)buffer, capacity,
|
|
ZYAN_NULL));
|
|
ZYAN_ASSERT(string->vector.capacity == capacity);
|
|
// Some of the string code relies on `sizeof(char) == 1`
|
|
ZYAN_ASSERT(string->vector.element_size == 1);
|
|
|
|
*(char*)string->vector.data = '\0';
|
|
++string->vector.size;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringDestroy(ZyanString* string)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
if (string->flags & ZYAN_STRING_HAS_FIXED_CAPACITY)
|
|
{
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
return ZyanVectorDestroy(&string->vector);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Duplication */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
#ifndef ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringDuplicate(ZyanString* destination, const ZyanStringView* source,
|
|
ZyanUSize capacity)
|
|
{
|
|
return ZyanStringDuplicateEx(destination, source, capacity, ZyanAllocatorDefault(),
|
|
ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
|
|
}
|
|
|
|
#endif // ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringDuplicateEx(ZyanString* destination, const ZyanStringView* source,
|
|
ZyanUSize capacity, ZyanAllocator* allocator, float growth_factor, float shrink_threshold)
|
|
{
|
|
if (!source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
const ZyanUSize len = source->string.vector.size;
|
|
capacity = ZYAN_MAX(capacity, len - 1);
|
|
ZYAN_CHECK(ZyanStringInitEx(destination, capacity, allocator, growth_factor, shrink_threshold));
|
|
ZYAN_ASSERT(destination->vector.capacity >= len);
|
|
|
|
ZYAN_MEMCPY(destination->vector.data, source->string.vector.data,
|
|
source->string.vector.size - 1);
|
|
destination->vector.size = len;
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringDuplicateCustomBuffer(ZyanString* destination, const ZyanStringView* source,
|
|
char* buffer, ZyanUSize capacity)
|
|
{
|
|
if (!source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
const ZyanUSize len = source->string.vector.size;
|
|
if (capacity < len)
|
|
{
|
|
return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanStringInitCustomBuffer(destination, buffer, capacity));
|
|
ZYAN_ASSERT(destination->vector.capacity >= len);
|
|
|
|
ZYAN_MEMCPY(destination->vector.data, source->string.vector.data,
|
|
source->string.vector.size - 1);
|
|
destination->vector.size = len;
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Concatenation */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
#ifndef ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringConcat(ZyanString* destination, const ZyanStringView* s1,
|
|
const ZyanStringView* s2, ZyanUSize capacity)
|
|
{
|
|
return ZyanStringConcatEx(destination, s1, s2, capacity, ZyanAllocatorDefault(),
|
|
ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
|
|
}
|
|
|
|
#endif // ZYAN_NO_LIBC
|
|
|
|
ZyanStatus ZyanStringConcatEx(ZyanString* destination, const ZyanStringView* s1,
|
|
const ZyanStringView* s2, ZyanUSize capacity, ZyanAllocator* allocator, float growth_factor,
|
|
float shrink_threshold)
|
|
{
|
|
if (!s1 || !s2 || !s1->string.vector.size || !s2->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
const ZyanUSize len = s1->string.vector.size + s2->string.vector.size - 1;
|
|
capacity = ZYAN_MAX(capacity, len - 1);
|
|
ZYAN_CHECK(ZyanStringInitEx(destination, capacity, allocator, growth_factor, shrink_threshold));
|
|
ZYAN_ASSERT(destination->vector.capacity >= len);
|
|
|
|
ZYAN_MEMCPY(destination->vector.data, s1->string.vector.data, s1->string.vector.size - 1);
|
|
ZYAN_MEMCPY((char*)destination->vector.data + s1->string.vector.size - 1,
|
|
s2->string.vector.data, s2->string.vector.size - 1);
|
|
destination->vector.size = len;
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringConcatCustomBuffer(ZyanString* destination, const ZyanStringView* s1,
|
|
const ZyanStringView* s2, char* buffer, ZyanUSize capacity)
|
|
{
|
|
if (!s1 || !s2 || !s1->string.vector.size || !s2->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
const ZyanUSize len = s1->string.vector.size + s2->string.vector.size - 1;
|
|
if (capacity < len)
|
|
{
|
|
return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanStringInitCustomBuffer(destination, buffer, capacity));
|
|
ZYAN_ASSERT(destination->vector.capacity >= len);
|
|
|
|
ZYAN_MEMCPY(destination->vector.data, s1->string.vector.data, s1->string.vector.size - 1);
|
|
ZYAN_MEMCPY((char*)destination->vector.data + s1->string.vector.size - 1,
|
|
s2->string.vector.data, s2->string.vector.size - 1);
|
|
destination->vector.size = len;
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Views */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringViewInsideView(ZyanStringView* view, const ZyanStringView* source)
|
|
{
|
|
if (!view || !source)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
view->string.vector.data = source->string.vector.data;
|
|
view->string.vector.size = source->string.vector.size;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringViewInsideViewEx(ZyanStringView* view, const ZyanStringView* source,
|
|
ZyanUSize index, ZyanUSize count)
|
|
{
|
|
if (!view || !source)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (index + count >= source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
view->string.vector.data = (void*)((char*)source->string.vector.data + index);
|
|
view->string.vector.size = count;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringViewInsideBuffer(ZyanStringView* view, const char* string)
|
|
{
|
|
if (!view || !string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
view->string.vector.data = (void*)string;
|
|
view->string.vector.size = ZYAN_STRLEN(string) + 1;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringViewInsideBufferEx(ZyanStringView* view, const char* buffer, ZyanUSize length)
|
|
{
|
|
if (!view || !buffer || !length)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
view->string.vector.data = (void*)buffer;
|
|
view->string.vector.size = length + 1;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringViewGetSize(const ZyanStringView* view, ZyanUSize* size)
|
|
{
|
|
if (!view || !size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
ZYAN_ASSERT(view->string.vector.size >= 1);
|
|
*size = view->string.vector.size - 1;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZYCORE_EXPORT ZyanStatus ZyanStringViewGetData(const ZyanStringView* view, const char** buffer)
|
|
{
|
|
if (!view || !buffer)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*buffer = view->string.vector.data;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Character access */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringGetChar(const ZyanStringView* string, ZyanUSize index, char* value)
|
|
{
|
|
if (!string || !value)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow direct access to the terminating '\0' character
|
|
if (index + 1 >= string->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
const char* chr;
|
|
ZYAN_CHECK(ZyanVectorGetPointer(&string->string.vector, index, (const void**)&chr));
|
|
*value = *chr;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringGetCharMutable(ZyanString* string, ZyanUSize index, char** value)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow direct access to the terminating '\0' character
|
|
if (index + 1 >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
return ZyanVectorGetPointerMutable(&string->vector, index, (void**)value);
|
|
}
|
|
|
|
ZyanStatus ZyanStringSetChar(ZyanString* string, ZyanUSize index, char value)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow direct access to the terminating '\0' character
|
|
if (index + 1 >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
return ZyanVectorSet(&string->vector, index, (void*)&value);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Insertion */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringInsert(ZyanString* destination, ZyanUSize index, const ZyanStringView* source)
|
|
{
|
|
if (!destination || !source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (index == destination->vector.size)
|
|
{
|
|
return ZyanStringAppend(destination, source);
|
|
}
|
|
|
|
// Don't allow insertion after the terminating '\0' character
|
|
if (index >= destination->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorInsertRange(&destination->vector, index, source->string.vector.data,
|
|
source->string.vector.size - 1));
|
|
ZYCORE_STRING_ASSERT_NULLTERMINATION(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringInsertEx(ZyanString* destination, ZyanUSize destination_index,
|
|
const ZyanStringView* source, ZyanUSize source_index, ZyanUSize count)
|
|
{
|
|
if (!destination || !source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (destination_index == destination->vector.size)
|
|
{
|
|
return ZyanStringAppendEx(destination, source, source_index, count);
|
|
}
|
|
|
|
// Don't allow insertion after the terminating '\0' character
|
|
if (destination_index >= destination->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (source_index + count >= source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorInsertRange(&destination->vector, destination_index,
|
|
(char*)source->string.vector.data + source_index, count));
|
|
ZYCORE_STRING_ASSERT_NULLTERMINATION(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Appending */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringAppend(ZyanString* destination, const ZyanStringView* source)
|
|
{
|
|
if (!destination || !source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
const ZyanUSize len = destination->vector.size;
|
|
ZYAN_CHECK(ZyanVectorResize(&destination->vector, len + source->string.vector.size - 1));
|
|
ZYAN_MEMCPY((char*)destination->vector.data + len - 1, source->string.vector.data,
|
|
source->string.vector.size - 1);
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringAppendEx(ZyanString* destination, const ZyanStringView* source,
|
|
ZyanUSize source_index, ZyanUSize count)
|
|
{
|
|
if (!destination || !source || !source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (source_index + count >= source->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
const ZyanUSize len = destination->vector.size;
|
|
ZYAN_CHECK(ZyanVectorResize(&destination->vector, len + count));
|
|
ZYAN_MEMCPY((char*)destination->vector.data + len - 1,
|
|
(const char*)source->string.vector.data + source_index, count);
|
|
ZYCORE_STRING_NULLTERMINATE(destination);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Deletion */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringDelete(ZyanString* string, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow removal of the terminating '\0' character
|
|
if (index + count >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorDeleteRange(&string->vector, index, count));
|
|
ZYCORE_STRING_NULLTERMINATE(string);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringTruncate(ZyanString* string, ZyanUSize index)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow removal of the terminating '\0' character
|
|
if (index >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorDeleteRange(&string->vector, index, string->vector.size - index - 1));
|
|
ZYCORE_STRING_NULLTERMINATE(string);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringClear(ZyanString* string)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorClear(&string->vector));
|
|
// `ZyanVector` guarantees a minimum capacity of 1 element/character
|
|
ZYAN_ASSERT(string->vector.capacity >= 1);
|
|
|
|
*(char*)string->vector.data = '\0';
|
|
string->vector.size++;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Searching */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringLPos(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index)
|
|
{
|
|
if (!haystack)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringLPosEx(haystack, needle, found_index, 0, haystack->string.vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringLPosEx(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
if (!haystack || !needle || !found_index)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (index + count >= haystack->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
if ((haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
|
|
(haystack->string.vector.size < needle->string.vector.size))
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* s = (const char*)haystack->string.vector.data + index;
|
|
const char* b = (const char*)needle->string.vector.data;
|
|
for (; s + 1 < (const char*)haystack->string.vector.data + haystack->string.vector.size; ++s)
|
|
{
|
|
if (*s != *b)
|
|
{
|
|
continue;
|
|
}
|
|
const char* a = s;
|
|
for (;;)
|
|
{
|
|
if ((ZyanUSize)(a - (const char*)haystack->string.vector.data) > index + count)
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
if (*b == 0)
|
|
{
|
|
*found_index = (ZyanISize)(s - (const char*)haystack->string.vector.data);
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
if (*a++ != *b++)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
b = (char*)needle->string.vector.data;
|
|
}
|
|
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
ZyanStatus ZyanStringLPosI(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index)
|
|
{
|
|
if (!haystack)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringLPosIEx(haystack, needle, found_index, 0, haystack->string.vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringLPosIEx(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
// This solution assumes that characters are represented using ASCII representation, i.e.,
|
|
// codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
|
|
// 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.
|
|
|
|
if (!haystack || !needle || !found_index)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (index + count >= haystack->string.vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
if ((haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
|
|
(haystack->string.vector.size < needle->string.vector.size))
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* s = (const char*)haystack->string.vector.data + index;
|
|
const char* b = (const char*)needle->string.vector.data;
|
|
for (; s + 1 < (const char*)haystack->string.vector.data + haystack->string.vector.size; ++s)
|
|
{
|
|
if ((*s != *b) && ((*s ^ 32) != *b))
|
|
{
|
|
continue;
|
|
}
|
|
const char* a = s;
|
|
for (;;)
|
|
{
|
|
if ((ZyanUSize)(a - (const char*)haystack->string.vector.data) > index + count)
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
if (*b == 0)
|
|
{
|
|
*found_index = (ZyanISize)(s - (const char*)haystack->string.vector.data);
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
const char c1 = *a++;
|
|
const char c2 = *b++;
|
|
if ((c1 != c2) && ((c1 ^ 32) != c2))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
b = (char*)needle->string.vector.data;
|
|
}
|
|
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
ZyanStatus ZyanStringRPos(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index)
|
|
{
|
|
if (!haystack)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringRPosEx(haystack, needle, found_index, haystack->string.vector.size - 1,
|
|
haystack->string.vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringRPosEx(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
if (!haystack || !needle || !found_index)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if ((index >= haystack->string.vector.size) || (count > index))
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (!index || !count ||
|
|
(haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
|
|
(haystack->string.vector.size < needle->string.vector.size))
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* s = (const char*)haystack->string.vector.data + index - 1;
|
|
const char* b = (const char*)needle->string.vector.data + needle->string.vector.size - 2;
|
|
for (; s >= (const char*)haystack->string.vector.data; --s)
|
|
{
|
|
if (*s != *b)
|
|
{
|
|
continue;
|
|
}
|
|
const char* a = s;
|
|
for (;;)
|
|
{
|
|
if (b < (const char*)needle->string.vector.data)
|
|
{
|
|
*found_index = (ZyanISize)(a - (const char*)haystack->string.vector.data + 1);
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
if (a < (const char*)haystack->string.vector.data + index - count)
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
if (*a-- != *b--)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
b = (char*)needle->string.vector.data + needle->string.vector.size - 2;
|
|
}
|
|
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
ZyanStatus ZyanStringRPosI(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index)
|
|
{
|
|
if (!haystack)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringRPosIEx(haystack, needle, found_index, haystack->string.vector.size - 1,
|
|
haystack->string.vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringRPosIEx(const ZyanStringView* haystack, const ZyanStringView* needle,
|
|
ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
// This solution assumes that characters are represented using ASCII representation, i.e.,
|
|
// codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
|
|
// 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.
|
|
|
|
if (!haystack || !needle || !found_index)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if ((index >= haystack->string.vector.size) || (count > index))
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (!index || !count ||
|
|
(haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
|
|
(haystack->string.vector.size < needle->string.vector.size))
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* s = (const char*)haystack->string.vector.data + index - 1;
|
|
const char* b = (const char*)needle->string.vector.data + needle->string.vector.size - 2;
|
|
for (; s >= (const char*)haystack->string.vector.data; --s)
|
|
{
|
|
if ((*s != *b) && ((*s ^ 32) != *b))
|
|
{
|
|
continue;
|
|
}
|
|
const char* a = s;
|
|
for (;;)
|
|
{
|
|
if (b < (const char*)needle->string.vector.data)
|
|
{
|
|
*found_index = (ZyanISize)(a - (const char*)haystack->string.vector.data + 1);
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
if (a < (const char*)haystack->string.vector.data + index - count)
|
|
{
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
const char c1 = *a--;
|
|
const char c2 = *b--;
|
|
if ((c1 != c2) && ((c1 ^ 32) != c2))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
b = (char*)needle->string.vector.data + needle->string.vector.size - 2;
|
|
}
|
|
|
|
*found_index = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Comparing */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringCompare(const ZyanStringView* s1, const ZyanStringView* s2, ZyanI32* result)
|
|
{
|
|
if (!s1 || !s2)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (s1->string.vector.size < s2->string.vector.size)
|
|
{
|
|
*result = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
if (s1->string.vector.size > s2->string.vector.size)
|
|
{
|
|
*result = 1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* const a = (char*)s1->string.vector.data;
|
|
const char* const b = (char*)s2->string.vector.data;
|
|
ZyanUSize i;
|
|
for (i = 0; (i + 1 < s1->string.vector.size) && (i + 1 < s2->string.vector.size); ++i)
|
|
{
|
|
if (a[i] == b[i])
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (a[i] == b[i])
|
|
{
|
|
*result = 0;
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
|
|
if ((a[i] | 32) < (b[i] | 32))
|
|
{
|
|
*result = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
*result = 1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
ZyanStatus ZyanStringCompareI(const ZyanStringView* s1, const ZyanStringView* s2, ZyanI32* result)
|
|
{
|
|
// This solution assumes that characters are represented using ASCII representation, i.e.,
|
|
// codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
|
|
// 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.
|
|
|
|
if (!s1 || !s2)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (s1->string.vector.size < s2->string.vector.size)
|
|
{
|
|
*result = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
if (s1->string.vector.size > s2->string.vector.size)
|
|
{
|
|
*result = 1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
const char* const a = (char*)s1->string.vector.data;
|
|
const char* const b = (char*)s2->string.vector.data;
|
|
ZyanUSize i;
|
|
for (i = 0; (i + 1 < s1->string.vector.size) && (i + 1 < s2->string.vector.size); ++i)
|
|
{
|
|
if ((a[i] == b[i]) || ((a[i] ^ 32) == b[i]))
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (a[i] == b[i])
|
|
{
|
|
*result = 0;
|
|
return ZYAN_STATUS_TRUE;
|
|
}
|
|
|
|
if ((a[i] | 32) < (b[i] | 32))
|
|
{
|
|
*result = -1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
*result = 1;
|
|
return ZYAN_STATUS_FALSE;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Case conversion */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringToLowerCase(ZyanString* string)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringToLowerCaseEx(string, 0, string->vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringToLowerCaseEx(ZyanString* string, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
// This solution assumes that characters are represented using ASCII representation, i.e.,
|
|
// codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
|
|
// 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.
|
|
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (index + count >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
char* s = (char*)string->vector.data + index;
|
|
for (ZyanUSize i = index; i < index + count; ++i)
|
|
{
|
|
const char c = *s;
|
|
if ((c >= 'A') && (c <= 'Z'))
|
|
{
|
|
*s = c | 32;
|
|
}
|
|
++s;
|
|
}
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringToUpperCase(ZyanString* string)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanStringToUpperCaseEx(string, 0, string->vector.size - 1);
|
|
}
|
|
|
|
ZyanStatus ZyanStringToUpperCaseEx(ZyanString* string, ZyanUSize index, ZyanUSize count)
|
|
{
|
|
// This solution assumes that characters are represented using ASCII representation, i.e.,
|
|
// codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
|
|
// 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.
|
|
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Don't allow access to the terminating '\0' character
|
|
if (index + count >= string->vector.size)
|
|
{
|
|
return ZYAN_STATUS_OUT_OF_RANGE;
|
|
}
|
|
|
|
char* s = (char*)string->vector.data + index;
|
|
for (ZyanUSize i = index; i < index + count; ++i)
|
|
{
|
|
const char c = *s;
|
|
if ((c >= 'a') && (c <= 'z'))
|
|
{
|
|
*s = c & ~32;
|
|
}
|
|
++s;
|
|
}
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Memory management */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringResize(ZyanString* string, ZyanUSize size)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
ZYAN_CHECK(ZyanVectorResize(&string->vector, size + 1));
|
|
ZYCORE_STRING_NULLTERMINATE(string);
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringReserve(ZyanString* string, ZyanUSize capacity)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanVectorReserve(&string->vector, capacity);
|
|
}
|
|
|
|
ZyanStatus ZyanStringShrinkToFit(ZyanString* string)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return ZyanVectorShrinkToFit(&string->vector);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
/* Information */
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
ZyanStatus ZyanStringGetCapacity(const ZyanString* string, ZyanUSize* capacity)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
ZYAN_ASSERT(string->vector.capacity >= 1);
|
|
*capacity = string->vector.capacity - 1;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringGetSize(const ZyanString* string, ZyanUSize* size)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
ZYAN_ASSERT(string->vector.size >= 1);
|
|
*size = string->vector.size - 1;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
ZyanStatus ZyanStringGetData(const ZyanString* string, const char** value)
|
|
{
|
|
if (!string)
|
|
{
|
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*value = string->vector.data;
|
|
|
|
return ZYAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
/* ============================================================================================== */
|