// Kyler Olsen // YREA SLS // String Helpers // November 2025 #include #include #include #include #include #include #include #include "sls/string.h" #include "sls/lexer.h" int sls_isascii(unsigned char c) { return c < 128; } size_t sls_str_nlen(const char *s, size_t maxlen) { size_t i; for (i = 0; i < maxlen; i++) if (s[i] == '\0') break; return i; } SlsStr sls_str_malloc(const char *s, size_t maxlen) { size_t length = sls_str_nlen(s, maxlen); char *new_str = (char *)malloc(sizeof(char) * (length + 1)); if (new_str == NULL) return SLS_STR_NULL; memcpy(new_str, s, length); new_str[length] = '\0'; return (SlsStr){length, new_str, TRUE}; } SlsStr sls_str_new(size_t length) { char *new_str = (char *)calloc(length + 1, sizeof(char)); if (new_str == NULL) return SLS_STR_NULL; return (SlsStr){length, new_str, TRUE}; } SlsStr sls_str_cpy(const SlsStr s) { return sls_str_malloc(s.str, s.len); } int32_t sls_str_cmp(const SlsStr a, const SlsStr b) { int cmp = strncmp(a.str, b.str, (a.len < b.len) ? a.len : b.len); if (cmp != 0) return cmp; return (a.len > b.len) - (a.len < b.len); } void sls_str_free(SlsStr *s) { if (s->allocated) { free((void *)s->str); s->len = 0; s->str = NULL; s->allocated = FALSE; } } typedef enum { FORMAT_PERCENT_ESCAPE, FORMAT_C_STRINGS, FORMAT_CHARACTER, FORMAT_INTEGER_32, FORMAT_INTEGER_64, FORMAT_UNSIGNED_INTEGER_64, FORMAT_SIZE_INTEGER, FORMAT_FLOAT, FORMAT_SLS_STR, FORMAT_SLS_TOKEN_TYPE, FORMAT_SLS_ARRAY_TYPE, FORMAT_SLS_BUILTIN_INTEGER, FORMAT_SLS_ERROR, FORMAT_SLS_BOOLEAN, } FormatStringTypes; typedef struct { FormatStringTypes type; union { const char *c_string; char character; int32_t integer_32; int64_t integer_64; uint64_t unsigned_integer_64; size_t size_integer; double ffloat; SlsStr sls_str; TokenType token_type; ArrayType array_type; IntegerBuiltInType builtin_integer; SlsError error; Boolean boolean; }; ptrdiff_t str_index; size_t self_length; } FormatStringItem; static size_t number_length(int64_t i) { if (i == 0) return 1; size_t len = (i < 0 ? 1 : 0); while (i) { len++; i /= 10; } return len; } static size_t unsigned_number_length(uint64_t i) { if (i == 0) return 1; size_t len = 0; while (i) { len++; i /= 10; } return len; } SlsStr sls_format(const SlsStr s, ...) { va_list args; va_start(args, s); size_t count = 0; const char *current = strchr(s.str, '%'); do { if (!current) break; if (!current[1]) break; switch (current[1]) { case '%': case 'y': case 'c': case 'd': case 'l': case 'u': case 'z': case 'f': case 's': case 't': case 'a': case 'i': case 'e': case 'b': count++; break; } current = strchr(current + 2, '%'); } while (current); FormatStringItem *items = (FormatStringItem *)malloc(sizeof(FormatStringItem) * count); if (items == NULL) return SLS_STR_NULL; size_t i = 0; const char *last_index = s.str; size_t length = s.len; current = strchr(s.str, '%'); do { switch (current[1]) { case '%': items[i].type = FORMAT_PERCENT_ESCAPE; length += items[i].self_length = 1; length -= 2; break; case 'y': items[i].type = FORMAT_C_STRINGS; items[i].c_string = va_arg(args, const char *); length += items[i].self_length = strlen(items[i].c_string); length -= 2; break; case 'c': items[i].type = FORMAT_CHARACTER; items[i].character = va_arg(args, int); length += items[i].self_length = 1; length -= 2; break; case 'd': items[i].type = FORMAT_INTEGER_32; items[i].integer_32 = va_arg(args, int32_t); length += items[i].self_length = number_length(items[i].integer_32); length -= 2; break; case 'l': items[i].type = FORMAT_INTEGER_64; items[i].integer_64 = va_arg(args, int64_t); length += items[i].self_length = number_length(items[i].integer_64); length -= 2; break; case 'u': items[i].type = FORMAT_UNSIGNED_INTEGER_64; items[i].unsigned_integer_64 = va_arg(args, uint64_t); length += items[i].self_length = unsigned_number_length(items[i].unsigned_integer_64); length -= 2; break; case 'z': items[i].type = FORMAT_SIZE_INTEGER; items[i].size_integer = va_arg(args, size_t); length += items[i].self_length = unsigned_number_length(items[i].size_integer); length -= 2; break; case 'f': items[i].type = FORMAT_FLOAT; items[i].ffloat = va_arg(args, double); length += items[i].self_length = snprintf(NULL, 0, "%.2f", items[i].ffloat); length -= 2; break; case 's': items[i].type = FORMAT_SLS_STR; items[i].sls_str = va_arg(args, SlsStr); length += items[i].self_length = items[i].sls_str.len; length -= 2; break; case 't': items[i].type = FORMAT_SLS_TOKEN_TYPE; items[i].token_type = va_arg(args, TokenType); if (items[i].token_type >= TOKEN_TYPE_COUNT) { free(items); return SLS_STR_NULL; } length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH); length -= 2; break; case 'a': items[i].type = FORMAT_SLS_ARRAY_TYPE; items[i].array_type = va_arg(args, ArrayType); if (items[i].array_type >= ARRAY_TYPE_COUNT) { free(items); return SLS_STR_NULL; } length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH); length -= 2; break; case 'i': items[i].type = FORMAT_SLS_BUILTIN_INTEGER; items[i].builtin_integer = va_arg(args, IntegerBuiltInType); if (items[i].builtin_integer >= INTEGER_TYPE_COUNT) { free(items); return SLS_STR_NULL; } length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH); length -= 2; break; case 'e': items[i].type = FORMAT_SLS_ERROR; items[i].error = va_arg(args, SlsError); length += items[i].self_length = items[i].error.message.len; length -= 2; break; case 'b': items[i].type = FORMAT_SLS_BOOLEAN; items[i].boolean = va_arg(args, Boolean); length += items[i].self_length = (items[i].boolean ? 4 : 5); length -= 2; break; } items[i].str_index = (ptrdiff_t)(current - last_index); last_index = current + 2; i++; current = strchr(current + 2, '%'); } while (current); va_end(args); char *temp = (char *)malloc(sizeof(char) * length); if (temp == NULL) { free(items); return SLS_STR_NULL; } SlsStr str_new = sls_str_new(length); if (str_new.str == NULL) { free(items); free(temp); return SLS_STR_NULL; } char *str = (char *)str_new.str; size_t item_i = 0; ptrdiff_t target_i = 0; ptrdiff_t source_i = 0; while (item_i < count) { memcpy(str + target_i, s.str + source_i, items[item_i].str_index); target_i += items[item_i].str_index; source_i += items[item_i].str_index + 2; switch (items[item_i].type) { case FORMAT_PERCENT_ESCAPE: memcpy(temp, "%", items[item_i].self_length + 1); break; case FORMAT_C_STRINGS: snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].c_string); break; case FORMAT_CHARACTER: snprintf(temp, items[item_i].self_length + 1, "%c", items[item_i].character); break; case FORMAT_INTEGER_32: snprintf(temp, items[item_i].self_length + 1, "%d", items[item_i].integer_32); break; case FORMAT_INTEGER_64: snprintf(temp, items[item_i].self_length + 1, "%ld", items[item_i].integer_64); break; case FORMAT_UNSIGNED_INTEGER_64: snprintf(temp, items[item_i].self_length + 1, "%lu", items[item_i].unsigned_integer_64); break; case FORMAT_SIZE_INTEGER: snprintf(temp, items[item_i].self_length + 1, "%zu", items[item_i].size_integer); break; case FORMAT_FLOAT: snprintf(temp, items[item_i].self_length + 1, "%.2f", items[item_i].ffloat); // Fixed-point decimal display break; case FORMAT_SLS_STR: snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].sls_str.str); break; case FORMAT_SLS_TOKEN_TYPE: snprintf(temp, items[item_i].self_length + 1, "%s", TOKEN_TYPES_NAMES[items[item_i].token_type]); break; case FORMAT_SLS_ARRAY_TYPE: snprintf(temp, items[item_i].self_length + 1, "%s", ARRAY_TYPES_NAMES[items[item_i].array_type]); break; case FORMAT_SLS_BUILTIN_INTEGER: snprintf(temp, items[item_i].self_length + 1, "%s", INTEGER_TYPES_NAMES[items[item_i].builtin_integer]); break; case FORMAT_SLS_ERROR: snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].error.message.str); break; case FORMAT_SLS_BOOLEAN: memcpy(temp, (items[item_i].boolean ? "TRUE" : "FALSE"), items[item_i].self_length + 1); break; } memcpy(str + target_i, temp, items[item_i].self_length); target_i += items[item_i].self_length; item_i++; } if (s.len > (size_t)source_i) memcpy(str + target_i, s.str + source_i, s.len - source_i); str[str_new.len] = '\0'; free(items); free(temp); return str_new; }