2025-07-29 15:15:00 +02:00
|
|
|
// string_backend.c
|
|
|
|
#include "wren.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2025-08-20 22:17:34 +02:00
|
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
2025-07-29 15:15:00 +02:00
|
|
|
|
|
|
|
// Helper to validate that the value in a slot is a string.
|
|
|
|
static bool validateString(WrenVM* vm, int slot, const char* name) {
|
|
|
|
if (wrenGetSlotType(vm, slot) == WREN_TYPE_STRING) return true;
|
|
|
|
wrenSetSlotString(vm, 0, "Argument must be a string.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-08-20 22:17:34 +02:00
|
|
|
// Helper to validate that the value in a slot is a number.
|
|
|
|
static bool validateNumber(WrenVM* vm, int slot, const char* name) {
|
|
|
|
if (wrenGetSlotType(vm, slot) == WREN_TYPE_NUM) return true;
|
|
|
|
wrenSetSlotString(vm, 0, "Argument must be a number.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-07-29 15:15:00 +02:00
|
|
|
// Implements String.endsWith(_).
|
|
|
|
void stringEndsWith(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Suffix")) return;
|
|
|
|
|
|
|
|
int stringLength, suffixLength;
|
|
|
|
const char* string = wrenGetSlotBytes(vm, 0, &stringLength);
|
|
|
|
const char* suffix = wrenGetSlotBytes(vm, 1, &suffixLength);
|
|
|
|
|
|
|
|
if (suffixLength > stringLength) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, memcmp(string + stringLength - suffixLength, suffix, suffixLength) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.startsWith(_).
|
|
|
|
void stringStartsWith(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Prefix")) return;
|
|
|
|
|
|
|
|
int stringLength, prefixLength;
|
|
|
|
const char* string = wrenGetSlotBytes(vm, 0, &stringLength);
|
|
|
|
const char* prefix = wrenGetSlotBytes(vm, 1, &prefixLength);
|
|
|
|
|
|
|
|
if (prefixLength > stringLength) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, memcmp(string, prefix, prefixLength) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.replace(_, _).
|
|
|
|
void stringReplace(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "From")) return;
|
|
|
|
if (!validateString(vm, 2, "To")) return;
|
|
|
|
|
|
|
|
int haystackLen, fromLen, toLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* from = wrenGetSlotBytes(vm, 1, &fromLen);
|
|
|
|
const char* to = wrenGetSlotBytes(vm, 2, &toLen);
|
|
|
|
|
|
|
|
if (fromLen == 0) {
|
2025-08-20 22:17:34 +02:00
|
|
|
wrenSetSlotString(vm, 0, haystack);
|
2025-07-29 15:15:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t resultCapacity = haystackLen * (toLen > fromLen ? toLen / fromLen + 1 : 1) + 1;
|
|
|
|
char* result = (char*)malloc(resultCapacity);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* dest = result;
|
|
|
|
const char* p = haystack;
|
|
|
|
const char* end = haystack + haystackLen;
|
|
|
|
|
|
|
|
while (p < end) {
|
|
|
|
const char* found = strstr(p, from);
|
|
|
|
if (found) {
|
|
|
|
size_t len = found - p;
|
|
|
|
memcpy(dest, p, len);
|
|
|
|
dest += len;
|
|
|
|
memcpy(dest, to, toLen);
|
|
|
|
dest += toLen;
|
|
|
|
p = found + fromLen;
|
|
|
|
} else {
|
|
|
|
size_t len = end - p;
|
|
|
|
memcpy(dest, p, len);
|
|
|
|
dest += len;
|
|
|
|
p = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*dest = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.split(_).
|
|
|
|
void stringSplit(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Delimiter")) return;
|
|
|
|
|
|
|
|
int haystackLen, delimLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* delim = wrenGetSlotBytes(vm, 1, &delimLen);
|
|
|
|
|
|
|
|
if (delimLen == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, "Delimiter cannot be empty.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-08-20 22:17:34 +02:00
|
|
|
wrenSetSlotNewList(vm, 0);
|
2025-07-29 15:15:00 +02:00
|
|
|
|
|
|
|
const char* p = haystack;
|
|
|
|
const char* end = haystack + haystackLen;
|
|
|
|
while (p < end) {
|
|
|
|
const char* found = strstr(p, delim);
|
|
|
|
if (found) {
|
|
|
|
wrenSetSlotBytes(vm, 1, p, found - p);
|
|
|
|
wrenInsertInList(vm, 0, -1, 1);
|
|
|
|
p = found + delimLen;
|
|
|
|
} else {
|
|
|
|
wrenSetSlotBytes(vm, 1, p, end - p);
|
|
|
|
wrenInsertInList(vm, 0, -1, 1);
|
|
|
|
p = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (haystackLen > 0 && (haystackLen >= delimLen) &&
|
2025-08-20 22:17:34 +02:00
|
|
|
memcmp(haystack + haystackLen - delimLen, delim, delimLen) == 0) {
|
2025-07-29 15:15:00 +02:00
|
|
|
wrenSetSlotBytes(vm, 1, "", 0);
|
|
|
|
wrenInsertInList(vm, 0, -1, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-20 22:17:34 +02:00
|
|
|
// Implements String.toUpper().
|
|
|
|
void stringToUpper(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
result[i] = toupper((unsigned char)str[i]);
|
|
|
|
}
|
|
|
|
result[len] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toLower().
|
|
|
|
void stringToLower(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
result[i] = tolower((unsigned char)str[i]);
|
|
|
|
}
|
|
|
|
result[len] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.trim().
|
|
|
|
void stringTrim(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
int start = 0;
|
|
|
|
int end = len - 1;
|
|
|
|
|
|
|
|
while (start < len && isspace((unsigned char)str[start])) start++;
|
|
|
|
while (end >= 0 && isspace((unsigned char)str[end])) end--;
|
|
|
|
|
|
|
|
if (start > end) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBytes(vm, 0, str + start, end - start + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.trimStart().
|
|
|
|
void stringTrimStart(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
int start = 0;
|
|
|
|
while (start < len && isspace((unsigned char)str[start])) start++;
|
|
|
|
|
|
|
|
wrenSetSlotBytes(vm, 0, str + start, len - start);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.trimEnd().
|
|
|
|
void stringTrimEnd(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
int end = len - 1;
|
|
|
|
while (end >= 0 && isspace((unsigned char)str[end])) end--;
|
|
|
|
|
|
|
|
wrenSetSlotBytes(vm, 0, str, end + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.count(_).
|
|
|
|
void stringCount(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Substring")) return;
|
|
|
|
|
|
|
|
int haystackLen, needleLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* needle = wrenGetSlotBytes(vm, 1, &needleLen);
|
|
|
|
|
|
|
|
if (needleLen == 0) {
|
|
|
|
wrenSetSlotDouble(vm, 0, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
const char* p = haystack;
|
|
|
|
const char* end = haystack + haystackLen;
|
|
|
|
|
|
|
|
while (p < end) {
|
|
|
|
const char* found = strstr(p, needle);
|
|
|
|
if (found) {
|
|
|
|
count++;
|
|
|
|
p = found + needleLen;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotDouble(vm, 0, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.indexOf(_).
|
|
|
|
void stringIndexOf(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Substring")) return;
|
|
|
|
|
|
|
|
int haystackLen, needleLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* needle = wrenGetSlotBytes(vm, 1, &needleLen);
|
|
|
|
|
|
|
|
if (needleLen == 0) {
|
|
|
|
wrenSetSlotDouble(vm, 0, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* found = strstr(haystack, needle);
|
|
|
|
if (found) {
|
|
|
|
wrenSetSlotDouble(vm, 0, found - haystack);
|
|
|
|
} else {
|
|
|
|
wrenSetSlotDouble(vm, 0, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.lastIndexOf(_).
|
|
|
|
void stringLastIndexOf(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Substring")) return;
|
|
|
|
|
|
|
|
int haystackLen, needleLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* needle = wrenGetSlotBytes(vm, 1, &needleLen);
|
|
|
|
|
|
|
|
if (needleLen == 0 || needleLen > haystackLen) {
|
|
|
|
wrenSetSlotDouble(vm, 0, -1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* lastFound = NULL;
|
|
|
|
const char* p = haystack;
|
|
|
|
|
|
|
|
while ((p = strstr(p, needle)) != NULL) {
|
|
|
|
lastFound = p;
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastFound) {
|
|
|
|
wrenSetSlotDouble(vm, 0, lastFound - haystack);
|
|
|
|
} else {
|
|
|
|
wrenSetSlotDouble(vm, 0, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.contains(_).
|
|
|
|
void stringContains(WrenVM* vm) {
|
|
|
|
if (!validateString(vm, 1, "Substring")) return;
|
|
|
|
|
|
|
|
int haystackLen, needleLen;
|
|
|
|
const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen);
|
|
|
|
const char* needle = wrenGetSlotBytes(vm, 1, &needleLen);
|
|
|
|
|
|
|
|
if (needleLen == 0) {
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, strstr(haystack, needle) != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toInt().
|
|
|
|
void stringToInt(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* endptr;
|
|
|
|
errno = 0;
|
|
|
|
long value = strtol(str, &endptr, 10);
|
|
|
|
|
|
|
|
if (errno != 0 || endptr == str || *endptr != '\0') {
|
|
|
|
wrenSetSlotNull(vm, 0);
|
|
|
|
} else {
|
|
|
|
wrenSetSlotDouble(vm, 0, (double)value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toNum().
|
|
|
|
void stringToNum(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* endptr;
|
|
|
|
errno = 0;
|
|
|
|
double value = strtod(str, &endptr);
|
|
|
|
|
|
|
|
if (errno != 0 || endptr == str || *endptr != '\0') {
|
|
|
|
wrenSetSlotNull(vm, 0);
|
|
|
|
} else {
|
|
|
|
wrenSetSlotDouble(vm, 0, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.reverse().
|
|
|
|
void stringReverse(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
result[i] = str[len - 1 - i];
|
|
|
|
}
|
|
|
|
result[len] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.repeat(_).
|
|
|
|
void stringRepeat(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Count")) return;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
int count = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
|
|
|
|
if (count < 0) {
|
|
|
|
wrenSetSlotString(vm, 0, "Count must be non-negative.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count == 0 || len == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t resultLen = len * count;
|
|
|
|
char* result = (char*)malloc(resultLen + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
memcpy(result + (i * len), str, len);
|
|
|
|
}
|
|
|
|
result[resultLen] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.padStart(_, _).
|
|
|
|
void stringPadStart(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Length")) return;
|
|
|
|
if (!validateString(vm, 2, "PadString")) return;
|
|
|
|
|
|
|
|
int strLen;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &strLen);
|
|
|
|
int targetLen = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
int padLen;
|
|
|
|
const char* padStr = wrenGetSlotBytes(vm, 2, &padLen);
|
|
|
|
|
|
|
|
if (targetLen <= strLen || padLen == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, str);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int padNeeded = targetLen - strLen;
|
|
|
|
char* result = (char*)malloc(targetLen + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
while (pos < padNeeded) {
|
|
|
|
int copyLen = (padNeeded - pos < padLen) ? (padNeeded - pos) : padLen;
|
|
|
|
memcpy(result + pos, padStr, copyLen);
|
|
|
|
pos += copyLen;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(result + padNeeded, str, strLen);
|
|
|
|
result[targetLen] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.padEnd(_, _).
|
|
|
|
void stringPadEnd(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Length")) return;
|
|
|
|
if (!validateString(vm, 2, "PadString")) return;
|
|
|
|
|
|
|
|
int strLen;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &strLen);
|
|
|
|
int targetLen = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
int padLen;
|
|
|
|
const char* padStr = wrenGetSlotBytes(vm, 2, &padLen);
|
|
|
|
|
|
|
|
if (targetLen <= strLen || padLen == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, str);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int padNeeded = targetLen - strLen;
|
|
|
|
char* result = (char*)malloc(targetLen + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(result, str, strLen);
|
|
|
|
|
|
|
|
int pos = strLen;
|
|
|
|
while (pos < targetLen) {
|
|
|
|
int copyLen = (targetLen - pos < padLen) ? (targetLen - pos) : padLen;
|
|
|
|
memcpy(result + pos, padStr, copyLen);
|
|
|
|
pos += copyLen;
|
|
|
|
}
|
|
|
|
|
|
|
|
result[targetLen] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.charAt(_).
|
|
|
|
void stringCharAt(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Index")) return;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
int index = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
|
|
|
|
if (index < 0 || index >= len) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char result[2] = { str[index], '\0' };
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.charCodeAt(_).
|
|
|
|
void stringCharCodeAt(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Index")) return;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
int index = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
|
|
|
|
if (index < 0 || index >= len) {
|
|
|
|
wrenSetSlotNull(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotDouble(vm, 0, (unsigned char)str[index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.isNumeric().
|
|
|
|
void stringIsNumeric(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (!isdigit((unsigned char)str[i])) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.isAlpha().
|
|
|
|
void stringIsAlpha(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (!isalpha((unsigned char)str[i])) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.isAlphaNumeric().
|
|
|
|
void stringIsAlphaNumeric(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (!isalnum((unsigned char)str[i])) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.isEmpty().
|
|
|
|
void stringIsEmpty(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
wrenSetSlotBool(vm, 0, len == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.isWhitespace().
|
|
|
|
void stringIsWhitespace(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (!isspace((unsigned char)str[i])) {
|
|
|
|
wrenSetSlotBool(vm, 0, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBool(vm, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.capitalize().
|
|
|
|
void stringCapitalize(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
result[0] = toupper((unsigned char)str[0]);
|
|
|
|
for (int i = 1; i < len; i++) {
|
|
|
|
result[i] = tolower((unsigned char)str[i]);
|
|
|
|
}
|
|
|
|
result[len] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toCamelCase().
|
|
|
|
void stringToCamelCase(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
bool capitalizeNext = false;
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (str[i] == '_' || str[i] == '-' || isspace((unsigned char)str[i])) {
|
|
|
|
capitalizeNext = true;
|
|
|
|
} else {
|
|
|
|
if (capitalizeNext && j > 0) {
|
|
|
|
result[j++] = toupper((unsigned char)str[i]);
|
|
|
|
capitalizeNext = false;
|
|
|
|
} else {
|
|
|
|
result[j++] = (j == 0) ? tolower((unsigned char)str[i]) : str[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result[j] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toSnakeCase().
|
|
|
|
void stringToSnakeCase(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
// Allocate extra space for underscores
|
|
|
|
char* result = (char*)malloc(len * 2 + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (isupper((unsigned char)str[i]) && i > 0 && !isupper((unsigned char)str[i-1])) {
|
|
|
|
result[j++] = '_';
|
|
|
|
}
|
|
|
|
if (str[i] == '-' || isspace((unsigned char)str[i])) {
|
|
|
|
result[j++] = '_';
|
|
|
|
} else {
|
|
|
|
result[j++] = tolower((unsigned char)str[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result[j] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toKebabCase().
|
|
|
|
void stringToKebabCase(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
// Allocate extra space for hyphens
|
|
|
|
char* result = (char*)malloc(len * 2 + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (isupper((unsigned char)str[i]) && i > 0 && !isupper((unsigned char)str[i-1])) {
|
|
|
|
result[j++] = '-';
|
|
|
|
}
|
|
|
|
if (str[i] == '_' || isspace((unsigned char)str[i])) {
|
|
|
|
result[j++] = '-';
|
|
|
|
} else {
|
|
|
|
result[j++] = tolower((unsigned char)str[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result[j] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toPascalCase().
|
|
|
|
void stringToPascalCase(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
bool capitalizeNext = true;
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (str[i] == '_' || str[i] == '-' || isspace((unsigned char)str[i])) {
|
|
|
|
capitalizeNext = true;
|
|
|
|
} else {
|
|
|
|
if (capitalizeNext) {
|
|
|
|
result[j++] = toupper((unsigned char)str[i]);
|
|
|
|
capitalizeNext = false;
|
|
|
|
} else {
|
|
|
|
result[j++] = str[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result[j] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.swapCase().
|
|
|
|
void stringSwapCase(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
char* result = (char*)malloc(len + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (isupper((unsigned char)str[i])) {
|
|
|
|
result[i] = tolower((unsigned char)str[i]);
|
|
|
|
} else if (islower((unsigned char)str[i])) {
|
|
|
|
result[i] = toupper((unsigned char)str[i]);
|
|
|
|
} else {
|
|
|
|
result[i] = str[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result[len] = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.substring(_, _).
|
|
|
|
void stringSubstring(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Start")) return;
|
|
|
|
if (!validateNumber(vm, 2, "End")) return;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
int start = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
int end = (int)wrenGetSlotDouble(vm, 2);
|
|
|
|
|
|
|
|
// Handle negative indices
|
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end < 0) end = 0;
|
|
|
|
if (start > len) start = len;
|
|
|
|
if (end > len) end = len;
|
|
|
|
|
|
|
|
// Swap if start > end
|
|
|
|
if (start > end) {
|
|
|
|
int temp = start;
|
|
|
|
start = end;
|
|
|
|
end = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBytes(vm, 0, str + start, end - start);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.slice(_, _).
|
|
|
|
void stringSlice(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "Start")) return;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
int start = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
int end = len;
|
|
|
|
|
|
|
|
// Check if end parameter is provided
|
|
|
|
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_NUM) {
|
|
|
|
end = (int)wrenGetSlotDouble(vm, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle negative indices
|
|
|
|
if (start < 0) start = len + start;
|
|
|
|
if (end < 0) end = len + end;
|
|
|
|
|
|
|
|
// Clamp values
|
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end < 0) end = 0;
|
|
|
|
if (start > len) start = len;
|
|
|
|
if (end > len) end = len;
|
|
|
|
|
|
|
|
if (start >= end) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenSetSlotBytes(vm, 0, str + start, end - start);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.toBytes().
|
|
|
|
void stringToBytes(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
|
|
|
|
wrenSetSlotNewList(vm, 0);
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
wrenSetSlotDouble(vm, 1, (unsigned char)str[i]);
|
|
|
|
wrenInsertInList(vm, 0, -1, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.fromCharCode(_).
|
|
|
|
void stringFromCharCode(WrenVM* vm) {
|
|
|
|
if (!validateNumber(vm, 1, "CharCode")) return;
|
|
|
|
|
|
|
|
int code = (int)wrenGetSlotDouble(vm, 1);
|
|
|
|
|
|
|
|
if (code < 0 || code > 255) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char result[2] = { (char)code, '\0' };
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements String.length().
|
|
|
|
void stringLength(WrenVM* vm) {
|
|
|
|
int len;
|
|
|
|
const char* str = wrenGetSlotBytes(vm, 0, &len);
|
|
|
|
wrenSetSlotDouble(vm, 0, (double)len);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static method: String.join(_, _).
|
|
|
|
void stringJoin(WrenVM* vm) {
|
|
|
|
// First argument should be a list
|
|
|
|
if (wrenGetSlotType(vm, 1) != WREN_TYPE_LIST) {
|
|
|
|
wrenSetSlotString(vm, 0, "First argument must be a list.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!validateString(vm, 2, "Separator")) return;
|
|
|
|
|
|
|
|
int sepLen;
|
|
|
|
const char* separator = wrenGetSlotBytes(vm, 2, &sepLen);
|
|
|
|
|
|
|
|
int count = wrenGetListCount(vm, 1);
|
|
|
|
if (count == 0) {
|
|
|
|
wrenSetSlotString(vm, 0, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate total length needed
|
|
|
|
size_t totalLen = 0;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
wrenGetListElement(vm, 1, i, 0);
|
|
|
|
if (wrenGetSlotType(vm, 0) == WREN_TYPE_STRING) {
|
|
|
|
int itemLen;
|
|
|
|
wrenGetSlotBytes(vm, 0, &itemLen);
|
|
|
|
totalLen += itemLen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
totalLen += (count - 1) * sepLen;
|
|
|
|
|
|
|
|
char* result = (char*)malloc(totalLen + 1);
|
|
|
|
if (!result) {
|
|
|
|
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
|
|
|
wrenAbortFiber(vm, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* p = result;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
wrenGetListElement(vm, 1, i, 0);
|
|
|
|
if (wrenGetSlotType(vm, 0) == WREN_TYPE_STRING) {
|
|
|
|
int itemLen;
|
|
|
|
const char* item = wrenGetSlotBytes(vm, 0, &itemLen);
|
|
|
|
memcpy(p, item, itemLen);
|
|
|
|
p += itemLen;
|
|
|
|
|
|
|
|
if (i < count - 1) {
|
|
|
|
memcpy(p, separator, sepLen);
|
|
|
|
p += sepLen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
|
|
|
|
wrenSetSlotString(vm, 0, result);
|
|
|
|
free(result);
|
|
|
|
}
|
|
|
|
|
2025-07-29 15:15:00 +02:00
|
|
|
// Binds the foreign methods for the String class.
|
|
|
|
WrenForeignMethodFn bindStringForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
|
|
|
if (strcmp(module, "main") != 0 && strcmp(module, "core") != 0) return NULL;
|
2025-08-20 22:17:34 +02:00
|
|
|
if (strcmp(className, "String") != 0) return NULL;
|
|
|
|
|
|
|
|
// Instance methods
|
|
|
|
if (!isStatic) {
|
|
|
|
if (strcmp(signature, "endsWith(_)") == 0) return stringEndsWith;
|
|
|
|
if (strcmp(signature, "startsWith(_)") == 0) return stringStartsWith;
|
|
|
|
if (strcmp(signature, "replace(_,_)") == 0) return stringReplace;
|
|
|
|
if (strcmp(signature, "split(_)") == 0) return stringSplit;
|
|
|
|
if (strcmp(signature, "toUpper()") == 0) return stringToUpper;
|
|
|
|
if (strcmp(signature, "toLower()") == 0) return stringToLower;
|
|
|
|
if (strcmp(signature, "trim()") == 0) return stringTrim;
|
|
|
|
if (strcmp(signature, "trimStart()") == 0) return stringTrimStart;
|
|
|
|
if (strcmp(signature, "trimEnd()") == 0) return stringTrimEnd;
|
|
|
|
if (strcmp(signature, "count(_)") == 0) return stringCount;
|
|
|
|
if (strcmp(signature, "indexOf(_)") == 0) return stringIndexOf;
|
|
|
|
if (strcmp(signature, "lastIndexOf(_)") == 0) return stringLastIndexOf;
|
|
|
|
if (strcmp(signature, "contains(_)") == 0) return stringContains;
|
|
|
|
if (strcmp(signature, "toInt()") == 0) return stringToInt;
|
|
|
|
if (strcmp(signature, "toNum()") == 0) return stringToNum;
|
|
|
|
if (strcmp(signature, "reverse()") == 0) return stringReverse;
|
|
|
|
if (strcmp(signature, "repeat(_)") == 0) return stringRepeat;
|
|
|
|
if (strcmp(signature, "padStart(_,_)") == 0) return stringPadStart;
|
|
|
|
if (strcmp(signature, "padEnd(_,_)") == 0) return stringPadEnd;
|
|
|
|
if (strcmp(signature, "charAt(_)") == 0) return stringCharAt;
|
|
|
|
if (strcmp(signature, "charCodeAt(_)") == 0) return stringCharCodeAt;
|
|
|
|
if (strcmp(signature, "isNumeric()") == 0) return stringIsNumeric;
|
|
|
|
if (strcmp(signature, "isAlpha()") == 0) return stringIsAlpha;
|
|
|
|
if (strcmp(signature, "isAlphaNumeric()") == 0) return stringIsAlphaNumeric;
|
|
|
|
if (strcmp(signature, "isEmpty()") == 0) return stringIsEmpty;
|
|
|
|
if (strcmp(signature, "isWhitespace()") == 0) return stringIsWhitespace;
|
|
|
|
if (strcmp(signature, "capitalize()") == 0) return stringCapitalize;
|
|
|
|
if (strcmp(signature, "toCamelCase()") == 0) return stringToCamelCase;
|
|
|
|
if (strcmp(signature, "toSnakeCase()") == 0) return stringToSnakeCase;
|
|
|
|
if (strcmp(signature, "toKebabCase()") == 0) return stringToKebabCase;
|
|
|
|
if (strcmp(signature, "toPascalCase()") == 0) return stringToPascalCase;
|
|
|
|
if (strcmp(signature, "swapCase()") == 0) return stringSwapCase;
|
|
|
|
if (strcmp(signature, "substring(_,_)") == 0) return stringSubstring;
|
|
|
|
if (strcmp(signature, "slice(_,_)") == 0) return stringSlice;
|
|
|
|
if (strcmp(signature, "slice(_)") == 0) return stringSlice;
|
|
|
|
if (strcmp(signature, "toBytes()") == 0) return stringToBytes;
|
|
|
|
if (strcmp(signature, "length()") == 0) return stringLength; // Added length method
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static methods
|
|
|
|
if (isStatic) {
|
|
|
|
if (strcmp(signature, "join(_,_)") == 0) return stringJoin;
|
|
|
|
if (strcmp(signature, "fromCharCode(_)") == 0) return stringFromCharCode;
|
|
|
|
}
|
|
|
|
|
2025-07-29 15:15:00 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|