From 5b682ddcf6921590c5c4051efe665f50e7cfd468 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Wed, 7 Sep 2016 23:33:01 +1000 Subject: [PATCH] Add draft better string library implementation --- Dengine.vcxproj | 2 + Dengine.vcxproj.filters | 6 ++ src/Common.c | 2 +- src/String.c | 117 +++++++++++++++++++++++++++++++++++ src/WorldTraveller.c | 25 ++++++++ src/include/Dengine/Common.h | 2 +- src/include/Dengine/String.h | 17 +++++ 7 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 src/String.c create mode 100644 src/include/Dengine/String.h diff --git a/Dengine.vcxproj b/Dengine.vcxproj index 417bcf5..0275641 100644 --- a/Dengine.vcxproj +++ b/Dengine.vcxproj @@ -129,6 +129,7 @@ + @@ -153,6 +154,7 @@ + diff --git a/Dengine.vcxproj.filters b/Dengine.vcxproj.filters index ca60c51..36ba4a9 100644 --- a/Dengine.vcxproj.filters +++ b/Dengine.vcxproj.filters @@ -51,6 +51,9 @@ Source Files + + Source Files + @@ -107,5 +110,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/src/Common.c b/src/Common.c index f0e274f..08c230a 100644 --- a/src/Common.c +++ b/src/Common.c @@ -39,7 +39,7 @@ char *common_strncpy(char *dest, const char *src, i32 numChars) return dest; } -char *common_memset(char *const ptr, const i32 value, const i32 numBytes) +u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes) { for (i32 i = 0; i < numBytes; i++) ptr[i] = value; diff --git a/src/String.c b/src/String.c new file mode 100644 index 0000000..0161f5d --- /dev/null +++ b/src/String.c @@ -0,0 +1,117 @@ +#include "Dengine/String.h" +#include "Dengine/Platform.h" + +/* + * +-------------------------------------+ + * | Header | C-String | Null Terminator | + * +-------------------------------------+ + * | + * +--> Functions return the c-string for compatibility with other + * string libraries + * + * Headers are retrieved using pointer arithmetric from the C string. These + * strings are typechecked by their own typedef char String. + */ + +typedef struct StringHeader +{ + i32 len; + + // NOTE(doyle): A string is stored as one contiguous chunk of memory. We + // don't use a pointer for storing the string as this'd require an extra + // 4 bytes to store the pointer, which we don't need if everything is + // contiguous. The string follows on from the len, and we return the address + // of the string to simulate a pointer. + String string; +} StringHeader; + +// TODO(doyle): string capacity- append if already enough space +INTERNAL StringHeader *string_getHeader(String *const string) +{ + StringHeader *result = NULL; + + // NOTE(doyle): C-String must be located at end of struct type for offset to + // be correct! We cannot just subtract the string-header since we start at + // the string ptr position + if (string) + { + i32 byteOffsetToHeader = sizeof(StringHeader) - sizeof(String *); + result = CAST(StringHeader *)((CAST(u8 *) string) - byteOffsetToHeader); + } + + return result; +} + +i32 string_len(String *const string) +{ + if (!string) return -1; + + StringHeader *header = string_getHeader(string); + i32 result = header->len; + return result; +} + +String *const string_append(MemoryArena *const arena, String *oldString, + char *appendString, i32 appendLen) + +{ + if (!oldString || !appendString || !arena) return oldString; + + /* Calculate size of new string */ + StringHeader *oldHeader = string_getHeader(oldString); + i32 newLen = oldHeader->len + appendLen; + String *newString = string_makeLen(arena, newLen); + + /* Append strings together */ + String *insertPtr = newString; + common_strncpy(insertPtr, oldString, oldHeader->len); + insertPtr += oldHeader->len; + common_strncpy(insertPtr, appendString, appendLen); + + /* Free old string */ + string_free(arena, oldString); + + return newString; +} + +void string_free(MemoryArena *arena, String *string) +{ + if (!string || !arena) return; + + StringHeader *header = string_getHeader(string); + i32 bytesToFree = sizeof(StringHeader) + header->len; + + common_memset((u8 *)header, 0, bytesToFree); + PLATFORM_MEM_FREE(arena, header, bytesToFree); + string = NULL; +} + +String *const string_make(MemoryArena *const arena, char *string) +{ + if (!arena) return NULL; + + i32 len = common_strlen(string); + String *result = string_makeLen(arena, len); + common_strncpy(result, string, len); + + return result; +} + +String *const string_makeLen(MemoryArena *const arena, i32 len) +{ + if (!arena) return NULL; + + // NOTE(doyle): Allocate the string header size plus the len. But _note_ + // that StringHeader contains a single String character. This has + // a side-effect of already preallocating a byte for the null-terminating + // character. Whilst the len of a string counts up to the last character + // _not_ including null-terminator. + i32 bytesToAllocate = sizeof(StringHeader) + len; + void *chunk = PLATFORM_MEM_ALLOC(arena, bytesToAllocate, u8); + if (!chunk) return NULL; + + StringHeader *header = CAST(StringHeader *) chunk; + header->len = len; + return &header->string; +} + diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 4a06460..5a6f2da 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -3,6 +3,7 @@ #include "Dengine/Debug.h" #include "Dengine/Entity.h" #include "Dengine/Platform.h" +#include "Dengine/String.h" #include "Dengine/UserInterface.h" enum State @@ -575,6 +576,30 @@ INTERNAL void unitTest(MemoryArena *arena) ASSERT(common_atoi("+32", common_strlen("+32")) == 32); ASSERT(common_atoi("+ 32", common_strlen("+ 32")) == 0); asset_unitTest(arena); + + i32 memBefore = arena->bytesAllocated; + String *hello = string_make(arena, "hello, "); + String *world = string_make(arena, "world"); + ASSERT(string_len(hello) == 7); + ASSERT(string_len(world) == 5); + + hello = string_append(arena, hello, world, string_len(world)); + ASSERT(string_len(hello) == 12); + string_free(arena, hello); + string_free(arena, world); + + hello = string_make(arena, ""); + world = string_make(arena, ""); + hello = string_append(arena, hello, world, string_len(world)); + ASSERT(string_len(hello) == 0); + ASSERT(string_len(world) == 0); + + string_free(arena, hello); + string_free(arena, world); + + i32 memAfter = arena->bytesAllocated; + + ASSERT(memBefore == memAfter); } // TODO(doyle): Remove and implement own random generator! diff --git a/src/include/Dengine/Common.h b/src/include/Dengine/Common.h index 399d9ef..141e26c 100644 --- a/src/include/Dengine/Common.h +++ b/src/include/Dengine/Common.h @@ -32,7 +32,7 @@ i32 common_strlen(const char *const string); i32 common_strcmp(const char *a, const char *b); void common_strncat(char *dest, const char *src, i32 numChars); char *common_strncpy(char *dest, const char *src, i32 numChars); -char *common_memset(char *const ptr, const i32 value, const i32 numBytes); +u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes); // Max buffer size should be 11 for 32 bit integers void common_itoa(i32 value, char *buf, i32 bufSize); diff --git a/src/include/Dengine/String.h b/src/include/Dengine/String.h new file mode 100644 index 0000000..07f1c17 --- /dev/null +++ b/src/include/Dengine/String.h @@ -0,0 +1,17 @@ +#ifndef DENGINE_STRING_H +#define DENGINE_STRING_H + +#include "Dengine/Common.h" + +typedef struct MemoryArena MemoryArena; + +typedef char String; + +i32 string_len(String *const string); +String *const string_append(MemoryArena *const arena, String *oldString, + String *appendString, i32 appendLen); +void string_free(MemoryArena *arena, String *string); +String *const string_make(MemoryArena *const arena, char *string); +String *const string_makeLen(MemoryArena *const arena, i32 len); + +#endif