forked from ground/ground
455 lines
14 KiB
C
455 lines
14 KiB
C
#include <ctype.h>
|
|
#include <groundext.h>
|
|
#include <groundvm.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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");
|
|
}
|
|
|
|
size_t inputLen = strlen(string);
|
|
|
|
if (start >= inputLen) {
|
|
ERROR("Start is outside string!", "OutOfBounds");
|
|
} else if (end >= inputLen || end < 0) {
|
|
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");
|
|
|
|
} |