From a428b015319e51f6c77e6f4b068591595ee9d80d Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Mon, 13 Apr 2026 18:06:33 +1000 Subject: [PATCH] wrote string library --- libs/string/string.c | 452 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 libs/string/string.c diff --git a/libs/string/string.c b/libs/string/string.c new file mode 100644 index 0000000..f2fab4b --- /dev/null +++ b/libs/string/string.c @@ -0,0 +1,452 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GroundValue stringUpper(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + char* uppercase = malloc(sizeof(char) * (strlen(string) + 1)); + if (!uppercase) + ERROR("Failed to allocate memory while getting uppercase of string!", "AllocFail"); + + for (size_t i = 0; i < strlen(string); i++) { + uppercase[i] = toupper(string[i]); + } + + return groundCreateValue(STRING, uppercase); +} + +GroundValue stringLower(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + char* lowercase = malloc(sizeof(char) * (strlen(string) + 1)); + if (!lowercase) + ERROR("Failed to allocate memory while getting uppercase of string!", "AllocFail"); + + for (size_t i = 0; i < strlen(string); i++) { + lowercase[i] = tolower(string[i]); + } + + return groundCreateValue(STRING, lowercase); +} + +GroundValue stringStartsWith(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + char* prefix = args.values[1].data.stringVal; + + return groundCreateValue(BOOL, strncmp(prefix, string, strlen(prefix)) == 0); +} + +GroundValue stringEndsWith(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + char* suffix = args.values[1].data.stringVal; + + size_t strLen = strlen(string); + size_t suffixLen = strlen(suffix); + if (suffixLen > strLen) return groundCreateValue(BOOL, false); // can't start with suffix if suffix is longer + + return groundCreateValue( + BOOL, + strcmp(string + strLen - suffixLen, suffix) == 0 + ); +} + +GroundValue stringSubstring(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + int64_t start = args.values[1].data.intVal; + int64_t end = args.values[2].data.intVal; + + if (end < start) { + ERROR("End can't be less than start when getting substring!", "EndBeforeStart"); + } + + int64_t inputLen = strlen(string); + + if (start >= inputLen) + ERROR("Start is outside string!", "OutOfBounds"); + else if (end >= inputLen) + ERROR("End is outside string!", "OutOfBounds"); + + char* buffer = malloc(inputLen + 1); + if (!buffer) + ERROR("Failed to allocate memory while getting substring of string!", "AllocFail"); + + // string_Substring("Hello!", 1, 3) -> "ell" + // amount = 3 - 1 + 1 = 3 + strncpy(buffer, string + start, end - start + 1); + buffer[end - start + 1] = 0; + + return groundCreateValue(STRING, buffer); +} + +GroundValue stringCharAt(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + int64_t index = args.values[1].data.intVal; + + if (index < 0) + ERROR("Index can't be less than 0!", "OutOfBounds"); + + int64_t stringLen = strlen(string); + if (index >= stringLen) { + char buffer[256]; + sprintf(buffer, "Attempt to get char at index %ld when string is of length %ld.", index, stringLen); + ERROR(buffer, "OutOfBounds"); + } + + char buffer[2] = {string[index], 0}; + return groundCreateValue(STRING, buffer); +} + +GroundValue stringFind(GroundScope* scope, List args) { + char* haystack = args.values[0].data.stringVal; + char* needle = args.values[1].data.stringVal; + + char* result = strstr(haystack, needle); + if (!result) + return groundCreateValue(INT, (int64_t)-1); + + return groundCreateValue(INT, result - haystack); +} + +GroundValue stringFindLast(GroundScope* scope, List args) { + char* haystack = args.values[0].data.stringVal; + char* needle = args.values[1].data.stringVal; + + if (!*needle) { + return groundCreateValue(INT, (int64_t)strlen(haystack)-1); + } + + const char* p = haystack; + while (*p) p++; + + const char* q = needle; + while (*q) q++; + + bool found = false; + + while (!found && (p - haystack) >= (q - needle)) { + const char* s = p; + const char* t = q; + + while (t != needle && s != haystack && *(s - 1) == *(t - 1)) { + s--; + t--; + } + + found = (t == needle); + + if (found) p = s; + else p--; + } + + return groundCreateValue(INT, (int64_t)(found ? p - haystack : -1)); +} + +GroundValue stringContains(GroundScope* scope, List args) { + char* haystack = args.values[0].data.stringVal; + char* needle = args.values[1].data.stringVal; + + char* result = strstr(haystack, needle); + return groundCreateValue(BOOL, result != NULL); +} + +GroundValue stringCount(GroundScope* scope, List args) { + char* haystack = args.values[0].data.stringVal; + char* needle = args.values[1].data.stringVal; + + int64_t count = 0; + const char* tmp = haystack; + while ((tmp = strstr(tmp, needle))) { + count++; + tmp++; + } + + return groundCreateValue(INT, count); +} + +GroundValue stringTrim(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + + // trip whitespace from start + char* start = string; + while (*start && isspace((unsigned char)*start)) start++; + + // trim whitespace from end + char* end = string + strlen(string); + while (end > start && isspace((unsigned char)*(end - 1))) end--; + + size_t len = end - start; + + char* buffer = malloc(len + 1); + if (!buffer) + ERROR("Failed to allocate memory while trimming string!", "AllocFail"); + + memcpy(buffer, start, len); + buffer[len] = 0; + + return groundCreateValue(STRING, buffer); +} + +GroundValue stringTrimLeft(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + + // trip whitespace from start + char* start = string; + while (*start && isspace((unsigned char)*start)) start++; + + size_t len = strlen(start); + + char* buffer = malloc(len + 1); + if (!buffer) + ERROR("Failed to allocate memory while trimming string!", "AllocFail"); + + memcpy(buffer, start, len); + buffer[len] = 0; + + return groundCreateValue(STRING, buffer); +} + +GroundValue stringTrimRight(GroundScope* scope, List args) { + char* str = args.values[0].data.stringVal; + + char* end = str + strlen(str); + while (end > str && isspace((unsigned char)*(end - 1))) { + end--; + } + + size_t len = end - str; + + char* buffer = malloc(len + 1); + if (!buffer) + ERROR("Failed to allocate memory while trimming string!", "AllocFail"); + memcpy(buffer, str, len); + buffer[len] = '\0'; + + return groundCreateValue(STRING, buffer); +} + +GroundValue stringReplace(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + char* from = args.values[1].data.stringVal; + char* to = args.values[2].data.stringVal; + + // === THANK YOU TO https://creativeandcritical.net FOR THEIR repl_str() FUNCTION === \\ + /* Adjust each of the below values to suit your needs. */ + + /* Increment positions cache size initially by this number. */ + size_t cache_sz_inc = 16; + /* Thereafter, each time capacity needs to be increased, + * multiply the increment by this factor. */ + const size_t cache_sz_inc_factor = 3; + /* But never increment capacity by more than this number. */ + const size_t cache_sz_inc_max = 1048576; + + char *pret, *ret = NULL; + const char *pstr2, *pstr = string; + size_t i, count = 0; + #if (__STDC_VERSION__ >= 199901L) + uintptr_t *pos_cache_tmp, *pos_cache = NULL; + #else + ptrdiff_t *pos_cache_tmp, *pos_cache = NULL; + #endif + size_t cache_sz = 0; + size_t cpylen, orglen, retlen, tolen, fromlen = strlen(from); + + /* Find all matches and cache their positions. */ + while ((pstr2 = strstr(pstr, from)) != NULL) { + count++; + + /* Increase the cache size when necessary. */ + if (cache_sz < count) { + cache_sz += cache_sz_inc; + pos_cache_tmp = realloc(pos_cache, sizeof(*pos_cache) * cache_sz); + if (pos_cache_tmp == NULL) { + goto end_repl_str; + } else pos_cache = pos_cache_tmp; + cache_sz_inc *= cache_sz_inc_factor; + if (cache_sz_inc > cache_sz_inc_max) { + cache_sz_inc = cache_sz_inc_max; + } + } + + pos_cache[count-1] = pstr2 - string; + pstr = pstr2 + fromlen; + } + + orglen = pstr - string + strlen(pstr); + + /* Allocate memory for the post-replacement string. */ + if (count > 0) { + tolen = strlen(to); + retlen = orglen + (tolen - fromlen) * count; + } else retlen = orglen; + ret = malloc(retlen + 1); + if (ret == NULL) { + goto end_repl_str; + } + + if (count == 0) { + /* If no matches, then just duplicate the string. */ + strcpy(ret, string); + } else { + /* Otherwise, duplicate the string whilst performing + * the replacements using the position cache. */ + pret = ret; + memcpy(pret, string, pos_cache[0]); + pret += pos_cache[0]; + for (i = 0; i < count; i++) { + memcpy(pret, to, tolen); + pret += tolen; + pstr = string + pos_cache[i] + fromlen; + cpylen = (i == count-1 ? orglen : pos_cache[i+1]) - pos_cache[i] - fromlen; + memcpy(pret, pstr, cpylen); + pret += cpylen; + } + ret[retlen] = '\0'; + } + +end_repl_str: + /* Free the cache and return the post-replacement string, + * which will be NULL in the event of an error. */ + free(pos_cache); + return groundCreateValue(STRING, ret); +} + +GroundValue stringRepeat(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + int64_t times = args.values[1].data.intVal; + + if (times < 0) + ERROR("Can't repeat string negative number of times!", "ValueError"); + + char* out = malloc(strlen(string) * times + 1); + if (!out) + ERROR("Failed to allocate memory while repeating string!", "AllocFail"); + + strcpy(out, string); + while (--times > 0) { + strcat(out, string); + } + + return groundCreateValue(STRING, out); +} + +GroundValue stringReverse(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + + size_t stringLen = strlen(string); + char* out = malloc(stringLen + 1); + if (!out) + ERROR("Failed to allocate memory while reversing string!", "AllocFail"); + + unsigned int i = 0; + while (stringLen > 0) out[i++] = string[stringLen-- - 1]; + out[i] = 0; + + return groundCreateValue(STRING, out); +} + +GroundValue stringIsAlpha(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + const char* p = string; + + bool isAlpha = true; + + while (*p) { + if (!isalpha(*p)) { + isAlpha = false; + break; + } + p++; + } + + return groundCreateValue(BOOL, isAlpha); +} + +GroundValue stringIsDigit(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + const char* p = string; + + bool isDigit = true; + + while (*p) { + if (!isdigit(*p)) { + isDigit = false; + break; + } + p++; + } + + return groundCreateValue(BOOL, isDigit); +} + +GroundValue stringIsAlnum(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + const char* p = string; + + bool isAlnum = true; + + while (*p) { + if (!isalnum(*p)) { + isAlnum = false; + break; + } + p++; + } + + return groundCreateValue(BOOL, isAlnum); +} + +GroundValue stringIsSpace(GroundScope* scope, List args) { + char* string = args.values[0].data.stringVal; + const char* p = string; + + bool isSpace = true; + + while (*p) { + if (!isspace(*p)) { + isSpace = false; + break; + } + p++; + } + + return groundCreateValue(BOOL, isSpace); +} + +void ground_init(GroundScope* scope) { + // modify string + groundAddNativeFunction(scope, "string_Upper", stringUpper, STRING, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_Lower", stringLower, STRING, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_Trim", stringTrim, STRING, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_TrimLeft", stringTrimLeft, STRING, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_TrimRight", stringTrimRight, STRING, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_Substring", stringSubstring, STRING, 3, STRING, "str", INT, "start", INT, "end"); + groundAddNativeFunction(scope, "string_Replace", stringReplace, STRING, 3, STRING, "str", STRING, "from", STRING, "to"); + groundAddNativeFunction(scope, "string_Repeat", stringRepeat, STRING, 2, STRING, "str", INT, "times"); + groundAddNativeFunction(scope, "string_Reverse", stringReverse, STRING, 1, STRING, "str"); + + // check for substring + groundAddNativeFunction(scope, "string_StartsWith", stringStartsWith, BOOL, 2, STRING, "str", STRING, "prefix"); + groundAddNativeFunction(scope, "string_EndsWith", stringEndsWith, BOOL, 2, STRING, "str", STRING, "suffix"); + groundAddNativeFunction(scope, "string_Contains", stringContains, BOOL, 2, STRING, "haystack", STRING, "needle"); + + // character classification + groundAddNativeFunction(scope, "string_IsAlpha", stringIsAlpha, BOOL, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_IsDigit", stringIsDigit, BOOL, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_IsAlnum", stringIsAlnum, BOOL, 1, STRING, "str"); + groundAddNativeFunction(scope, "string_IsSpace", stringIsSpace, BOOL, 1, STRING, "str"); + + // get char + groundAddNativeFunction(scope, "string_CharAt", stringCharAt, STRING, 2, STRING, "str", INT, "index"); + + // find index substring + groundAddNativeFunction(scope, "string_Find", stringFind, INT, 2, STRING, "haystack", STRING, "needle"); + groundAddNativeFunction(scope, "string_FindLast", stringFindLast, INT, 2, STRING, "haystack", STRING, "needle"); + groundAddNativeFunction(scope, "string_Count", stringCount, INT, 2, STRING, "haystack", STRING, "needle"); + +} \ No newline at end of file